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 * @category  Crypt
21 * @package   Common
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
28namespace phpseclib3\Crypt\Common\Formats\Keys;
29
30use ParagonIE\ConstantTime\Base64;
31use phpseclib3\Common\Functions\Strings;
32use phpseclib3\Crypt\AES;
33use phpseclib3\Crypt\DES;
34use phpseclib3\Crypt\Random;
35use phpseclib3\Crypt\RC2;
36use phpseclib3\Crypt\RC4;
37use phpseclib3\Crypt\TripleDES;
38use phpseclib3\Exception\InsufficientSetupException;
39use phpseclib3\Exception\UnsupportedAlgorithmException;
40use phpseclib3\File\ASN1;
41use phpseclib3\File\ASN1\Maps;
42
43/**
44 * PKCS#8 Formatted Key Handler
45 *
46 * @package Common
47 * @author  Jim Wigginton <terrafrost@php.net>
48 * @access  public
49 */
50abstract class PKCS8 extends PKCS
51{
52    /**
53     * Default encryption algorithm
54     *
55     * @var string
56     * @access private
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
66     * @access private
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
76     * @access private
77     */
78    private static $defaultPRF = 'id-hmacWithSHA256';
79
80    /**
81     * Default Iteration Count
82     *
83     * @var int
84     * @access private
85     */
86    private static $defaultIterationCount = 2048;
87
88    /**
89     * OIDs loaded
90     *
91     * @var bool
92     * @access private
93     */
94    private static $oidsLoaded = false;
95
96    /**
97     * Sets the default encryption algorithm
98     *
99     * @access public
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     *
110     * @access public
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     *
121     * @access public
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     *
132     * @access public
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
144     * @access public
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
194     * @access public
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
210     * @access public
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
232     * @access public
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     *
267     * @access private
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     *
331     * @access public
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     *
508     * @access public
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     *
622     * @access public
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}
708