Subversion Repositories oidplus

Rev

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