Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
90.32% covered (success)
90.32%
56 / 62
40.00% covered (danger)
40.00%
2 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
EncryptedPassword
91.80% covered (success)
91.80%
56 / 61
40.00% covered (danger)
40.00%
2 / 5
17.16
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
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 crypt
94.74% covered (success)
94.74%
18 / 19
0.00% covered (danger)
0.00%
0 / 1
6.01
 update
88.00% covered (warning)
88.00%
22 / 25
0.00% covered (danger)
0.00%
0 / 1
6.06
 verify
91.67% covered (success)
91.67%
11 / 12
0.00% covered (danger)
0.00%
0 / 1
3.01
1<?php
2/**
3 * Implements the EncryptedPassword 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
27/**
28 * Helper class for passwords that use another password hash underneath it
29 * and encrypts that hash with a configured secret.
30 *
31 * @since 1.24
32 */
33class EncryptedPassword extends ParameterizedPassword {
34    protected function getDelimiter(): string {
35        return ':';
36    }
37
38    protected function getDefaultParams(): array {
39        return [
40            'cipher' => $this->config['cipher'],
41            'secret' => (string)( count( $this->config['secrets'] ) - 1 )
42        ];
43    }
44
45    public function crypt( string $password ): void {
46        $secret = $this->config['secrets'][(int)$this->params['secret']];
47
48        // Clear error string
49        while ( openssl_error_string() !== false );
50
51        if ( $this->hash ) {
52            $decrypted = openssl_decrypt(
53                    $this->hash, $this->params['cipher'],
54                    $secret, 0, base64_decode( $this->args[0] ) );
55            if ( $decrypted === false ) {
56                throw new PasswordError( 'Error decrypting password: ' . openssl_error_string() );
57            }
58            $underlyingPassword = $this->factory->newFromCiphertext( $decrypted );
59        } else {
60            $underlyingPassword = $this->factory->newFromType( $this->config['underlying'] );
61        }
62
63        $underlyingPassword->crypt( $password );
64        if ( count( $this->args ) ) {
65            $iv = base64_decode( $this->args[0] );
66        } else {
67            $iv = random_bytes( openssl_cipher_iv_length( $this->params['cipher'] ) );
68        }
69
70        $this->hash = openssl_encrypt(
71            $underlyingPassword->toString(), $this->params['cipher'], $secret, 0, $iv );
72        if ( $this->hash === false ) {
73            throw new PasswordError( 'Error encrypting password: ' . openssl_error_string() );
74        }
75        $this->args = [ base64_encode( $iv ) ];
76    }
77
78    /**
79     * Updates the underlying hash by encrypting it with the newest secret.
80     *
81     * @throws PasswordError If the configuration is not valid
82     * @return bool True if the password was updated
83     */
84    public function update(): bool {
85        if ( count( $this->args ) != 1 || $this->params == $this->getDefaultParams() ) {
86            // Hash does not need updating
87            return false;
88        }
89
90        // Clear error string
91        while ( openssl_error_string() !== false );
92
93        // Decrypt the underlying hash
94        $underlyingHash = openssl_decrypt(
95            $this->hash,
96            $this->params['cipher'],
97            $this->config['secrets'][(int)$this->params['secret']],
98            0,
99            base64_decode( $this->args[0] )
100        );
101        if ( $underlyingHash === false ) {
102            throw new PasswordError( 'Error decrypting password: ' . openssl_error_string() );
103        }
104
105        // Reset the params
106        $this->params = $this->getDefaultParams();
107
108        // Check the key size with the new params
109        $iv = random_bytes( openssl_cipher_iv_length( $this->params['cipher'] ) );
110        $this->hash = openssl_encrypt(
111                $underlyingHash,
112                $this->params['cipher'],
113                $this->config['secrets'][(int)$this->params['secret']],
114                0,
115                $iv
116            );
117        if ( $this->hash === false ) {
118            throw new PasswordError( 'Error encrypting password: ' . openssl_error_string() );
119        }
120
121        $this->args = [ base64_encode( $iv ) ];
122
123        return true;
124    }
125
126    /**
127     * @inheritDoc
128     */
129    public function verify( string $password ): bool {
130        // Clear error string
131        while ( openssl_error_string() !== false );
132
133        // Decrypt the underlying hash
134        $underlyingHash = openssl_decrypt(
135            $this->hash,
136            $this->params['cipher'],
137            $this->config['secrets'][(int)$this->params['secret']],
138            0,
139            base64_decode( $this->args[0] )
140        );
141        if ( $underlyingHash === false ) {
142            throw new PasswordError( 'Error decrypting password: ' . openssl_error_string() );
143        }
144
145        $storedPassword = $this->factory->newFromCiphertext( $underlyingHash );
146        return $storedPassword->verify( $password );
147    }
148}
149
150/** @deprecated since 1.43 use MediaWiki\\Password\\EncryptedPassword */
151class_alias( EncryptedPassword::class, 'EncryptedPassword' );