PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / trunk
JetBackup – Backup, Restore & Migrate vtrunk
3.1.22.4 3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / src / JetBackup / 3rdparty / phpseclib3 / Crypt / Common / Formats / Keys / PuTTY.php
backup / src / JetBackup / 3rdparty / phpseclib3 / Crypt / Common / Formats / Keys Last commit date
.htaccess 1 year ago JWK.php 1 year ago OpenSSH.php 1 year ago PKCS.php 1 year ago PKCS1.php 1 year ago PKCS8.php 1 year ago PuTTY.php 1 year ago index.html 1 year ago web.config 1 year ago
PuTTY.php
354 lines
1 <?php
2
3 /**
4 * PuTTY Formatted Key Handler
5 *
6 * See PuTTY's SSHPUBK.C and https://tartarus.org/~simon/putty-snapshots/htmldoc/AppendixC.html
7 *
8 * PHP version 5
9 *
10 * @author Jim Wigginton <terrafrost@php.net>
11 * @copyright 2016 Jim Wigginton
12 * @license http://www.opensource.org/licenses/mit-license.html MIT License
13 * @link http://phpseclib.sourceforge.net
14 */
15
16 declare(strict_types=1);
17
18 namespace phpseclib3\Crypt\Common\Formats\Keys;
19
20 use phpseclib3\Common\Functions\Strings;
21 use phpseclib3\Crypt\AES;
22 use phpseclib3\Crypt\Hash;
23 use phpseclib3\Crypt\Random;
24 use phpseclib3\Exception\RuntimeException;
25 use phpseclib3\Exception\UnexpectedValueException;
26 use phpseclib3\Exception\UnsupportedAlgorithmException;
27
28 /**
29 * PuTTY Formatted Key Handler
30 *
31 * @author Jim Wigginton <terrafrost@php.net>
32 */
33 abstract class PuTTY
34 {
35 /**
36 * Default comment
37 *
38 * @var string
39 */
40 private static $comment = 'phpseclib-generated-key';
41
42 /**
43 * Default version
44 *
45 * @var int
46 */
47 private static $version = 2;
48
49 /**
50 * Sets the default comment
51 */
52 public static function setComment(string $comment): void
53 {
54 self::$comment = str_replace(["\r", "\n"], '', $comment);
55 }
56
57 /**
58 * Sets the default version
59 */
60 public static function setVersion(int $version): void
61 {
62 if ($version != 2 && $version != 3) {
63 throw new RuntimeException('Only supported versions are 2 and 3');
64 }
65 self::$version = $version;
66 }
67
68 /**
69 * Generate a symmetric key for PuTTY v2 keys
70 */
71 private static function generateV2Key(string $password, int $length): string
72 {
73 $symkey = '';
74 $sequence = 0;
75 while (strlen($symkey) < $length) {
76 $temp = pack('Na*', $sequence++, $password);
77 $symkey .= Strings::hex2bin(sha1($temp));
78 }
79 return substr($symkey, 0, $length);
80 }
81
82 /**
83 * Generate a symmetric key for PuTTY v3 keys
84 */
85 private static function generateV3Key(string $password, string $flavour, int $memory, int $passes, string $salt): array
86 {
87 if (!function_exists('sodium_crypto_pwhash')) {
88 throw new RuntimeException('sodium_crypto_pwhash needs to exist for Argon2 password hasing');
89 }
90
91 switch ($flavour) {
92 case 'Argon2i':
93 $flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2I13;
94 break;
95 case 'Argon2id':
96 $flavour = SODIUM_CRYPTO_PWHASH_ALG_ARGON2ID13;
97 break;
98 default:
99 throw new UnsupportedAlgorithmException('Only Argon2i and Argon2id are supported');
100 }
101
102 $length = 80; // keylen + ivlen + mac_keylen
103 $temp = sodium_crypto_pwhash($length, $password, $salt, $passes, $memory << 10, $flavour);
104
105 $symkey = substr($temp, 0, 32);
106 $symiv = substr($temp, 32, 16);
107 $hashkey = substr($temp, -32);
108
109 return compact('symkey', 'symiv', 'hashkey');
110 }
111
112 /**
113 * Break a public or private key down into its constituent components
114 *
115 * @param array|string $key
116 * @param string|false $password
117 * @return array|false
118 */
119 public static function load($key, $password)
120 {
121 if (!Strings::is_stringable($key)) {
122 throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key));
123 }
124
125 if (str_contains($key, 'BEGIN SSH2 PUBLIC KEY')) {
126 $lines = preg_split('#[\r\n]+#', $key);
127 switch (true) {
128 case $lines[0] != '---- BEGIN SSH2 PUBLIC KEY ----':
129 throw new UnexpectedValueException('Key doesn\'t start with ---- BEGIN SSH2 PUBLIC KEY ----');
130 case $lines[count($lines) - 1] != '---- END SSH2 PUBLIC KEY ----':
131 throw new UnexpectedValueException('Key doesn\'t end with ---- END SSH2 PUBLIC KEY ----');
132 }
133 $lines = array_splice($lines, 1, -1);
134 $lines = array_map(fn ($line) => rtrim($line, "\r\n"), $lines);
135 $data = $current = '';
136 $values = [];
137 $in_value = false;
138 foreach ($lines as $line) {
139 switch (true) {
140 case preg_match('#^(.*?): (.*)#', $line, $match):
141 $in_value = $line[-1] == '\\';
142 $current = strtolower($match[1]);
143 $values[$current] = $in_value ? substr($match[2], 0, -1) : $match[2];
144 break;
145 case $in_value:
146 $in_value = $line[-1] == '\\';
147 $values[$current] .= $in_value ? substr($line, 0, -1) : $line;
148 break;
149 default:
150 $data .= $line;
151 }
152 }
153
154 $components = call_user_func([static::PUBLIC_HANDLER, 'load'], $data);
155 if ($components === false) {
156 throw new UnexpectedValueException('Unable to decode public key');
157 }
158 $components += $values;
159 $components['comment'] = str_replace(['\\\\', '\"'], ['\\', '"'], $values['comment']);
160
161 return $components;
162 }
163
164 $components = [];
165
166 $key = preg_split('#\r\n|\r|\n#', trim($key));
167 if (Strings::shift($key[0], strlen('PuTTY-User-Key-File-')) != 'PuTTY-User-Key-File-') {
168 return false;
169 }
170 $version = (int) Strings::shift($key[0], 3); // should be either "2: " or "3: 0" prior to int casting
171 if ($version != 2 && $version != 3) {
172 throw new RuntimeException('Only v2 and v3 PuTTY private keys are supported');
173 }
174 $components['type'] = $type = rtrim($key[0]);
175 if (!in_array($type, static::$types)) {
176 $error = count(static::$types) == 1 ?
177 'Only ' . static::$types[0] . ' keys are supported. ' :
178 '';
179 throw new UnsupportedAlgorithmException($error . 'This is an unsupported ' . $type . ' key');
180 }
181 $encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1]));
182 $components['comment'] = trim(preg_replace('#Comment: (.+)#', '$1', $key[2]));
183
184 $publicLength = (int) trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3]));
185 $public = Strings::base64_decode(implode('', array_map('trim', array_slice($key, 4, $publicLength))));
186
187 $source = Strings::packSSH2('ssss', $type, $encryption, $components['comment'], $public);
188
189 extract(unpack('Nlength', Strings::shift($public, 4)));
190 $newtype = Strings::shift($public, $length);
191 if ($newtype != $type) {
192 throw new RuntimeException('The binary type does not match the human readable type field');
193 }
194
195 $components['public'] = $public;
196
197 switch ($version) {
198 case 3:
199 $hashkey = '';
200 break;
201 case 2:
202 $hashkey = 'putty-private-key-file-mac-key';
203 }
204
205 $offset = $publicLength + 4;
206 switch ($encryption) {
207 case 'aes256-cbc':
208 $crypto = new AES('cbc');
209 switch ($version) {
210 case 3:
211 $flavour = trim(preg_replace('#Key-Derivation: (.*)#', '$1', $key[$offset++]));
212 $memory = trim(preg_replace('#Argon2-Memory: (\d+)#', '$1', $key[$offset++]));
213 $passes = trim(preg_replace('#Argon2-Passes: (\d+)#', '$1', $key[$offset++]));
214 $parallelism = trim(preg_replace('#Argon2-Parallelism: (\d+)#', '$1', $key[$offset++]));
215 $salt = Strings::hex2bin(trim(preg_replace('#Argon2-Salt: ([0-9a-f]+)#', '$1', $key[$offset++])));
216
217 extract(self::generateV3Key($password, $flavour, (int)$memory, (int)$passes, $salt));
218
219 break;
220 case 2:
221 $symkey = self::generateV2Key($password, 32);
222 $symiv = str_repeat("\0", $crypto->getBlockLength() >> 3);
223 $hashkey .= $password;
224 }
225 }
226
227 switch ($version) {
228 case 3:
229 $hash = new Hash('sha256');
230 $hash->setKey($hashkey);
231 break;
232 case 2:
233 $hash = new Hash('sha1');
234 $hash->setKey(sha1($hashkey, true));
235 }
236
237 $privateLength = (int) trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$offset++]));
238 $private = Strings::base64_decode(implode('', array_map('trim', array_slice($key, $offset, $privateLength))));
239
240 if ($encryption != 'none') {
241 $crypto->setKey($symkey);
242 $crypto->setIV($symiv);
243 $crypto->disablePadding();
244 $private = $crypto->decrypt($private);
245 }
246
247 $source .= Strings::packSSH2('s', $private);
248
249 $hmac = trim(preg_replace('#Private-MAC: (.+)#', '$1', $key[$offset + $privateLength]));
250 $hmac = Strings::hex2bin($hmac);
251
252 if (!hash_equals($hash->hash($source), $hmac)) {
253 throw new UnexpectedValueException('MAC validation error');
254 }
255
256 $components['private'] = $private;
257
258 return $components;
259 }
260
261 /**
262 * Wrap a private key appropriately
263 *
264 * @param string|false $password
265 * @param array $options optional
266 */
267 protected static function wrapPrivateKey(string $public, string $private, string $type, $password, array $options = []): string
268 {
269 $encryption = (!empty($password) || is_string($password)) ? 'aes256-cbc' : 'none';
270 $comment = $options['comment'] ?? self::$comment;
271 $version = $options['version'] ?? self::$version;
272
273 $key = "PuTTY-User-Key-File-$version: $type\r\n";
274 $key .= "Encryption: $encryption\r\n";
275 $key .= "Comment: $comment\r\n";
276
277 $public = Strings::packSSH2('s', $type) . $public;
278
279 $source = Strings::packSSH2('ssss', $type, $encryption, $comment, $public);
280
281 $public = Strings::base64_encode($public);
282 $key .= "Public-Lines: " . ((strlen($public) + 63) >> 6) . "\r\n";
283 $key .= chunk_split($public, 64);
284
285 if (empty($password) && !is_string($password)) {
286 $source .= Strings::packSSH2('s', $private);
287 switch ($version) {
288 case 3:
289 $hash = new Hash('sha256');
290 $hash->setKey('');
291 break;
292 case 2:
293 $hash = new Hash('sha1');
294 $hash->setKey(sha1('putty-private-key-file-mac-key', true));
295 }
296 } else {
297 $private .= Random::string(16 - (strlen($private) & 15));
298 $source .= Strings::packSSH2('s', $private);
299 $crypto = new AES('cbc');
300
301 switch ($version) {
302 case 3:
303 $salt = Random::string(16);
304 $key .= "Key-Derivation: Argon2id\r\n";
305 $key .= "Argon2-Memory: 8192\r\n";
306 $key .= "Argon2-Passes: 13\r\n";
307 $key .= "Argon2-Parallelism: 1\r\n";
308 $key .= "Argon2-Salt: " . Strings::bin2hex($salt) . "\r\n";
309 extract(self::generateV3Key($password, 'Argon2id', 8192, 13, $salt));
310
311 $hash = new Hash('sha256');
312 $hash->setKey($hashkey);
313
314 break;
315 case 2:
316 $symkey = self::generateV2Key($password, 32);
317 $symiv = str_repeat("\0", $crypto->getBlockLength() >> 3);
318 $hashkey = 'putty-private-key-file-mac-key' . $password;
319
320 $hash = new Hash('sha1');
321 $hash->setKey(sha1($hashkey, true));
322 }
323
324 $crypto->setKey($symkey);
325 $crypto->setIV($symiv);
326 $crypto->disablePadding();
327 $private = $crypto->encrypt($private);
328 $mac = $hash->hash($source);
329 }
330
331 $private = Strings::base64_encode($private);
332 $key .= 'Private-Lines: ' . ((strlen($private) + 63) >> 6) . "\r\n";
333 $key .= chunk_split($private, 64);
334 $key .= 'Private-MAC: ' . Strings::bin2hex($hash->hash($source)) . "\r\n";
335
336 return $key;
337 }
338
339 /**
340 * Wrap a public key appropriately
341 *
342 * This is basically the format described in RFC 4716 (https://tools.ietf.org/html/rfc4716)
343 */
344 protected static function wrapPublicKey(string $key, string $type): string
345 {
346 $key = pack('Na*a*', strlen($type), $type, $key);
347 $key = "---- BEGIN SSH2 PUBLIC KEY ----\r\n" .
348 'Comment: "' . str_replace(['\\', '"'], ['\\\\', '\"'], self::$comment) . "\"\r\n" .
349 chunk_split(Strings::base64_encode($key), 64) .
350 '---- END SSH2 PUBLIC KEY ----';
351 return $key;
352 }
353 }
354