Merge "Make DBAccessBase use DBConnRef, rename $wiki, and hide getLoadBalancer()"
[lhc/web/wiklou.git] / includes / password / LayeredParameterizedPassword.php
1 <?php
2 /**
3 * Implements the LayeredParameterizedPassword class for the MediaWiki software.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 */
22
23 /**
24 * This password hash type layers one or more parameterized password types
25 * on top of each other.
26 *
27 * The underlying types must be parameterized. This wrapping type accumulates
28 * all the parameters and arguments from each hash and then passes the hash of
29 * the last layer as the password for the next layer.
30 *
31 * @since 1.24
32 */
33 class LayeredParameterizedPassword extends ParameterizedPassword {
34 protected function getDelimiter() {
35 return '!';
36 }
37
38 protected function getDefaultParams() {
39 $params = [];
40
41 foreach ( $this->config['types'] as $type ) {
42 $passObj = $this->factory->newFromType( $type );
43
44 if ( !$passObj instanceof ParameterizedPassword ) {
45 throw new MWException( 'Underlying type must be a parameterized password.' );
46 } elseif ( $passObj->getDelimiter() === $this->getDelimiter() ) {
47 throw new MWException( 'Underlying type cannot use same delimiter as encapsulating type.' );
48 }
49
50 $params[] = implode( $passObj->getDelimiter(), $passObj->getDefaultParams() );
51 }
52
53 return $params;
54 }
55
56 public function crypt( $password ) {
57 $lastHash = $password;
58 foreach ( $this->config['types'] as $i => $type ) {
59 // Construct pseudo-hash based on params and arguments
60 /** @var ParameterizedPassword $passObj */
61 $passObj = $this->factory->newFromType( $type );
62 '@phan-var ParameterizedPassword $passObj';
63
64 $params = '';
65 $args = '';
66 if ( $this->params[$i] !== '' ) {
67 $params = $this->params[$i] . $passObj->getDelimiter();
68 }
69 if ( isset( $this->args[$i] ) && $this->args[$i] !== '' ) {
70 $args = $this->args[$i] . $passObj->getDelimiter();
71 }
72 $existingHash = ":$type:" . $params . $args . $this->hash;
73
74 // Hash the last hash with the next type in the layer
75 $passObj = $this->factory->newFromCiphertext( $existingHash );
76 '@phan-var ParameterizedPassword $passObj';
77 $passObj->crypt( $lastHash );
78
79 // Move over the params and args
80 $this->params[$i] = implode( $passObj->getDelimiter(), $passObj->params );
81 $this->args[$i] = implode( $passObj->getDelimiter(), $passObj->args );
82 $lastHash = $passObj->hash;
83 }
84
85 $this->hash = $lastHash;
86 }
87
88 /**
89 * Finish the hashing of a partially hashed layered hash
90 *
91 * Given a password hash that is hashed using the first layer of this object's
92 * configuration, perform the remaining layers of password hashing in order to
93 * get an updated hash with all the layers.
94 *
95 * @param ParameterizedPassword $passObj Password hash of the first layer
96 *
97 * @throws MWException If the first parameter is not of the correct type
98 */
99 public function partialCrypt( ParameterizedPassword $passObj ) {
100 $type = $passObj->config['type'];
101 if ( $type !== $this->config['types'][0] ) {
102 throw new MWException( 'Only a hash in the first layer can be finished.' );
103 }
104
105 // Gather info from the existing hash
106 $this->params[0] = implode( $passObj->getDelimiter(), $passObj->params );
107 $this->args[0] = implode( $passObj->getDelimiter(), $passObj->args );
108 $lastHash = $passObj->hash;
109
110 // Layer the remaining types
111 foreach ( $this->config['types'] as $i => $type ) {
112 if ( $i == 0 ) {
113 continue;
114 }
115
116 // Construct pseudo-hash based on params and arguments
117 /** @var ParameterizedPassword $passObj */
118 $passObj = $this->factory->newFromType( $type );
119 '@phan-var ParameterizedPassword $passObj';
120
121 $params = '';
122 $args = '';
123 if ( $this->params[$i] !== '' ) {
124 $params = $this->params[$i] . $passObj->getDelimiter();
125 }
126 if ( isset( $this->args[$i] ) && $this->args[$i] !== '' ) {
127 $args = $this->args[$i] . $passObj->getDelimiter();
128 }
129 $existingHash = ":$type:" . $params . $args . $this->hash;
130
131 // Hash the last hash with the next type in the layer
132 $passObj = $this->factory->newFromCiphertext( $existingHash );
133 '@phan-var ParameterizedPassword $passObj';
134 $passObj->crypt( $lastHash );
135
136 // Move over the params and args
137 $this->params[$i] = implode( $passObj->getDelimiter(), $passObj->params );
138 $this->args[$i] = implode( $passObj->getDelimiter(), $passObj->args );
139 $lastHash = $passObj->hash;
140 }
141
142 $this->hash = $lastHash;
143 }
144 }