Subversion Repositories oidplus

Rev

Rev 846 | Rev 1042 | Go to most recent revision | Blame | Compare with Previous | Last modification | View Log | RSS feed

  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.  
  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.  *
  46.  * @package Common
  47.  * @author  Jim Wigginton <terrafrost@php.net>
  48.  * @access  public
  49.  */
  50. abstract 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.