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.  * Miccrosoft BLOB Formatted RSA Key Handler
  5.  *
  6.  * More info:
  7.  *
  8.  * https://msdn.microsoft.com/en-us/library/windows/desktop/aa375601(v=vs.85).aspx
  9.  *
  10.  * PHP version 5
  11.  *
  12.  * @category  Crypt
  13.  * @package   RSA
  14.  * @author    Jim Wigginton <terrafrost@php.net>
  15.  * @copyright 2015 Jim Wigginton
  16.  * @license   http://www.opensource.org/licenses/mit-license.html  MIT License
  17.  * @link      http://phpseclib.sourceforge.net
  18.  */
  19.  
  20. namespace phpseclib3\Crypt\RSA\Formats\Keys;
  21.  
  22. use ParagonIE\ConstantTime\Base64;
  23. use phpseclib3\Common\Functions\Strings;
  24. use phpseclib3\Exception\UnsupportedFormatException;
  25. use phpseclib3\Math\BigInteger;
  26.  
  27. /**
  28.  * Microsoft BLOB Formatted RSA Key Handler
  29.  *
  30.  * @package RSA
  31.  * @author  Jim Wigginton <terrafrost@php.net>
  32.  * @access  public
  33.  */
  34. abstract class MSBLOB
  35. {
  36.     /**
  37.      * Public/Private Key Pair
  38.      *
  39.      * @access private
  40.      */
  41.     const PRIVATEKEYBLOB = 0x7;
  42.     /**
  43.      * Public Key
  44.      *
  45.      * @access private
  46.      */
  47.     const PUBLICKEYBLOB = 0x6;
  48.     /**
  49.      * Public Key
  50.      *
  51.      * @access private
  52.      */
  53.     const PUBLICKEYBLOBEX = 0xA;
  54.     /**
  55.      * RSA public key exchange algorithm
  56.      *
  57.      * @access private
  58.      */
  59.     const CALG_RSA_KEYX = 0x0000A400;
  60.     /**
  61.      * RSA public key exchange algorithm
  62.      *
  63.      * @access private
  64.      */
  65.     const CALG_RSA_SIGN = 0x00002400;
  66.     /**
  67.      * Public Key
  68.      *
  69.      * @access private
  70.      */
  71.     const RSA1 = 0x31415352;
  72.     /**
  73.      * Private Key
  74.      *
  75.      * @access private
  76.      */
  77.     const RSA2 = 0x32415352;
  78.  
  79.     /**
  80.      * Break a public or private key down into its constituent components
  81.      *
  82.      * @access public
  83.      * @param string $key
  84.      * @param string $password optional
  85.      * @return array
  86.      */
  87.     public static function load($key, $password = '')
  88.     {
  89.         if (!Strings::is_stringable($key)) {
  90.             throw new \UnexpectedValueException('Key should be a string - not a ' . gettype($key));
  91.         }
  92.  
  93.         $key = Base64::decode($key);
  94.  
  95.         if (!is_string($key)) {
  96.             throw new \UnexpectedValueException('Base64 decoding produced an error');
  97.         }
  98.         if (strlen($key) < 20) {
  99.             throw new \UnexpectedValueException('Key appears to be malformed');
  100.         }
  101.  
  102.         // PUBLICKEYSTRUC  publickeystruc
  103.         // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387453(v=vs.85).aspx
  104.         extract(unpack('atype/aversion/vreserved/Valgo', Strings::shift($key, 8)));
  105.         /**
  106.          * @var string $type
  107.          * @var string $version
  108.          * @var integer $reserved
  109.          * @var integer $algo
  110.          */
  111.         switch (ord($type)) {
  112.             case self::PUBLICKEYBLOB:
  113.             case self::PUBLICKEYBLOBEX:
  114.                 $publickey = true;
  115.                 break;
  116.             case self::PRIVATEKEYBLOB:
  117.                 $publickey = false;
  118.                 break;
  119.             default:
  120.                 throw new \UnexpectedValueException('Key appears to be malformed');
  121.         }
  122.  
  123.         $components = ['isPublicKey' => $publickey];
  124.  
  125.         // https://msdn.microsoft.com/en-us/library/windows/desktop/aa375549(v=vs.85).aspx
  126.         switch ($algo) {
  127.             case self::CALG_RSA_KEYX:
  128.             case self::CALG_RSA_SIGN:
  129.                 break;
  130.             default:
  131.                 throw new \UnexpectedValueException('Key appears to be malformed');
  132.         }
  133.  
  134.         // RSAPUBKEY rsapubkey
  135.         // https://msdn.microsoft.com/en-us/library/windows/desktop/aa387685(v=vs.85).aspx
  136.         // could do V for pubexp but that's unsigned 32-bit whereas some PHP installs only do signed 32-bit
  137.         extract(unpack('Vmagic/Vbitlen/a4pubexp', Strings::shift($key, 12)));
  138.         /**
  139.          * @var integer $magic
  140.          * @var integer $bitlen
  141.          * @var string $pubexp
  142.          */
  143.         switch ($magic) {
  144.             case self::RSA2:
  145.                 $components['isPublicKey'] = false;
  146.                 // fall-through
  147.             case self::RSA1:
  148.                 break;
  149.             default:
  150.                 throw new \UnexpectedValueException('Key appears to be malformed');
  151.         }
  152.  
  153.         $baseLength = $bitlen / 16;
  154.         if (strlen($key) != 2 * $baseLength && strlen($key) != 9 * $baseLength) {
  155.             throw new \UnexpectedValueException('Key appears to be malformed');
  156.         }
  157.  
  158.         $components[$components['isPublicKey'] ? 'publicExponent' : 'privateExponent'] = new BigInteger(strrev($pubexp), 256);
  159.         // BYTE modulus[rsapubkey.bitlen/8]
  160.         $components['modulus'] = new BigInteger(strrev(Strings::shift($key, $bitlen / 8)), 256);
  161.  
  162.         if ($publickey) {
  163.             return $components;
  164.         }
  165.  
  166.         $components['isPublicKey'] = false;
  167.  
  168.         // BYTE prime1[rsapubkey.bitlen/16]
  169.         $components['primes'] = [1 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)];
  170.         // BYTE prime2[rsapubkey.bitlen/16]
  171.         $components['primes'][] = new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256);
  172.         // BYTE exponent1[rsapubkey.bitlen/16]
  173.         $components['exponents'] = [1 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)];
  174.         // BYTE exponent2[rsapubkey.bitlen/16]
  175.         $components['exponents'][] = new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256);
  176.         // BYTE coefficient[rsapubkey.bitlen/16]
  177.         $components['coefficients'] = [2 => new BigInteger(strrev(Strings::shift($key, $bitlen / 16)), 256)];
  178.         if (isset($components['privateExponent'])) {
  179.             $components['publicExponent'] = $components['privateExponent'];
  180.         }
  181.         // BYTE privateExponent[rsapubkey.bitlen/8]
  182.         $components['privateExponent'] = new BigInteger(strrev(Strings::shift($key, $bitlen / 8)), 256);
  183.  
  184.         return $components;
  185.     }
  186.  
  187.     /**
  188.      * Convert a private key to the appropriate format.
  189.      *
  190.      * @access public
  191.      * @param \phpseclib3\Math\BigInteger $n
  192.      * @param \phpseclib3\Math\BigInteger $e
  193.      * @param \phpseclib3\Math\BigInteger $d
  194.      * @param array $primes
  195.      * @param array $exponents
  196.      * @param array $coefficients
  197.      * @param string $password optional
  198.      * @return string
  199.      */
  200.     public static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, array $primes, array $exponents, array $coefficients, $password = '')
  201.     {
  202.         if (count($primes) != 2) {
  203.             throw new \InvalidArgumentException('MSBLOB does not support multi-prime RSA keys');
  204.         }
  205.  
  206.         if (!empty($password) && is_string($password)) {
  207.             throw new UnsupportedFormatException('MSBLOB private keys do not support encryption');
  208.         }
  209.  
  210.         $n = strrev($n->toBytes());
  211.         $e = str_pad(strrev($e->toBytes()), 4, "\0");
  212.         $key = pack('aavV', chr(self::PRIVATEKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX);
  213.         $key .= pack('VVa*', self::RSA2, 8 * strlen($n), $e);
  214.         $key .= $n;
  215.         $key .= strrev($primes[1]->toBytes());
  216.         $key .= strrev($primes[2]->toBytes());
  217.         $key .= strrev($exponents[1]->toBytes());
  218.         $key .= strrev($exponents[2]->toBytes());
  219.         $key .= strrev($coefficients[2]->toBytes());
  220.         $key .= strrev($d->toBytes());
  221.  
  222.         return Base64::encode($key);
  223.     }
  224.  
  225.     /**
  226.      * Convert a public key to the appropriate format
  227.      *
  228.      * @access public
  229.      * @param \phpseclib3\Math\BigInteger $n
  230.      * @param \phpseclib3\Math\BigInteger $e
  231.      * @return string
  232.      */
  233.     public static function savePublicKey(BigInteger $n, BigInteger $e)
  234.     {
  235.         $n = strrev($n->toBytes());
  236.         $e = str_pad(strrev($e->toBytes()), 4, "\0");
  237.         $key = pack('aavV', chr(self::PUBLICKEYBLOB), chr(2), 0, self::CALG_RSA_KEYX);
  238.         $key .= pack('VVa*', self::RSA1, 8 * strlen($n), $e);
  239.         $key .= $n;
  240.  
  241.         return Base64::encode($key);
  242.     }
  243. }
  244.