Subversion Repositories oidplus

Rev

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