Subversion Repositories oidplus

Rev

Rev 1427 | 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.  * @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');
  144.                 $cipher->setKeyLength(64);
  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 DES('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.     {
  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.  
  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']);
  354.                     if (!$temp) {
  355.                         throw new \RuntimeException('Unable to decode BER');
  356.                     }
  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);
  362.                     if (!$decoded) {
  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']);
  371.                     if (!$temp) {
  372.                         throw new \RuntimeException('Unable to decode BER');
  373.                     }
  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']);
  381.                     if (!$temp) {
  382.                         throw new \RuntimeException('Unable to decode BER');
  383.                     }
  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']);
  391.                         if (!$temp) {
  392.                             throw new \RuntimeException('Unable to decode BER');
  393.                         }
  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.                             if (!$temp) {
  417.                                 throw new \RuntimeException('Unable to decode BER');
  418.                             }
  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);
  437.                             if (!$decoded) {
  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)) {
  456.             if ($isPublic) {
  457.                 throw new \UnexpectedValueException('Human readable string claims public key but DER encoded string claims private key');
  458.             }
  459.  
  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)) {
  489.             if ($isPrivate) {
  490.                 throw new \UnexpectedValueException('Human readable string claims private key but DER encoded string claims public key');
  491.             }
  492.  
  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" .
  620.                    chunk_split(Strings::base64_encode($key), 64) .
  621.                    "-----END ENCRYPTED PRIVATE KEY-----";
  622.         }
  623.  
  624.         return "-----BEGIN PRIVATE KEY-----\r\n" .
  625.                chunk_split(Strings::base64_encode($key), 64) .
  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' => [
  643.                 'algorithm' => is_string(static::OID_NAME) ? static::OID_NAME : $oid
  644.             ],
  645.             'publicKey' => "\0" . $key
  646.         ];
  647.  
  648.         if ($oid != 'id-Ed25519' && $oid != 'id-Ed448') {
  649.             $key['publicKeyAlgorithm']['parameters'] = $params;
  650.         }
  651.  
  652.         $key = ASN1::encodeDER($key, Maps\PublicKeyInfo::MAP);
  653.  
  654.         return "-----BEGIN PUBLIC KEY-----\r\n" .
  655.                chunk_split(Strings::base64_encode($key), 64) .
  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);
  679.         if (!$decoded) {
  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.     {
  694.         if (!Strings::is_stringable($key)) {
  695.             throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
  696.         }
  697.  
  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);
  707.             if (!$decoded) {
  708.                 throw new \RuntimeException('Unable to decode BER');
  709.             }
  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);
  716.                     if (!$decoded) {
  717.                         throw new \RuntimeException('Unable to decode BER');
  718.                     }
  719.                     $kdf['parameters'] = ASN1::asn1map($decoded[0], Maps\PBKDF2params::MAP);
  720.             }
  721.         }
  722.  
  723.         return $r['encryptionAlgorithm'];
  724.     }
  725. }
  726.