Subversion Repositories oidplus

Rev

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

  1. <?php
  2.  
  3. /**
  4.  * PKCS1 Formatted Key Handler
  5.  *
  6.  * PHP version 5
  7.  *
  8.  * @category  Crypt
  9.  * @package   Common
  10.  * @author    Jim Wigginton <terrafrost@php.net>
  11.  * @copyright 2015 Jim Wigginton
  12.  * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
  13.  * @link      http://phpseclib.sourceforge.net
  14.  */
  15.  
  16. namespace phpseclib3\Crypt\Common\Formats\Keys;
  17.  
  18. use ParagonIE\ConstantTime\Base64;
  19. use ParagonIE\ConstantTime\Hex;
  20. use phpseclib3\Common\Functions\Strings;
  21. use phpseclib3\Crypt\AES;
  22. use phpseclib3\Crypt\DES;
  23. use phpseclib3\Crypt\Random;
  24. use phpseclib3\Crypt\TripleDES;
  25. use phpseclib3\Exception\UnsupportedAlgorithmException;
  26. use phpseclib3\File\ASN1;
  27.  
  28. /**
  29.  * PKCS1 Formatted Key Handler
  30.  *
  31.  * @package RSA
  32.  * @author  Jim Wigginton <terrafrost@php.net>
  33.  * @access  public
  34.  */
  35. abstract class PKCS1 extends PKCS
  36. {
  37.     /**
  38.      * Default encryption algorithm
  39.      *
  40.      * @var string
  41.      * @access private
  42.      */
  43.     private static $defaultEncryptionAlgorithm = 'AES-128-CBC';
  44.  
  45.     /**
  46.      * Sets the default encryption algorithm
  47.      *
  48.      * @access public
  49.      * @param string $algo
  50.      */
  51.     public static function setEncryptionAlgorithm($algo)
  52.     {
  53.         self::$defaultEncryptionAlgorithm = $algo;
  54.     }
  55.  
  56.     /**
  57.      * Returns the mode constant corresponding to the mode string
  58.      *
  59.      * @access public
  60.      * @param string $mode
  61.      * @return int
  62.      * @throws \UnexpectedValueException if the block cipher mode is unsupported
  63.      */
  64.     private static function getEncryptionMode($mode)
  65.     {
  66.         switch ($mode) {
  67.             case 'CBC':
  68.             case 'ECB':
  69.             case 'CFB':
  70.             case 'OFB':
  71.             case 'CTR':
  72.                 return $mode;
  73.         }
  74.         throw new \UnexpectedValueException('Unsupported block cipher mode of operation');
  75.     }
  76.  
  77.     /**
  78.      * Returns a cipher object corresponding to a string
  79.      *
  80.      * @access public
  81.      * @param string $algo
  82.      * @return string
  83.      * @throws \UnexpectedValueException if the encryption algorithm is unsupported
  84.      */
  85.     private static function getEncryptionObject($algo)
  86.     {
  87.         $modes = '(CBC|ECB|CFB|OFB|CTR)';
  88.         switch (true) {
  89.             case preg_match("#^AES-(128|192|256)-$modes$#", $algo, $matches):
  90.                 $cipher = new AES(self::getEncryptionMode($matches[2]));
  91.                 $cipher->setKeyLength($matches[1]);
  92.                 return $cipher;
  93.             case preg_match("#^DES-EDE3-$modes$#", $algo, $matches):
  94.                 return new TripleDES(self::getEncryptionMode($matches[1]));
  95.             case preg_match("#^DES-$modes$#", $algo, $matches):
  96.                 return new DES(self::getEncryptionMode($matches[1]));
  97.             default:
  98.                 throw new UnsupportedAlgorithmException($algo . ' is not a supported algorithm');
  99.         }
  100.     }
  101.  
  102.     /**
  103.      * Generate a symmetric key for PKCS#1 keys
  104.      *
  105.      * @access private
  106.      * @param string $password
  107.      * @param string $iv
  108.      * @param int $length
  109.      * @return string
  110.      */
  111.     private static function generateSymmetricKey($password, $iv, $length)
  112.     {
  113.         $symkey = '';
  114.         $iv = substr($iv, 0, 8);
  115.         while (strlen($symkey) < $length) {
  116.             $symkey .= md5($symkey . $password . $iv, true);
  117.         }
  118.         return substr($symkey, 0, $length);
  119.     }
  120.  
  121.     /**
  122.      * Break a public or private key down into its constituent components
  123.      *
  124.      * @access public
  125.      * @param string $key
  126.      * @param string $password optional
  127.      * @return array
  128.      */
  129.     protected static function load($key, $password)
  130.     {
  131.         if (!Strings::is_stringable($key)) {
  132.             throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
  133.         }
  134.  
  135.         /* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is
  136.            "outside the scope" of PKCS#1.  PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to
  137.            protect private keys, however, that's not what OpenSSL* does.  OpenSSL protects private keys by adding
  138.            two new "fields" to the key - DEK-Info and Proc-Type.  These fields are discussed here:
  139.  
  140.            http://tools.ietf.org/html/rfc1421#section-4.6.1.1
  141.            http://tools.ietf.org/html/rfc1421#section-4.6.1.3
  142.  
  143.            DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell.
  144.            DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation
  145.            function.  As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's
  146.            own implementation.  ie. the implementation *is* the standard and any bugs that may exist in that
  147.            implementation are part of the standard, as well.
  148.  
  149.            * OpenSSL is the de facto standard.  It's utilized by OpenSSH and other projects */
  150.         if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) {
  151.             $iv = Hex::decode(trim($matches[2]));
  152.             // remove the Proc-Type / DEK-Info sections as they're no longer needed
  153.             $key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key);
  154.             $ciphertext = ASN1::extractBER($key);
  155.             if ($ciphertext === false) {
  156.                 $ciphertext = $key;
  157.             }
  158.             $crypto = self::getEncryptionObject($matches[1]);
  159.             $crypto->setKey(self::generateSymmetricKey($password, $iv, $crypto->getKeyLength() >> 3));
  160.             $crypto->setIV($iv);
  161.             $key = $crypto->decrypt($ciphertext);
  162.         } else {
  163.             if (self::$format != self::MODE_DER) {
  164.                 $decoded = ASN1::extractBER($key);
  165.                 if ($decoded !== false) {
  166.                     $key = $decoded;
  167.                 } elseif (self::$format == self::MODE_PEM) {
  168.                     throw new \UnexpectedValueException('Expected base64-encoded PEM format but was unable to decode base64 text');
  169.                 }
  170.             }
  171.         }
  172.  
  173.         return $key;
  174.     }
  175.  
  176.     /**
  177.      * Wrap a private key appropriately
  178.      *
  179.      * @access public
  180.      * @param string $key
  181.      * @param string $type
  182.      * @param string $password
  183.      * @param array $options optional
  184.      * @return string
  185.      */
  186.     protected static function wrapPrivateKey($key, $type, $password, array $options = [])
  187.     {
  188.         if (empty($password) || !is_string($password)) {
  189.             return "-----BEGIN $type PRIVATE KEY-----\r\n" .
  190.                    chunk_split(Base64::encode($key), 64) .
  191.                    "-----END $type PRIVATE KEY-----";
  192.         }
  193.  
  194.         $encryptionAlgorithm = isset($options['encryptionAlgorithm']) ? $options['encryptionAlgorithm'] : self::$defaultEncryptionAlgorithm;
  195.  
  196.         $cipher = self::getEncryptionObject($encryptionAlgorithm);
  197.         $iv = Random::string($cipher->getBlockLength() >> 3);
  198.         $cipher->setKey(self::generateSymmetricKey($password, $iv, $cipher->getKeyLength() >> 3));
  199.         $cipher->setIV($iv);
  200.         $iv = strtoupper(Hex::encode($iv));
  201.         return "-----BEGIN $type PRIVATE KEY-----\r\n" .
  202.                "Proc-Type: 4,ENCRYPTED\r\n" .
  203.                "DEK-Info: " . $encryptionAlgorithm . ",$iv\r\n" .
  204.                "\r\n" .
  205.                chunk_split(Base64::encode($cipher->encrypt($key)), 64) .
  206.                "-----END $type PRIVATE KEY-----";
  207.     }
  208.  
  209.     /**
  210.      * Wrap a public key appropriately
  211.      *
  212.      * @access public
  213.      * @param string $key
  214.      * @param string $type
  215.      * @return string
  216.      */
  217.     protected static function wrapPublicKey($key, $type)
  218.     {
  219.         return "-----BEGIN $type PUBLIC KEY-----\r\n" .
  220.                chunk_split(Base64::encode($key), 64) .
  221.                "-----END $type PUBLIC KEY-----";
  222.     }
  223. }
  224.