PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / 3.1.22.3
JetBackup – Backup, Restore & Migrate v3.1.22.3
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 / PKCS8.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
PKCS8.php
698 lines
1 <?php
2
3 /**
4 * PKCS#8 Formatted Key Handler
5 *
6 * PHP version 5
7 *
8 * Used by PHP's openssl_public_encrypt() and openssl's rsautl (when -pubin is set)
9 *
10 * Processes keys with the following headers:
11 *
12 * -----BEGIN ENCRYPTED PRIVATE KEY-----
13 * -----BEGIN PRIVATE KEY-----
14 * -----BEGIN PUBLIC KEY-----
15 *
16 * Analogous to ssh-keygen's pkcs8 format (as specified by -m). Although PKCS8
17 * is specific to private keys it's basically creating a DER-encoded wrapper
18 * for keys. This just extends that same concept to public keys (much like ssh-keygen)
19 *
20 * @author Jim Wigginton <terrafrost@php.net>
21 * @copyright 2015 Jim Wigginton
22 * @license http://www.opensource.org/licenses/mit-license.html MIT License
23 * @link http://phpseclib.sourceforge.net
24 */
25
26 declare(strict_types=1);
27
28 namespace phpseclib3\Crypt\Common\Formats\Keys;
29
30 use phpseclib3\Common\Functions\Strings;
31 use phpseclib3\Crypt\AES;
32 use phpseclib3\Crypt\Common\SymmetricKey;
33 use phpseclib3\Crypt\DES;
34 use phpseclib3\Crypt\Random;
35 use phpseclib3\Crypt\RC2;
36 use phpseclib3\Crypt\RC4;
37 use phpseclib3\Crypt\TripleDES;
38 use phpseclib3\Exception\InsufficientSetupException;
39 use phpseclib3\Exception\RuntimeException;
40 use phpseclib3\Exception\UnexpectedValueException;
41 use phpseclib3\Exception\UnsupportedAlgorithmException;
42 use phpseclib3\File\ASN1;
43 use phpseclib3\File\ASN1\Maps;
44
45 /**
46 * PKCS#8 Formatted Key Handler
47 *
48 * @author Jim Wigginton <terrafrost@php.net>
49 */
50 abstract class PKCS8 extends PKCS
51 {
52 /**
53 * Default encryption algorithm
54 *
55 * @var string
56 */
57 private static $defaultEncryptionAlgorithm = 'id-PBES2';
58
59 /**
60 * Default encryption scheme
61 *
62 * Only used when defaultEncryptionAlgorithm is id-PBES2
63 *
64 * @var string
65 */
66 private static $defaultEncryptionScheme = 'aes128-CBC-PAD';
67
68 /**
69 * Default PRF
70 *
71 * Only used when defaultEncryptionAlgorithm is id-PBES2
72 *
73 * @var string
74 */
75 private static $defaultPRF = 'id-hmacWithSHA256';
76
77 /**
78 * Default Iteration Count
79 *
80 * @var int
81 */
82 private static $defaultIterationCount = 2048;
83
84 /**
85 * OIDs loaded
86 *
87 * @var bool
88 */
89 private static $oidsLoaded = false;
90
91 /**
92 * Sets the default encryption algorithm
93 */
94 public static function setEncryptionAlgorithm(string $algo): void
95 {
96 self::$defaultEncryptionAlgorithm = $algo;
97 }
98
99 /**
100 * Sets the default encryption algorithm for PBES2
101 */
102 public static function setEncryptionScheme(string $algo): void
103 {
104 self::$defaultEncryptionScheme = $algo;
105 }
106
107 /**
108 * Sets the iteration count
109 */
110 public static function setIterationCount(int $count): void
111 {
112 self::$defaultIterationCount = $count;
113 }
114
115 /**
116 * Sets the PRF for PBES2
117 */
118 public static function setPRF(string $algo): void
119 {
120 self::$defaultPRF = $algo;
121 }
122
123 /**
124 * Returns a SymmetricKey object based on a PBES1 $algo
125 *
126 * @return SymmetricKey
127 */
128 private static function getPBES1EncryptionObject(string $algo)
129 {
130 $algo = preg_match('#^pbeWith(?:MD2|MD5|SHA1|SHA)And(.*?)-CBC$#', $algo, $matches) ?
131 $matches[1] :
132 substr($algo, 13); // strlen('pbeWithSHAAnd') == 13
133
134 switch ($algo) {
135 case 'DES':
136 $cipher = new DES('cbc');
137 break;
138 case 'RC2':
139 $cipher = new RC2('cbc');
140 $cipher->setKeyLength(64);
141 break;
142 case '3-KeyTripleDES':
143 $cipher = new TripleDES('cbc');
144 break;
145 case '2-KeyTripleDES':
146 $cipher = new TripleDES('cbc');
147 $cipher->setKeyLength(128);
148 break;
149 case '128BitRC2':
150 $cipher = new RC2('cbc');
151 $cipher->setKeyLength(128);
152 break;
153 case '40BitRC2':
154 $cipher = new RC2('cbc');
155 $cipher->setKeyLength(40);
156 break;
157 case '128BitRC4':
158 $cipher = new RC4();
159 $cipher->setKeyLength(128);
160 break;
161 case '40BitRC4':
162 $cipher = new RC4();
163 $cipher->setKeyLength(40);
164 break;
165 default:
166 throw new UnsupportedAlgorithmException("$algo is not a supported algorithm");
167 }
168
169 return $cipher;
170 }
171
172 /**
173 * Returns a hash based on a PBES1 $algo
174 */
175 private static function getPBES1Hash(string $algo): string
176 {
177 if (preg_match('#^pbeWith(MD2|MD5|SHA1|SHA)And.*?-CBC$#', $algo, $matches)) {
178 return $matches[1] == 'SHA' ? 'sha1' : $matches[1];
179 }
180
181 return 'sha1';
182 }
183
184 /**
185 * Returns a KDF baesd on a PBES1 $algo
186 */
187 private static function getPBES1KDF(string $algo): string
188 {
189 switch ($algo) {
190 case 'pbeWithMD2AndDES-CBC':
191 case 'pbeWithMD2AndRC2-CBC':
192 case 'pbeWithMD5AndDES-CBC':
193 case 'pbeWithMD5AndRC2-CBC':
194 case 'pbeWithSHA1AndDES-CBC':
195 case 'pbeWithSHA1AndRC2-CBC':
196 return 'pbkdf1';
197 }
198
199 return 'pkcs12';
200 }
201
202 /**
203 * Returns a SymmetricKey object baesd on a PBES2 $algo
204 */
205 private static function getPBES2EncryptionObject(string $algo): SymmetricKey
206 {
207 switch ($algo) {
208 case 'desCBC':
209 $cipher = new DES('cbc');
210 break;
211 case 'des-EDE3-CBC':
212 $cipher = new TripleDES('cbc');
213 break;
214 case 'rc2CBC':
215 $cipher = new RC2('cbc');
216 // in theory this can be changed
217 $cipher->setKeyLength(128);
218 break;
219 case 'rc5-CBC-PAD':
220 throw new UnsupportedAlgorithmException('rc5-CBC-PAD is not supported for PBES2 PKCS#8 keys');
221 case 'aes128-CBC-PAD':
222 case 'aes192-CBC-PAD':
223 case 'aes256-CBC-PAD':
224 $cipher = new AES('cbc');
225 $cipher->setKeyLength((int) substr($algo, 3, 3));
226 break;
227 default:
228 throw new UnsupportedAlgorithmException("$algo is not supported");
229 }
230
231 return $cipher;
232 }
233
234 /**
235 * Initialize static variables
236 */
237 private static function initialize_static_variables(): void
238 {
239 if (!isset(static::$childOIDsLoaded)) {
240 throw new InsufficientSetupException('This class should not be called directly');
241 }
242
243 if (!static::$childOIDsLoaded) {
244 ASN1::loadOIDs(is_array(static::OID_NAME) ?
245 array_combine(static::OID_NAME, static::OID_VALUE) :
246 [static::OID_NAME => static::OID_VALUE]);
247 static::$childOIDsLoaded = true;
248 }
249 if (!self::$oidsLoaded) {
250 // from https://tools.ietf.org/html/rfc2898
251 ASN1::loadOIDs([
252 // PBES1 encryption schemes
253 'pbeWithMD2AndDES-CBC' => '1.2.840.113549.1.5.1',
254 'pbeWithMD2AndRC2-CBC' => '1.2.840.113549.1.5.4',
255 'pbeWithMD5AndDES-CBC' => '1.2.840.113549.1.5.3',
256 'pbeWithMD5AndRC2-CBC' => '1.2.840.113549.1.5.6',
257 'pbeWithSHA1AndDES-CBC' => '1.2.840.113549.1.5.10',
258 'pbeWithSHA1AndRC2-CBC' => '1.2.840.113549.1.5.11',
259
260 // from PKCS#12:
261 // https://tools.ietf.org/html/rfc7292
262 'pbeWithSHAAnd128BitRC4' => '1.2.840.113549.1.12.1.1',
263 'pbeWithSHAAnd40BitRC4' => '1.2.840.113549.1.12.1.2',
264 'pbeWithSHAAnd3-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.3',
265 'pbeWithSHAAnd2-KeyTripleDES-CBC' => '1.2.840.113549.1.12.1.4',
266 'pbeWithSHAAnd128BitRC2-CBC' => '1.2.840.113549.1.12.1.5',
267 'pbeWithSHAAnd40BitRC2-CBC' => '1.2.840.113549.1.12.1.6',
268
269 'id-PBKDF2' => '1.2.840.113549.1.5.12',
270 'id-PBES2' => '1.2.840.113549.1.5.13',
271 'id-PBMAC1' => '1.2.840.113549.1.5.14',
272
273 // from PKCS#5 v2.1:
274 // http://www.rsa.com/rsalabs/pkcs/files/h11302-wp-pkcs5v2-1-password-based-cryptography-standard.pdf
275 'id-hmacWithSHA1' => '1.2.840.113549.2.7',
276 'id-hmacWithSHA224' => '1.2.840.113549.2.8',
277 'id-hmacWithSHA256' => '1.2.840.113549.2.9',
278 'id-hmacWithSHA384' => '1.2.840.113549.2.10',
279 'id-hmacWithSHA512' => '1.2.840.113549.2.11',
280 'id-hmacWithSHA512-224' => '1.2.840.113549.2.12',
281 'id-hmacWithSHA512-256' => '1.2.840.113549.2.13',
282
283 'desCBC' => '1.3.14.3.2.7',
284 'des-EDE3-CBC' => '1.2.840.113549.3.7',
285 'rc2CBC' => '1.2.840.113549.3.2',
286 'rc5-CBC-PAD' => '1.2.840.113549.3.9',
287
288 'aes128-CBC-PAD' => '2.16.840.1.101.3.4.1.2',
289 'aes192-CBC-PAD' => '2.16.840.1.101.3.4.1.22',
290 'aes256-CBC-PAD' => '2.16.840.1.101.3.4.1.42',
291 ]);
292 self::$oidsLoaded = true;
293 }
294 }
295
296 /**
297 * Break a public or private key down into its constituent components
298 *
299 * @param string|array $key
300 */
301 protected static function load($key, ?string $password = null): array
302 {
303 if (!Strings::is_stringable($key)) {
304 throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key));
305 }
306
307 $isPublic = str_contains($key, 'PUBLIC');
308 $isPrivate = str_contains($key, 'PRIVATE');
309
310 $decoded = self::preParse($key);
311
312 $meta = [];
313
314 $decrypted = ASN1::asn1map($decoded[0], Maps\EncryptedPrivateKeyInfo::MAP);
315 if ($password !== null && strlen($password) && is_array($decrypted)) {
316 $algorithm = $decrypted['encryptionAlgorithm']['algorithm'];
317 switch ($algorithm) {
318 // PBES1
319 case 'pbeWithMD2AndDES-CBC':
320 case 'pbeWithMD2AndRC2-CBC':
321 case 'pbeWithMD5AndDES-CBC':
322 case 'pbeWithMD5AndRC2-CBC':
323 case 'pbeWithSHA1AndDES-CBC':
324 case 'pbeWithSHA1AndRC2-CBC':
325 case 'pbeWithSHAAnd3-KeyTripleDES-CBC':
326 case 'pbeWithSHAAnd2-KeyTripleDES-CBC':
327 case 'pbeWithSHAAnd128BitRC2-CBC':
328 case 'pbeWithSHAAnd40BitRC2-CBC':
329 case 'pbeWithSHAAnd128BitRC4':
330 case 'pbeWithSHAAnd40BitRC4':
331 $cipher = self::getPBES1EncryptionObject($algorithm);
332 $hash = self::getPBES1Hash($algorithm);
333 $kdf = self::getPBES1KDF($algorithm);
334
335 $meta['meta']['algorithm'] = $algorithm;
336
337 $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
338 if (!$temp) {
339 throw new RuntimeException('Unable to decode BER');
340 }
341 extract(ASN1::asn1map($temp[0], Maps\PBEParameter::MAP));
342 $iterationCount = (int) $iterationCount->toString();
343 $cipher->setPassword($password, $kdf, $hash, $salt, $iterationCount);
344 $key = $cipher->decrypt($decrypted['encryptedData']);
345 $decoded = ASN1::decodeBER($key);
346 if (!$decoded) {
347 throw new RuntimeException('Unable to decode BER 2');
348 }
349
350 break;
351 case 'id-PBES2':
352 $meta['meta']['algorithm'] = $algorithm;
353
354 $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
355 if (!$temp) {
356 throw new RuntimeException('Unable to decode BER');
357 }
358 $temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP);
359 extract($temp);
360
361 $cipher = self::getPBES2EncryptionObject($encryptionScheme['algorithm']);
362 $meta['meta']['cipher'] = $encryptionScheme['algorithm'];
363
364 $temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
365 if (!$temp) {
366 throw new RuntimeException('Unable to decode BER');
367 }
368 $temp = ASN1::asn1map($temp[0], Maps\PBES2params::MAP);
369 extract($temp);
370
371 if (!$cipher instanceof RC2) {
372 $cipher->setIV($encryptionScheme['parameters']['octetString']);
373 } else {
374 $temp = ASN1::decodeBER($encryptionScheme['parameters']);
375 if (!$temp) {
376 throw new RuntimeException('Unable to decode BER');
377 }
378 extract(ASN1::asn1map($temp[0], Maps\RC2CBCParameter::MAP));
379 $effectiveKeyLength = (int) $rc2ParametersVersion->toString();
380 switch ($effectiveKeyLength) {
381 case 160:
382 $effectiveKeyLength = 40;
383 break;
384 case 120:
385 $effectiveKeyLength = 64;
386 break;
387 case 58:
388 $effectiveKeyLength = 128;
389 break;
390 //default: // should be >= 256
391 }
392 $cipher->setIV($iv);
393 $cipher->setKeyLength($effectiveKeyLength);
394 }
395
396 $meta['meta']['keyDerivationFunc'] = $keyDerivationFunc['algorithm'];
397 switch ($keyDerivationFunc['algorithm']) {
398 case 'id-PBKDF2':
399 $temp = ASN1::decodeBER($keyDerivationFunc['parameters']);
400 if (!$temp) {
401 throw new RuntimeException('Unable to decode BER');
402 }
403 $prf = ['algorithm' => 'id-hmacWithSHA1'];
404 $params = ASN1::asn1map($temp[0], Maps\PBKDF2params::MAP);
405 extract($params);
406 $meta['meta']['prf'] = $prf['algorithm'];
407 $hash = str_replace('-', '/', substr($prf['algorithm'], 11));
408 $params = [
409 $password,
410 'pbkdf2',
411 $hash,
412 $salt,
413 (int) $iterationCount->toString(),
414 ];
415 if (isset($keyLength)) {
416 $params[] = (int) $keyLength->toString();
417 }
418 $cipher->setPassword(...$params);
419 $key = $cipher->decrypt($decrypted['encryptedData']);
420 $decoded = ASN1::decodeBER($key);
421 if (!$decoded) {
422 throw new RuntimeException('Unable to decode BER 3');
423 }
424 break;
425 default:
426 throw new UnsupportedAlgorithmException('Only PBKDF2 is supported for PBES2 PKCS#8 keys');
427 }
428 break;
429 case 'id-PBMAC1':
430 //$temp = ASN1::decodeBER($decrypted['encryptionAlgorithm']['parameters']);
431 //$value = ASN1::asn1map($temp[0], Maps\PBMAC1params::MAP);
432 // since i can't find any implementation that does PBMAC1 it is unsupported
433 throw new UnsupportedAlgorithmException('Only PBES1 and PBES2 PKCS#8 keys are supported.');
434 // at this point we'll assume that the key conforms to PublicKeyInfo
435 }
436 }
437
438 $private = ASN1::asn1map($decoded[0], Maps\OneAsymmetricKey::MAP);
439 if (is_array($private)) {
440 if ($isPublic) {
441 throw new UnexpectedValueException('Human readable string claims public key but DER encoded string claims private key');
442 }
443
444 if (isset($private['privateKeyAlgorithm']['parameters']) && !$private['privateKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][1]['content'][1])) {
445 $temp = $decoded[0]['content'][1]['content'][1];
446 $private['privateKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length']));
447 }
448 if (is_array(static::OID_NAME)) {
449 if (!in_array($private['privateKeyAlgorithm']['algorithm'], static::OID_NAME)) {
450 throw new UnsupportedAlgorithmException($private['privateKeyAlgorithm']['algorithm'] . ' is not a supported key type');
451 }
452 } else {
453 if ($private['privateKeyAlgorithm']['algorithm'] != static::OID_NAME) {
454 throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $private['privateKeyAlgorithm']['algorithm'] . ' key');
455 }
456 }
457 if (isset($private['publicKey'])) {
458 if ($private['publicKey'][0] != "\0") {
459 throw new UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($private['publicKey'][0]));
460 }
461 $private['publicKey'] = substr($private['publicKey'], 1);
462 }
463 return $private + $meta;
464 }
465
466 // EncryptedPrivateKeyInfo and PublicKeyInfo have largely identical "signatures". the only difference
467 // is that the former has an octet string and the later has a bit string. the first byte of a bit
468 // string represents the number of bits in the last byte that are to be ignored but, currently,
469 // bit strings wanting a non-zero amount of bits trimmed are not supported
470 $public = ASN1::asn1map($decoded[0], Maps\PublicKeyInfo::MAP);
471
472 if (is_array($public)) {
473 if ($isPrivate) {
474 throw new UnexpectedValueException('Human readable string claims private key but DER encoded string claims public key');
475 }
476
477 if ($public['publicKey'][0] != "\0") {
478 throw new UnexpectedValueException('The first byte of the public key should be null - not ' . bin2hex($public['publicKey'][0]));
479 }
480 if (is_array(static::OID_NAME)) {
481 if (!in_array($public['publicKeyAlgorithm']['algorithm'], static::OID_NAME)) {
482 throw new UnsupportedAlgorithmException($public['publicKeyAlgorithm']['algorithm'] . ' is not a supported key type');
483 }
484 } else {
485 if ($public['publicKeyAlgorithm']['algorithm'] != static::OID_NAME) {
486 throw new UnsupportedAlgorithmException('Only ' . static::OID_NAME . ' keys are supported; this is a ' . $public['publicKeyAlgorithm']['algorithm'] . ' key');
487 }
488 }
489 if (isset($public['publicKeyAlgorithm']['parameters']) && !$public['publicKeyAlgorithm']['parameters'] instanceof ASN1\Element && isset($decoded[0]['content'][0]['content'][1])) {
490 $temp = $decoded[0]['content'][0]['content'][1];
491 $public['publicKeyAlgorithm']['parameters'] = new ASN1\Element(substr($key, $temp['start'], $temp['length']));
492 }
493 $public['publicKey'] = substr($public['publicKey'], 1);
494 return $public;
495 }
496
497 throw new RuntimeException('Unable to parse using either OneAsymmetricKey or PublicKeyInfo ASN1 maps');
498 }
499
500 /**
501 * Wrap a private key appropriately
502 *
503 * @param array|string $attr
504 * @param string|false $password
505 * @param string|null $oid optional
506 * @param string $publicKey optional
507 * @param array $options optional
508 */
509 protected static function wrapPrivateKey(string $key, $attr, $params, $password, string $oid = null, string $publicKey = '', array $options = []): string
510 {
511 self::initialize_static_variables();
512
513 $key = [
514 'version' => 'v1',
515 'privateKeyAlgorithm' => [
516 'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid,
517 ],
518 'privateKey' => $key,
519 ];
520 if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') {
521 $key['privateKeyAlgorithm']['parameters'] = $params;
522 }
523 if (!empty($attr)) {
524 $key['attributes'] = $attr;
525 }
526 if (!empty($publicKey)) {
527 $key['version'] = 'v2';
528 $key['publicKey'] = $publicKey;
529 }
530 $key = ASN1::encodeDER($key, Maps\OneAsymmetricKey::MAP);
531 if (!empty($password) && is_string($password)) {
532 $salt = Random::string(8);
533
534 $iterationCount = $options['iterationCount'] ?? self::$defaultIterationCount;
535 $encryptionAlgorithm = $options['encryptionAlgorithm'] ?? self::$defaultEncryptionAlgorithm;
536 $encryptionScheme = $options['encryptionScheme'] ?? self::$defaultEncryptionScheme;
537 $prf = $options['PRF'] ?? self::$defaultPRF;
538
539 if ($encryptionAlgorithm == 'id-PBES2') {
540 $crypto = self::getPBES2EncryptionObject($encryptionScheme);
541 $hash = str_replace('-', '/', substr($prf, 11));
542 $kdf = 'pbkdf2';
543 $iv = Random::string($crypto->getBlockLength() >> 3);
544
545 $PBKDF2params = [
546 'salt' => $salt,
547 'iterationCount' => $iterationCount,
548 'prf' => ['algorithm' => $prf, 'parameters' => null],
549 ];
550 $PBKDF2params = ASN1::encodeDER($PBKDF2params, Maps\PBKDF2params::MAP);
551
552 if (!$crypto instanceof RC2) {
553 $params = ['octetString' => $iv];
554 } else {
555 $params = [
556 'rc2ParametersVersion' => 58,
557 'iv' => $iv,
558 ];
559 $params = ASN1::encodeDER($params, Maps\RC2CBCParameter::MAP);
560 $params = new ASN1\Element($params);
561 }
562
563 $params = [
564 'keyDerivationFunc' => [
565 'algorithm' => 'id-PBKDF2',
566 'parameters' => new ASN1\Element($PBKDF2params),
567 ],
568 'encryptionScheme' => [
569 'algorithm' => $encryptionScheme,
570 'parameters' => $params,
571 ],
572 ];
573 $params = ASN1::encodeDER($params, Maps\PBES2params::MAP);
574
575 $crypto->setIV($iv);
576 } else {
577 $crypto = self::getPBES1EncryptionObject($encryptionAlgorithm);
578 $hash = self::getPBES1Hash($encryptionAlgorithm);
579 $kdf = self::getPBES1KDF($encryptionAlgorithm);
580
581 $params = [
582 'salt' => $salt,
583 'iterationCount' => $iterationCount,
584 ];
585 $params = ASN1::encodeDER($params, Maps\PBEParameter::MAP);
586 }
587 $crypto->setPassword($password, $kdf, $hash, $salt, $iterationCount);
588 $key = $crypto->encrypt($key);
589
590 $key = [
591 'encryptionAlgorithm' => [
592 'algorithm' => $encryptionAlgorithm,
593 'parameters' => new ASN1\Element($params),
594 ],
595 'encryptedData' => $key,
596 ];
597
598 $key = ASN1::encodeDER($key, Maps\EncryptedPrivateKeyInfo::MAP);
599
600 return "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" .
601 chunk_split(Strings::base64_encode($key), 64) .
602 "-----END ENCRYPTED PRIVATE KEY-----";
603 }
604
605 return "-----BEGIN PRIVATE KEY-----\r\n" .
606 chunk_split(Strings::base64_encode($key), 64) .
607 "-----END PRIVATE KEY-----";
608 }
609
610 /**
611 * Wrap a public key appropriately
612 */
613 protected static function wrapPublicKey(string $key, $params, string $oid = null): string
614 {
615 self::initialize_static_variables();
616
617 $key = [
618 'publicKeyAlgorithm' => [
619 'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid,
620 ],
621 'publicKey' => "\0" . $key,
622 ];
623
624 if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') {
625 $key['publicKeyAlgorithm']['parameters'] = $params;
626 }
627
628 $key = ASN1::encodeDER($key, Maps\PublicKeyInfo::MAP);
629
630 return "-----BEGIN PUBLIC KEY-----\r\n" .
631 chunk_split(Strings::base64_encode($key), 64) .
632 "-----END PUBLIC KEY-----";
633 }
634
635 /**
636 * Perform some preliminary parsing of the key
637 *
638 * @param string|array $key
639 */
640 private static function preParse(&$key): array
641 {
642 self::initialize_static_variables();
643
644 if (self::$format != self::MODE_DER) {
645 $decoded = ASN1::extractBER($key);
646 if ($decoded !== false) {
647 $key = $decoded;
648 } elseif (self::$format == self::MODE_PEM) {
649 throw new UnexpectedValueException('Expected base64-encoded PEM format but was unable to decode base64 text');
650 }
651 }
652
653 $decoded = ASN1::decodeBER($key);
654 if (!$decoded) {
655 throw new RuntimeException('Unable to decode BER');
656 }
657
658 return $decoded;
659 }
660
661 /**
662 * Returns the encryption parameters used by the key
663 */
664 public static function extractEncryptionAlgorithm(string $key): array
665 {
666 if (!Strings::is_stringable($key)) {
667 throw new UnexpectedValueException('Key should be a string - not a ' . gettype($key));
668 }
669
670 $decoded = self::preParse($key);
671
672 $r = ASN1::asn1map($decoded[0], ASN1\Maps\EncryptedPrivateKeyInfo::MAP);
673 if (!is_array($r)) {
674 throw new RuntimeException('Unable to parse using EncryptedPrivateKeyInfo map');
675 }
676
677 if ($r['encryptionAlgorithm']['algorithm'] == 'id-PBES2') {
678 $decoded = ASN1::decodeBER($r['encryptionAlgorithm']['parameters']->element);
679 if (!$decoded) {
680 throw new RuntimeException('Unable to decode BER');
681 }
682 $r['encryptionAlgorithm']['parameters'] = ASN1::asn1map($decoded[0], ASN1\Maps\PBES2params::MAP);
683
684 $kdf = &$r['encryptionAlgorithm']['parameters']['keyDerivationFunc'];
685 switch ($kdf['algorithm']) {
686 case 'id-PBKDF2':
687 $decoded = ASN1::decodeBER($kdf['parameters']->element);
688 if (!$decoded) {
689 throw new RuntimeException('Unable to decode BER');
690 }
691 $kdf['parameters'] = ASN1::asn1map($decoded[0], Maps\PBKDF2params::MAP);
692 }
693 }
694
695 return $r['encryptionAlgorithm'];
696 }
697 }
698