Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
86.54% covered (warning)
86.54%
45 / 52
50.00% covered (danger)
50.00%
2 / 4
CRAP
0.00% covered (danger)
0.00%
0 / 1
LayeredParameterizedPassword
88.24% covered (warning)
88.24%
45 / 51
50.00% covered (danger)
50.00%
2 / 4
17.47
0.00% covered (danger)
0.00%
0 / 1
 getDelimiter
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDefaultParams
63.64% covered (warning)
63.64%
7 / 11
0.00% covered (danger)
0.00%
0 / 1
4.77
 crypt
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
5
 partialCrypt
91.30% covered (success)
91.30%
21 / 23
0.00% covered (danger)
0.00%
0 / 1
7.03
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
23declare( strict_types = 1 );
24
25namespace MediaWiki\Password;
26
27use InvalidArgumentException;
28use UnexpectedValueException;
29
30/**
31 * This password hash type layers one or more parameterized password types
32 * on top of each other.
33 *
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.
37 *
38 * @since 1.24
39 */
40class LayeredParameterizedPassword extends ParameterizedPassword {
41    protected function getDelimiter(): string {
42        return '!';
43    }
44
45    protected function getDefaultParams(): array {
46        $params = [];
47
48        foreach ( $this->config['types'] as $type ) {
49            $passObj = $this->factory->newFromType( $type );
50
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.'
56                );
57            }
58
59            $params[] = implode( $passObj->getDelimiter(), $passObj->getDefaultParams() );
60        }
61
62        return $params;
63    }
64
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';
72
73            $params = '';
74            $args = '';
75            if ( $this->params[$i] !== '' ) {
76                $params = $this->params[$i] . $passObj->getDelimiter();
77            }
78            if ( isset( $this->args[$i] ) && $this->args[$i] !== '' ) {
79                $args = $this->args[$i] . $passObj->getDelimiter();
80            }
81            $existingHash = ":$type:" . $params . $args . $this->hash;
82
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 );
87
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;
92        }
93
94        $this->hash = $lastHash;
95    }
96
97    /**
98     * Finish the hashing of a partially hashed layered hash
99     *
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.
103     *
104     * @param ParameterizedPassword $passObj Password hash of the first layer
105     */
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.' );
110        }
111
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;
116
117        // Layer the remaining types
118        foreach ( $this->config['types'] as $i => $type ) {
119            if ( $i == 0 ) {
120                continue;
121            }
122
123            // Construct pseudo-hash based on params and arguments
124            /** @var ParameterizedPassword $passObj */
125            $passObj = $this->factory->newFromType( $type );
126            '@phan-var ParameterizedPassword $passObj';
127
128            $params = '';
129            $args = '';
130            if ( $this->params[$i] !== '' ) {
131                $params = $this->params[$i] . $passObj->getDelimiter();
132            }
133            if ( isset( $this->args[$i] ) && $this->args[$i] !== '' ) {
134                $args = $this->args[$i] . $passObj->getDelimiter();
135            }
136            $existingHash = ":$type:" . $params . $args . $this->hash;
137
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 );
142
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;
147        }
148
149        $this->hash = $lastHash;
150    }
151}
152
153/** @deprecated since 1.43 use MediaWiki\\Password\\LayeredParameterizedPassword */
154class_alias( LayeredParameterizedPassword::class, 'LayeredParameterizedPassword' );