forked from GNUsocial/gnu-social
		
	
		
			
	
	
		
			488 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
		
		
			
		
	
	
			488 lines
		
	
	
		
			17 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
|   | <?php | ||
|  | /** | ||
|  |  * PKCS Formatted RSA Key Handler | ||
|  |  * | ||
|  |  * PHP version 5 | ||
|  |  * | ||
|  |  * @category  Crypt | ||
|  |  * @package   RSA | ||
|  |  * @author    Jim Wigginton <terrafrost@php.net> | ||
|  |  * @copyright 2015 Jim Wigginton | ||
|  |  * @license   http://www.opensource.org/licenses/mit-license.html  MIT License | ||
|  |  * @link      http://phpseclib.sourceforge.net | ||
|  |  */ | ||
|  | 
 | ||
|  | namespace phpseclib\Crypt\RSA; | ||
|  | 
 | ||
|  | use ParagonIE\ConstantTime\Base64; | ||
|  | use ParagonIE\ConstantTime\Hex; | ||
|  | use phpseclib\Crypt\AES; | ||
|  | use phpseclib\Crypt\Base; | ||
|  | use phpseclib\Crypt\DES; | ||
|  | use phpseclib\Crypt\TripleDES; | ||
|  | use phpseclib\Math\BigInteger; | ||
|  | 
 | ||
|  | /** | ||
|  |  * PKCS Formatted RSA Key Handler | ||
|  |  * | ||
|  |  * @package RSA | ||
|  |  * @author  Jim Wigginton <terrafrost@php.net> | ||
|  |  * @access  public | ||
|  |  */ | ||
|  | abstract class PKCS | ||
|  | { | ||
|  |     /**#@+
 | ||
|  |      * @access private | ||
|  |      * @see \phpseclib\Crypt\RSA::createKey() | ||
|  |      */ | ||
|  |     /** | ||
|  |      * ASN1 Integer | ||
|  |      */ | ||
|  |     const ASN1_INTEGER = 2; | ||
|  |     /** | ||
|  |      * ASN1 Bit String | ||
|  |      */ | ||
|  |     const ASN1_BITSTRING = 3; | ||
|  |     /** | ||
|  |      * ASN1 Octet String | ||
|  |      */ | ||
|  |     const ASN1_OCTETSTRING = 4; | ||
|  |     /** | ||
|  |      * ASN1 Object Identifier | ||
|  |      */ | ||
|  |     const ASN1_OBJECT = 6; | ||
|  |     /** | ||
|  |      * ASN1 Sequence (with the constucted bit set) | ||
|  |      */ | ||
|  |     const ASN1_SEQUENCE = 48; | ||
|  |     /**#@-*/ | ||
|  | 
 | ||
|  |     /**#@+
 | ||
|  |      * @access private | ||
|  |      */ | ||
|  |     /** | ||
|  |      * Auto-detect the format | ||
|  |      */ | ||
|  |     const MODE_ANY = 0; | ||
|  |     /** | ||
|  |      * Require base64-encoded PEM's be supplied | ||
|  |      */ | ||
|  |     const MODE_PEM = 1; | ||
|  |     /** | ||
|  |      * Require raw DER's be supplied | ||
|  |      */ | ||
|  |     const MODE_DER = 2; | ||
|  |     /**#@-*/ | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Is the key a base-64 encoded PEM, DER or should it be auto-detected? | ||
|  |      * | ||
|  |      * @access private | ||
|  |      * @param int | ||
|  |      */ | ||
|  |     static $format = self::MODE_ANY; | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Returns the mode constant corresponding to the mode string | ||
|  |      * | ||
|  |      * @access public | ||
|  |      * @param string $mode | ||
|  |      * @return int | ||
|  |      * @throws \UnexpectedValueException if the block cipher mode is unsupported | ||
|  |      */ | ||
|  |     static function getEncryptionMode($mode) | ||
|  |     { | ||
|  |         switch ($mode) { | ||
|  |             case 'CBC': | ||
|  |                 return Base::MODE_CBC; | ||
|  |             case 'ECB': | ||
|  |                 return Base::MODE_ECB; | ||
|  |             case 'CFB': | ||
|  |                 return Base::MODE_CFB; | ||
|  |             case 'OFB': | ||
|  |                 return Base::MODE_OFB; | ||
|  |             case 'CTR': | ||
|  |                 return Base::MODE_CTR; | ||
|  |         } | ||
|  |         throw new \UnexpectedValueException('Unsupported block cipher mode of operation'); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Returns a cipher object corresponding to a string | ||
|  |      * | ||
|  |      * @access public | ||
|  |      * @param string $algo | ||
|  |      * @return string | ||
|  |      * @throws \UnexpectedValueException if the encryption algorithm is unsupported | ||
|  |      */ | ||
|  |     static function getEncryptionObject($algo) | ||
|  |     { | ||
|  |         $modes = '(CBC|ECB|CFB|OFB|CTR)'; | ||
|  |         switch (true) { | ||
|  |             case preg_match("#^AES-(128|192|256)-$modes$#", $algo, $matches): | ||
|  |                 $cipher = new AES(self::getEncryptionMode($matches[2])); | ||
|  |                 $cipher->setKeyLength($matches[1]); | ||
|  |                 return $cipher; | ||
|  |             case preg_match("#^DES-EDE3-$modes$#", $algo, $matches): | ||
|  |                 return new TripleDES(self::getEncryptionMode($matches[1])); | ||
|  |             case preg_match("#^DES-$modes$#", $algo, $matches): | ||
|  |                 return new DES(self::getEncryptionMode($matches[1])); | ||
|  |             default: | ||
|  |                 throw new \UnexpectedValueException('Unsupported encryption algorithmn'); | ||
|  |         } | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Generate a symmetric key for PKCS#1 keys
 | ||
|  |      * | ||
|  |      * @access public | ||
|  |      * @param string $password | ||
|  |      * @param string $iv | ||
|  |      * @param int $length | ||
|  |      * @return string | ||
|  |      */ | ||
|  |     static function generateSymmetricKey($password, $iv, $length) | ||
|  |     { | ||
|  |         $symkey = ''; | ||
|  |         $iv = substr($iv, 0, 8); | ||
|  |         while (strlen($symkey) < $length) { | ||
|  |             $symkey.= md5($symkey . $password . $iv, true); | ||
|  |         } | ||
|  |         return substr($symkey, 0, $length); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Break a public or private key down into its constituent components | ||
|  |      * | ||
|  |      * @access public | ||
|  |      * @param string $key | ||
|  |      * @param string $password optional | ||
|  |      * @return array | ||
|  |      */ | ||
|  |     static function load($key, $password = '') | ||
|  |     { | ||
|  |         if (!is_string($key)) { | ||
|  |             return false; | ||
|  |         } | ||
|  | 
 | ||
|  |         $components = array('isPublicKey' => strpos($key, 'PUBLIC') !== false); | ||
|  | 
 | ||
|  |         /* Although PKCS#1 proposes a format that public and private keys can use, encrypting them is
 | ||
|  |            "outside the scope" of PKCS#1.  PKCS#1 then refers you to PKCS#12 and PKCS#15 if you're wanting to
 | ||
|  |            protect private keys, however, that's not what OpenSSL* does.  OpenSSL protects private keys by adding | ||
|  |            two new "fields" to the key - DEK-Info and Proc-Type.  These fields are discussed here: | ||
|  | 
 | ||
|  |            http://tools.ietf.org/html/rfc1421#section-4.6.1.1
 | ||
|  |            http://tools.ietf.org/html/rfc1421#section-4.6.1.3
 | ||
|  | 
 | ||
|  |            DES-EDE3-CBC as an algorithm, however, is not discussed anywhere, near as I can tell. | ||
|  |            DES-CBC and DES-EDE are discussed in RFC1423, however, DES-EDE3-CBC isn't, nor is its key derivation | ||
|  |            function.  As is, the definitive authority on this encoding scheme isn't the IETF but rather OpenSSL's | ||
|  |            own implementation.  ie. the implementation *is* the standard and any bugs that may exist in that | ||
|  |            implementation are part of the standard, as well. | ||
|  | 
 | ||
|  |            * OpenSSL is the de facto standard.  It's utilized by OpenSSH and other projects */ | ||
|  |         if (preg_match('#DEK-Info: (.+),(.+)#', $key, $matches)) { | ||
|  |             $iv = Hex::decode(trim($matches[2])); | ||
|  |             // remove the Proc-Type / DEK-Info sections as they're no longer needed
 | ||
|  |             $key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key); | ||
|  |             $ciphertext = self::_extractBER($key); | ||
|  |             if ($ciphertext === false) { | ||
|  |                 $ciphertext = $key; | ||
|  |             } | ||
|  |             $crypto = self::getEncryptionObject($matches[1]); | ||
|  |             $crypto->setKey(self::generateSymmetricKey($password, $iv, $crypto->getKeyLength() >> 3)); | ||
|  |             $crypto->setIV($iv); | ||
|  |             $key = $crypto->decrypt($ciphertext); | ||
|  |             if ($key === false) { | ||
|  |                 return false; | ||
|  |             } | ||
|  |         } else { | ||
|  |             if (self::$format != self::MODE_DER) { | ||
|  |                 $decoded = self::_extractBER($key); | ||
|  |                 if ($decoded !== false) { | ||
|  |                     $key = $decoded; | ||
|  |                 } elseif (self::$format == self::MODE_PEM) { | ||
|  |                     return false; | ||
|  |                 } | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         if (ord(self::_string_shift($key)) != self::ASN1_SEQUENCE) { | ||
|  |             return false; | ||
|  |         } | ||
|  |         if (self::_decodeLength($key) != strlen($key)) { | ||
|  |             return false; | ||
|  |         } | ||
|  | 
 | ||
|  |         $tag = ord(self::_string_shift($key)); | ||
|  |         /* intended for keys for which OpenSSL's asn1parse returns the following: | ||
|  | 
 | ||
|  |             0:d=0  hl=4 l= 631 cons: SEQUENCE | ||
|  |             4:d=1  hl=2 l=   1 prim:  INTEGER           :00 | ||
|  |             7:d=1  hl=2 l=  13 cons:  SEQUENCE | ||
|  |             9:d=2  hl=2 l=   9 prim:   OBJECT            :rsaEncryption | ||
|  |            20:d=2  hl=2 l=   0 prim:   NULL | ||
|  |            22:d=1  hl=4 l= 609 prim:  OCTET STRING | ||
|  | 
 | ||
|  |            ie. PKCS8 keys */ | ||
|  | 
 | ||
|  |         if ($tag == self::ASN1_INTEGER && substr($key, 0, 3) == "\x01\x00\x30") { | ||
|  |             self::_string_shift($key, 3); | ||
|  |             $tag = self::ASN1_SEQUENCE; | ||
|  |         } | ||
|  | 
 | ||
|  |         if ($tag == self::ASN1_SEQUENCE) { | ||
|  |             $temp = self::_string_shift($key, self::_decodeLength($key)); | ||
|  |             if (ord(self::_string_shift($temp)) != self::ASN1_OBJECT) { | ||
|  |                 return false; | ||
|  |             } | ||
|  |             $length = self::_decodeLength($temp); | ||
|  |             switch (self::_string_shift($temp, $length)) { | ||
|  |                 case "\x2a\x86\x48\x86\xf7\x0d\x01\x01\x01": // rsaEncryption
 | ||
|  |                     break; | ||
|  |                 case "\x2a\x86\x48\x86\xf7\x0d\x01\x05\x03": // pbeWithMD5AndDES-CBC
 | ||
|  |                     /* | ||
|  |                        PBEParameter ::= SEQUENCE { | ||
|  |                            salt OCTET STRING (SIZE(8)), | ||
|  |                            iterationCount INTEGER } | ||
|  |                     */ | ||
|  |                     if (ord(self::_string_shift($temp)) != self::ASN1_SEQUENCE) { | ||
|  |                         return false; | ||
|  |                     } | ||
|  |                     if (self::_decodeLength($temp) != strlen($temp)) { | ||
|  |                         return false; | ||
|  |                     } | ||
|  |                     self::_string_shift($temp); // assume it's an octet string
 | ||
|  |                     $salt = self::_string_shift($temp, self::_decodeLength($temp)); | ||
|  |                     if (ord(self::_string_shift($temp)) != self::ASN1_INTEGER) { | ||
|  |                         return false; | ||
|  |                     } | ||
|  |                     self::_decodeLength($temp); | ||
|  |                     list(, $iterationCount) = unpack('N', str_pad($temp, 4, chr(0), STR_PAD_LEFT)); | ||
|  |                     self::_string_shift($key); // assume it's an octet string
 | ||
|  |                     $length = self::_decodeLength($key); | ||
|  |                     if (strlen($key) != $length) { | ||
|  |                         return false; | ||
|  |                     } | ||
|  | 
 | ||
|  |                     $crypto = new DES(DES::MODE_CBC); | ||
|  |                     $crypto->setPassword($password, 'pbkdf1', 'md5', $salt, $iterationCount); | ||
|  |                     $key = $crypto->decrypt($key); | ||
|  |                     if ($key === false) { | ||
|  |                         return false; | ||
|  |                     } | ||
|  |                     return self::load($key); | ||
|  |                 default: | ||
|  |                     return false; | ||
|  |             } | ||
|  |             /* intended for keys for which OpenSSL's asn1parse returns the following: | ||
|  | 
 | ||
|  |                 0:d=0  hl=4 l= 290 cons: SEQUENCE | ||
|  |                 4:d=1  hl=2 l=  13 cons:  SEQUENCE | ||
|  |                 6:d=2  hl=2 l=   9 prim:   OBJECT            :rsaEncryption | ||
|  |                17:d=2  hl=2 l=   0 prim:   NULL | ||
|  |                19:d=1  hl=4 l= 271 prim:  BIT STRING */ | ||
|  |             $tag = ord(self::_string_shift($key)); // skip over the BIT STRING / OCTET STRING tag
 | ||
|  |             self::_decodeLength($key); // skip over the BIT STRING / OCTET STRING length
 | ||
|  |             // "The initial octet shall encode, as an unsigned binary integer wtih bit 1 as the least significant bit, the number of
 | ||
|  |             //  unused bits in the final subsequent octet. The number shall be in the range zero to seven."
 | ||
|  |             //  -- http://www.itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf (section 8.6.2.2)
 | ||
|  |             if ($tag == self::ASN1_BITSTRING) { | ||
|  |                 self::_string_shift($key); | ||
|  |             } | ||
|  |             if (ord(self::_string_shift($key)) != self::ASN1_SEQUENCE) { | ||
|  |                 return false; | ||
|  |             } | ||
|  |             if (self::_decodeLength($key) != strlen($key)) { | ||
|  |                 return false; | ||
|  |             } | ||
|  |             $tag = ord(self::_string_shift($key)); | ||
|  |         } | ||
|  |         if ($tag != self::ASN1_INTEGER) { | ||
|  |             return false; | ||
|  |         } | ||
|  | 
 | ||
|  |         $length = self::_decodeLength($key); | ||
|  |         $temp = self::_string_shift($key, $length); | ||
|  |         if (strlen($temp) != 1 || ord($temp) > 2) { | ||
|  |             $components['modulus'] = new BigInteger($temp, 256); | ||
|  |             self::_string_shift($key); // skip over self::ASN1_INTEGER
 | ||
|  |             $length = self::_decodeLength($key); | ||
|  |             $components[$components['isPublicKey'] ? 'publicExponent' : 'privateExponent'] = new BigInteger(self::_string_shift($key, $length), 256); | ||
|  | 
 | ||
|  |             return $components; | ||
|  |         } | ||
|  |         if (ord(self::_string_shift($key)) != self::ASN1_INTEGER) { | ||
|  |             return false; | ||
|  |         } | ||
|  |         $length = self::_decodeLength($key); | ||
|  |         $components['modulus'] = new BigInteger(self::_string_shift($key, $length), 256); | ||
|  |         self::_string_shift($key); | ||
|  |         $length = self::_decodeLength($key); | ||
|  |         $components['publicExponent'] = new BigInteger(self::_string_shift($key, $length), 256); | ||
|  |         self::_string_shift($key); | ||
|  |         $length = self::_decodeLength($key); | ||
|  |         $components['privateExponent'] = new BigInteger(self::_string_shift($key, $length), 256); | ||
|  |         self::_string_shift($key); | ||
|  |         $length = self::_decodeLength($key); | ||
|  |         $components['primes'] = array(1 => new BigInteger(self::_string_shift($key, $length), 256)); | ||
|  |         self::_string_shift($key); | ||
|  |         $length = self::_decodeLength($key); | ||
|  |         $components['primes'][] = new BigInteger(self::_string_shift($key, $length), 256); | ||
|  |         self::_string_shift($key); | ||
|  |         $length = self::_decodeLength($key); | ||
|  |         $components['exponents'] = array(1 => new BigInteger(self::_string_shift($key, $length), 256)); | ||
|  |         self::_string_shift($key); | ||
|  |         $length = self::_decodeLength($key); | ||
|  |         $components['exponents'][] = new BigInteger(self::_string_shift($key, $length), 256); | ||
|  |         self::_string_shift($key); | ||
|  |         $length = self::_decodeLength($key); | ||
|  |         $components['coefficients'] = array(2 => new BigInteger(self::_string_shift($key, $length), 256)); | ||
|  | 
 | ||
|  |         if (!empty($key)) { | ||
|  |             if (ord(self::_string_shift($key)) != self::ASN1_SEQUENCE) { | ||
|  |                 return false; | ||
|  |             } | ||
|  |             self::_decodeLength($key); | ||
|  |             while (!empty($key)) { | ||
|  |                 if (ord(self::_string_shift($key)) != self::ASN1_SEQUENCE) { | ||
|  |                     return false; | ||
|  |                 } | ||
|  |                 self::_decodeLength($key); | ||
|  |                 $key = substr($key, 1); | ||
|  |                 $length = self::_decodeLength($key); | ||
|  |                 $components['primes'][] = new BigInteger(self::_string_shift($key, $length), 256); | ||
|  |                 self::_string_shift($key); | ||
|  |                 $length = self::_decodeLength($key); | ||
|  |                 $components['exponents'][] = new BigInteger(self::_string_shift($key, $length), 256); | ||
|  |                 self::_string_shift($key); | ||
|  |                 $length = self::_decodeLength($key); | ||
|  |                 $components['coefficients'][] = new BigInteger(self::_string_shift($key, $length), 256); | ||
|  |             } | ||
|  |         } | ||
|  | 
 | ||
|  |         return $components; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Require base64-encoded PEM's be supplied | ||
|  |      * | ||
|  |      * @see self::load() | ||
|  |      * @access public | ||
|  |      */ | ||
|  |     static function requirePEM() | ||
|  |     { | ||
|  |         self::$format = self::MODE_PEM; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Require raw DER's be supplied | ||
|  |      * | ||
|  |      * @see self::load() | ||
|  |      * @access public | ||
|  |      */ | ||
|  |     static function requireDER() | ||
|  |     { | ||
|  |         self::$format = self::MODE_DER; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Accept any format and auto detect the format | ||
|  |      * | ||
|  |      * This is the default setting | ||
|  |      * | ||
|  |      * @see self::load() | ||
|  |      * @access public | ||
|  |      */ | ||
|  |     static function requireAny() | ||
|  |     { | ||
|  |         self::$format = self::MODE_ANY; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * DER-decode the length | ||
|  |      * | ||
|  |      * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See | ||
|  |      * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
 | ||
|  |      * | ||
|  |      * @access private | ||
|  |      * @param string $string | ||
|  |      * @return int | ||
|  |      */ | ||
|  |     static function _decodeLength(&$string) | ||
|  |     { | ||
|  |         $length = ord(self::_string_shift($string)); | ||
|  |         if ($length & 0x80) { // definite length, long form
 | ||
|  |             $length&= 0x7F; | ||
|  |             $temp = self::_string_shift($string, $length); | ||
|  |             list(, $length) = unpack('N', substr(str_pad($temp, 4, chr(0), STR_PAD_LEFT), -4)); | ||
|  |         } | ||
|  |         return $length; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * DER-encode the length | ||
|  |      * | ||
|  |      * DER supports lengths up to (2**8)**127, however, we'll only support lengths up to (2**8)**4.  See | ||
|  |      * {@link http://itu.int/ITU-T/studygroups/com17/languages/X.690-0207.pdf#p=13 X.690 paragraph 8.1.3} for more information.
 | ||
|  |      * | ||
|  |      * @access private | ||
|  |      * @param int $length | ||
|  |      * @return string | ||
|  |      */ | ||
|  |     static function _encodeLength($length) | ||
|  |     { | ||
|  |         if ($length <= 0x7F) { | ||
|  |             return chr($length); | ||
|  |         } | ||
|  | 
 | ||
|  |         $temp = ltrim(pack('N', $length), chr(0)); | ||
|  |         return pack('Ca*', 0x80 | strlen($temp), $temp); | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * String Shift | ||
|  |      * | ||
|  |      * Inspired by array_shift | ||
|  |      * | ||
|  |      * @param string $string | ||
|  |      * @param int $index | ||
|  |      * @return string | ||
|  |      * @access private | ||
|  |      */ | ||
|  |     static function _string_shift(&$string, $index = 1) | ||
|  |     { | ||
|  |         $substr = substr($string, 0, $index); | ||
|  |         $string = substr($string, $index); | ||
|  |         return $substr; | ||
|  |     } | ||
|  | 
 | ||
|  |     /** | ||
|  |      * Extract raw BER from Base64 encoding | ||
|  |      * | ||
|  |      * @access private | ||
|  |      * @param string $str | ||
|  |      * @return string | ||
|  |      */ | ||
|  |     static function _extractBER($str) | ||
|  |     { | ||
|  |         /* X.509 certs are assumed to be base64 encoded but sometimes they'll have additional things in them | ||
|  |          * above and beyond the ceritificate. | ||
|  |          * ie. some may have the following preceding the -----BEGIN CERTIFICATE----- line: | ||
|  |          * | ||
|  |          * Bag Attributes | ||
|  |          *     localKeyID: 01 00 00 00 | ||
|  |          * subject=/O=organization/OU=org unit/CN=common name | ||
|  |          * issuer=/O=organization/CN=common name | ||
|  |          */ | ||
|  |         $temp = preg_replace('#.*?^-+[^-]+-+[\r\n ]*$#ms', '', $str, 1); | ||
|  |         // remove the -----BEGIN CERTIFICATE----- and -----END CERTIFICATE----- stuff
 | ||
|  |         $temp = preg_replace('#-+[^-]+-+#', '', $temp); | ||
|  |         // remove new lines
 | ||
|  |         $temp = str_replace(array("\r", "\n", ' '), '', $temp); | ||
|  |         $temp = preg_match('#^[a-zA-Z\d/+]*={0,2}$#', $temp) ? Base64::decode($temp) : false; | ||
|  |         return $temp != false ? $temp : $str; | ||
|  |     } | ||
|  | } |