3 * Implements the LayeredParameterizedPassword class for the MediaWiki software.
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.
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.
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
23 declare( strict_types
= 1 );
25 namespace MediaWiki\Password
;
27 use InvalidArgumentException
;
28 use UnexpectedValueException
;
31 * This password hash type layers one or more parameterized password types
32 * on top of each other.
34 * The underlying types must be parameterized. This wrapping type accumulates
35 * all the parameters and arguments from each hash and then passes the hash of
36 * the last layer as the password for the next layer.
40 class LayeredParameterizedPassword
extends ParameterizedPassword
{
41 protected function getDelimiter(): string {
45 protected function getDefaultParams(): array {
48 foreach ( $this->config
['types'] as $type ) {
49 $passObj = $this->factory
->newFromType( $type );
51 if ( !$passObj instanceof ParameterizedPassword
) {
52 throw new UnexpectedValueException( 'Underlying type must be a parameterized password.' );
53 } elseif ( $passObj->getDelimiter() === $this->getDelimiter() ) {
54 throw new UnexpectedValueException(
55 'Underlying type cannot use same delimiter as encapsulating type.'
59 $params[] = implode( $passObj->getDelimiter(), $passObj->getDefaultParams() );
65 public function crypt( string $password ): void
{
66 $lastHash = $password;
67 foreach ( $this->config
['types'] as $i => $type ) {
68 // Construct pseudo-hash based on params and arguments
69 /** @var ParameterizedPassword $passObj */
70 $passObj = $this->factory
->newFromType( $type );
71 '@phan-var ParameterizedPassword $passObj';
75 if ( $this->params
[$i] !== '' ) {
76 $params = $this->params
[$i] . $passObj->getDelimiter();
78 if ( isset( $this->args
[$i] ) && $this->args
[$i] !== '' ) {
79 $args = $this->args
[$i] . $passObj->getDelimiter();
81 $existingHash = ":$type:" . $params . $args . $this->hash
;
83 // Hash the last hash with the next type in the layer
84 $passObj = $this->factory
->newFromCiphertext( $existingHash );
85 '@phan-var ParameterizedPassword $passObj';
86 $passObj->crypt( $lastHash );
88 // Move over the params and args
89 $this->params
[$i] = implode( $passObj->getDelimiter(), $passObj->params
);
90 $this->args
[$i] = implode( $passObj->getDelimiter(), $passObj->args
);
91 $lastHash = $passObj->hash
;
94 $this->hash
= $lastHash;
98 * Finish the hashing of a partially hashed layered hash
100 * Given a password hash that is hashed using the first layer of this object's
101 * configuration, perform the remaining layers of password hashing in order to
102 * get an updated hash with all the layers.
104 * @param ParameterizedPassword $passObj Password hash of the first layer
106 public function partialCrypt( ParameterizedPassword
$passObj ) {
107 $type = $passObj->config
['type'];
108 if ( $type !== $this->config
['types'][0] ) {
109 throw new InvalidArgumentException( 'Only a hash in the first layer can be finished.' );
112 // Gather info from the existing hash
113 $this->params
[0] = implode( $passObj->getDelimiter(), $passObj->params
);
114 $this->args
[0] = implode( $passObj->getDelimiter(), $passObj->args
);
115 $lastHash = $passObj->hash
;
117 // Layer the remaining types
118 foreach ( $this->config
['types'] as $i => $type ) {
123 // Construct pseudo-hash based on params and arguments
124 /** @var ParameterizedPassword $passObj */
125 $passObj = $this->factory
->newFromType( $type );
126 '@phan-var ParameterizedPassword $passObj';
130 if ( $this->params
[$i] !== '' ) {
131 $params = $this->params
[$i] . $passObj->getDelimiter();
133 if ( isset( $this->args
[$i] ) && $this->args
[$i] !== '' ) {
134 $args = $this->args
[$i] . $passObj->getDelimiter();
136 $existingHash = ":$type:" . $params . $args . $this->hash
;
138 // Hash the last hash with the next type in the layer
139 $passObj = $this->factory
->newFromCiphertext( $existingHash );
140 '@phan-var ParameterizedPassword $passObj';
141 $passObj->crypt( $lastHash );
143 // Move over the params and args
144 $this->params
[$i] = implode( $passObj->getDelimiter(), $passObj->params
);
145 $this->args
[$i] = implode( $passObj->getDelimiter(), $passObj->args
);
146 $lastHash = $passObj->hash
;
149 $this->hash
= $lastHash;
153 /** @deprecated since 1.43 use MediaWiki\\Password\\LayeredParameterizedPassword */
154 class_alias( LayeredParameterizedPassword
::class, 'LayeredParameterizedPassword' );