Subversion Repositories oidplus

Rev

Rev 1114 | Go to most recent revision | Details | Compare with Previous | Last modification | View Log | RSS feed

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