Exemplo n.º 1
0
 /**
  * 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(Strings::shift($key)) != self::ASN1_SEQUENCE) {
         return false;
     }
     if (ASN1::decodeLength($key) != strlen($key)) {
         return false;
     }
     $tag = ord(Strings::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) == "0") {
         Strings::shift($key, 3);
         $tag = self::ASN1_SEQUENCE;
     }
     if ($tag == self::ASN1_SEQUENCE) {
         $temp = Strings::shift($key, ASN1::decodeLength($key));
         if (ord(Strings::shift($temp)) != self::ASN1_OBJECT) {
             return false;
         }
         $length = ASN1::decodeLength($temp);
         switch (Strings::shift($temp, $length)) {
             case "*†H†÷\r":
                 // rsaEncryption
                 break;
             case "*†H†÷\r":
                 // pbeWithMD5AndDES-CBC
                 /*
                    PBEParameter ::= SEQUENCE {
                        salt OCTET STRING (SIZE(8)),
                        iterationCount INTEGER }
                 */
                 if (ord(Strings::shift($temp)) != self::ASN1_SEQUENCE) {
                     return false;
                 }
                 if (ASN1::decodeLength($temp) != strlen($temp)) {
                     return false;
                 }
                 Strings::shift($temp);
                 // assume it's an octet string
                 $salt = Strings::shift($temp, ASN1::decodeLength($temp));
                 if (ord(Strings::shift($temp)) != self::ASN1_INTEGER) {
                     return false;
                 }
                 ASN1::decodeLength($temp);
                 list(, $iterationCount) = unpack('N', str_pad($temp, 4, chr(0), STR_PAD_LEFT));
                 Strings::shift($key);
                 // assume it's an octet string
                 $length = ASN1::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(Strings::shift($key));
         // skip over the BIT STRING / OCTET STRING tag
         ASN1::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) {
             Strings::shift($key);
         }
         if (ord(Strings::shift($key)) != self::ASN1_SEQUENCE) {
             return false;
         }
         if (ASN1::decodeLength($key) != strlen($key)) {
             return false;
         }
         $tag = ord(Strings::shift($key));
     }
     if ($tag != self::ASN1_INTEGER) {
         return false;
     }
     $length = ASN1::decodeLength($key);
     $temp = Strings::shift($key, $length);
     if (strlen($temp) != 1 || ord($temp) > 2) {
         $components['modulus'] = new BigInteger($temp, 256);
         Strings::shift($key);
         // skip over self::ASN1_INTEGER
         $length = ASN1::decodeLength($key);
         $components[$components['isPublicKey'] ? 'publicExponent' : 'privateExponent'] = new BigInteger(Strings::shift($key, $length), 256);
         return $components;
     }
     if (ord(Strings::shift($key)) != self::ASN1_INTEGER) {
         return false;
     }
     $length = ASN1::decodeLength($key);
     $components['modulus'] = new BigInteger(Strings::shift($key, $length), 256);
     Strings::shift($key);
     $length = ASN1::decodeLength($key);
     $components['publicExponent'] = new BigInteger(Strings::shift($key, $length), 256);
     Strings::shift($key);
     $length = ASN1::decodeLength($key);
     $components['privateExponent'] = new BigInteger(Strings::shift($key, $length), 256);
     Strings::shift($key);
     $length = ASN1::decodeLength($key);
     $components['primes'] = array(1 => new BigInteger(Strings::shift($key, $length), 256));
     Strings::shift($key);
     $length = ASN1::decodeLength($key);
     $components['primes'][] = new BigInteger(Strings::shift($key, $length), 256);
     Strings::shift($key);
     $length = ASN1::decodeLength($key);
     $components['exponents'] = array(1 => new BigInteger(Strings::shift($key, $length), 256));
     Strings::shift($key);
     $length = ASN1::decodeLength($key);
     $components['exponents'][] = new BigInteger(Strings::shift($key, $length), 256);
     Strings::shift($key);
     $length = ASN1::decodeLength($key);
     $components['coefficients'] = array(2 => new BigInteger(Strings::shift($key, $length), 256));
     if (!empty($key)) {
         if (ord(Strings::shift($key)) != self::ASN1_SEQUENCE) {
             return false;
         }
         ASN1::decodeLength($key);
         while (!empty($key)) {
             if (ord(Strings::shift($key)) != self::ASN1_SEQUENCE) {
                 return false;
             }
             ASN1::decodeLength($key);
             $key = substr($key, 1);
             $length = ASN1::decodeLength($key);
             $components['primes'][] = new BigInteger(Strings::shift($key, $length), 256);
             Strings::shift($key);
             $length = ASN1::decodeLength($key);
             $components['exponents'][] = new BigInteger(Strings::shift($key, $length), 256);
             Strings::shift($key);
             $length = ASN1::decodeLength($key);
             $components['coefficients'][] = new BigInteger(Strings::shift($key, $length), 256);
         }
     }
     return $components;
 }
Exemplo n.º 2
0
 /**
  * Convert a private key to the appropriate format.
  *
  * @access private
  * @see    setPrivateKeyFormat()
  *
  * @param String $RSAPrivateKey
  *
  * @return String
  */
 function _convertPrivateKey($n, $e, $d, $primes, $exponents, $coefficients)
 {
     $signed = $this->privateKeyFormat != self::PRIVATE_FORMAT_XML;
     $num_primes = count($primes);
     $raw = array('version' => $num_primes == 2 ? chr(0) : chr(1), 'modulus' => $n->toBytes($signed), 'publicExponent' => $e->toBytes($signed), 'privateExponent' => $d->toBytes($signed), 'prime1' => $primes[1]->toBytes($signed), 'prime2' => $primes[2]->toBytes($signed), 'exponent1' => $exponents[1]->toBytes($signed), 'exponent2' => $exponents[2]->toBytes($signed), 'coefficient' => $coefficients[2]->toBytes($signed));
     // if the format in question does not support multi-prime rsa and multi-prime rsa was used,
     // call _convertPublicKey() instead.
     switch ($this->privateKeyFormat) {
         case self::PRIVATE_FORMAT_XML:
             if ($num_primes != 2) {
                 return false;
             }
             return "<RSAKeyValue>\r\n" . '  <Modulus>' . base64_encode($raw['modulus']) . "</Modulus>\r\n" . '  <Exponent>' . base64_encode($raw['publicExponent']) . "</Exponent>\r\n" . '  <P>' . base64_encode($raw['prime1']) . "</P>\r\n" . '  <Q>' . base64_encode($raw['prime2']) . "</Q>\r\n" . '  <DP>' . base64_encode($raw['exponent1']) . "</DP>\r\n" . '  <DQ>' . base64_encode($raw['exponent2']) . "</DQ>\r\n" . '  <InverseQ>' . base64_encode($raw['coefficient']) . "</InverseQ>\r\n" . '  <D>' . base64_encode($raw['privateExponent']) . "</D>\r\n" . '</RSAKeyValue>';
             break;
         case self::PRIVATE_FORMAT_PUTTY:
             if ($num_primes != 2) {
                 return false;
             }
             $key = "PuTTY-User-Key-File-2: ssh-rsa\r\nEncryption: ";
             $encryption = !empty($this->password) || is_string($this->password) ? 'aes256-cbc' : 'none';
             $key .= $encryption;
             $key .= "\r\nComment: " . $this->comment . "\r\n";
             $public = pack('Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($raw['publicExponent']), $raw['publicExponent'], strlen($raw['modulus']), $raw['modulus']);
             $source = pack('Na*Na*Na*Na*', strlen('ssh-rsa'), 'ssh-rsa', strlen($encryption), $encryption, strlen($this->comment), $this->comment, strlen($public), $public);
             $public = base64_encode($public);
             $key .= "Public-Lines: " . (strlen($public) + 63 >> 6) . "\r\n";
             $key .= chunk_split($public, 64);
             $private = pack('Na*Na*Na*Na*', strlen($raw['privateExponent']), $raw['privateExponent'], strlen($raw['prime1']), $raw['prime1'], strlen($raw['prime2']), $raw['prime2'], strlen($raw['coefficient']), $raw['coefficient']);
             if (empty($this->password) && !is_string($this->password)) {
                 $source .= pack('Na*', strlen($private), $private);
                 $hashkey = 'putty-private-key-file-mac-key';
             } else {
                 $private .= Random::string(16 - (strlen($private) & 15));
                 $source .= pack('Na*', strlen($private), $private);
                 $sequence = 0;
                 $symkey = '';
                 while (strlen($symkey) < 32) {
                     $temp = pack('Na*', $sequence++, $this->password);
                     $symkey .= pack('H*', sha1($temp));
                 }
                 $symkey = substr($symkey, 0, 32);
                 $crypto = new AES();
                 $crypto->setKey($symkey);
                 $crypto->disablePadding();
                 $private = $crypto->encrypt($private);
                 $hashkey = 'putty-private-key-file-mac-key' . $this->password;
             }
             $private = base64_encode($private);
             $key .= 'Private-Lines: ' . (strlen($private) + 63 >> 6) . "\r\n";
             $key .= chunk_split($private, 64);
             $hash = new Hash('sha1');
             $hash->setKey(pack('H*', sha1($hashkey)));
             $key .= 'Private-MAC: ' . bin2hex($hash->hash($source)) . "\r\n";
             return $key;
         default:
             // eg. self::PRIVATE_FORMAT_PKCS1
             $components = array();
             foreach ($raw as $name => $value) {
                 $components[$name] = pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($value)), $value);
             }
             $RSAPrivateKey = implode('', $components);
             if ($num_primes > 2) {
                 $OtherPrimeInfos = '';
                 for ($i = 3; $i <= $num_primes; $i++) {
                     // OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo
                     //
                     // OtherPrimeInfo ::= SEQUENCE {
                     //     prime             INTEGER,  -- ri
                     //     exponent          INTEGER,  -- di
                     //     coefficient       INTEGER   -- ti
                     // }
                     $OtherPrimeInfo = pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true));
                     $OtherPrimeInfo .= pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true));
                     $OtherPrimeInfo .= pack('Ca*a*', self::ASN1_INTEGER, $this->_encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true));
                     $OtherPrimeInfos .= pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo);
                 }
                 $RSAPrivateKey .= pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos);
             }
             $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
             if ($this->privateKeyFormat == self::PRIVATE_FORMAT_PKCS8) {
                 $rsaOID = pack('H*', '300d06092a864886f70d0101010500');
                 // hex version of MA0GCSqGSIb3DQEBAQUA
                 $RSAPrivateKey = pack('Ca*a*Ca*a*', self::ASN1_INTEGER, "", $rsaOID, 4, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
                 $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
                 if (!empty($this->password) || is_string($this->password)) {
                     $salt = Random::string(8);
                     $iterationCount = 2048;
                     $crypto = new DES();
                     $crypto->setPassword($this->password, 'pbkdf1', 'md5', $salt, $iterationCount);
                     $RSAPrivateKey = $crypto->encrypt($RSAPrivateKey);
                     $parameters = pack('Ca*a*Ca*N', self::ASN1_OCTETSTRING, $this->_encodeLength(strlen($salt)), $salt, self::ASN1_INTEGER, $this->_encodeLength(4), $iterationCount);
                     $pbeWithMD5AndDES_CBC = "*†H†÷\r";
                     $encryptionAlgorithm = pack('Ca*a*Ca*a*', self::ASN1_OBJECT, $this->_encodeLength(strlen($pbeWithMD5AndDES_CBC)), $pbeWithMD5AndDES_CBC, self::ASN1_SEQUENCE, $this->_encodeLength(strlen($parameters)), $parameters);
                     $RSAPrivateKey = pack('Ca*a*Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($encryptionAlgorithm)), $encryptionAlgorithm, self::ASN1_OCTETSTRING, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
                     $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, $this->_encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
                     $RSAPrivateKey = "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" . chunk_split(base64_encode($RSAPrivateKey), 64) . '-----END ENCRYPTED PRIVATE KEY-----';
                 } else {
                     $RSAPrivateKey = "-----BEGIN PRIVATE KEY-----\r\n" . chunk_split(base64_encode($RSAPrivateKey), 64) . '-----END PRIVATE KEY-----';
                 }
                 return $RSAPrivateKey;
             }
             if (!empty($this->password) || is_string($this->password)) {
                 $iv = Random::string(8);
                 $symkey = pack('H*', md5($this->password . $iv));
                 // symkey is short for symmetric key
                 $symkey .= substr(pack('H*', md5($symkey . $this->password . $iv)), 0, 8);
                 $des = new TripleDES();
                 $des->setKey($symkey);
                 $des->setIV($iv);
                 $iv = strtoupper(bin2hex($iv));
                 $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . "Proc-Type: 4,ENCRYPTED\r\n" . "DEK-Info: DES-EDE3-CBC,{$iv}\r\n" . "\r\n" . chunk_split(base64_encode($des->encrypt($RSAPrivateKey)), 64) . '-----END RSA PRIVATE KEY-----';
             } else {
                 $RSAPrivateKey = "-----BEGIN RSA PRIVATE KEY-----\r\n" . chunk_split(base64_encode($RSAPrivateKey), 64) . '-----END RSA PRIVATE KEY-----';
             }
             return $RSAPrivateKey;
     }
 }
Exemplo n.º 3
0
    /**
     * Break a public or private key down into its constituant components
     *
     * @access private
     * @see self::_convertPublicKey()
     * @see self::_convertPrivateKey()
     * @param string $key
     * @param int $type
     * @return array
     */
    function _parseKey($key, $type)
    {
        if ($type != self::PUBLIC_FORMAT_RAW && !is_string($key)) {
            return false;
        }

        switch ($type) {
            case self::PUBLIC_FORMAT_RAW:
                if (!is_array($key)) {
                    return false;
                }
                $components = array();
                switch (true) {
                    case isset($key['e']):
                        $components['publicExponent'] = $key['e']->copy();
                        break;
                    case isset($key['exponent']):
                        $components['publicExponent'] = $key['exponent']->copy();
                        break;
                    case isset($key['publicExponent']):
                        $components['publicExponent'] = $key['publicExponent']->copy();
                        break;
                    case isset($key[0]):
                        $components['publicExponent'] = $key[0]->copy();
                }
                switch (true) {
                    case isset($key['n']):
                        $components['modulus'] = $key['n']->copy();
                        break;
                    case isset($key['modulo']):
                        $components['modulus'] = $key['modulo']->copy();
                        break;
                    case isset($key['modulus']):
                        $components['modulus'] = $key['modulus']->copy();
                        break;
                    case isset($key[1]):
                        $components['modulus'] = $key[1]->copy();
                }
                return isset($components['modulus']) && isset($components['publicExponent']) ? $components : false;
            case self::PRIVATE_FORMAT_PKCS1:
            case self::PRIVATE_FORMAT_PKCS8:
            case self::PUBLIC_FORMAT_PKCS1:
                /* 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 = pack('H*', trim($matches[2]));
                    $symkey = pack('H*', md5($this->password . substr($iv, 0, 8))); // symkey is short for symmetric key
                    $symkey.= pack('H*', md5($symkey . $this->password . substr($iv, 0, 8)));
                    // remove the Proc-Type / DEK-Info sections as they're no longer needed
                    $key = preg_replace('#^(?:Proc-Type|DEK-Info): .*#m', '', $key);
                    $ciphertext = $this->_extractBER($key);
                    if ($ciphertext === false) {
                        $ciphertext = $key;
                    }
                    switch ($matches[1]) {
                        case 'AES-256-CBC':
                            $crypto = new AES();
                            break;
                        case 'AES-128-CBC':
                            $symkey = substr($symkey, 0, 16);
                            $crypto = new AES();
                            break;
                        case 'DES-EDE3-CFB':
                            $crypto = new TripleDES(Base::MODE_CFB);
                            break;
                        case 'DES-EDE3-CBC':
                            $symkey = substr($symkey, 0, 24);
                            $crypto = new TripleDES();
                            break;
                        case 'DES-CBC':
                            $crypto = new DES();
                            break;
                        default:
                            return false;
                    }
                    $crypto->setKey($symkey);
                    $crypto->setIV($iv);
                    $decoded = $crypto->decrypt($ciphertext);
                } else {
                    $decoded = $this->_extractBER($key);
                }

                if ($decoded !== false) {
                    $key = $decoded;
                }

                $components = array();

                if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
                    return false;
                }
                if ($this->_decodeLength($key) != strlen($key)) {
                    return false;
                }

                $tag = ord($this->_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") {
                    $this->_string_shift($key, 3);
                    $tag = self::ASN1_SEQUENCE;
                }

                if ($tag == self::ASN1_SEQUENCE) {
                    $temp = $this->_string_shift($key, $this->_decodeLength($key));
                    if (ord($this->_string_shift($temp)) != self::ASN1_OBJECT) {
                        return false;
                    }
                    $length = $this->_decodeLength($temp);
                    switch ($this->_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($this->_string_shift($temp)) != self::ASN1_SEQUENCE) {
                                return false;
                            }
                            if ($this->_decodeLength($temp) != strlen($temp)) {
                                return false;
                            }
                            $this->_string_shift($temp); // assume it's an octet string
                            $salt = $this->_string_shift($temp, $this->_decodeLength($temp));
                            if (ord($this->_string_shift($temp)) != self::ASN1_INTEGER) {
                                return false;
                            }
                            $this->_decodeLength($temp);
                            list(, $iterationCount) = unpack('N', str_pad($temp, 4, chr(0), STR_PAD_LEFT));
                            $this->_string_shift($key); // assume it's an octet string
                            $length = $this->_decodeLength($key);
                            if (strlen($key) != $length) {
                                return false;
                            }

                            $crypto = new DES();
                            $crypto->setPassword($this->password, 'pbkdf1', 'md5', $salt, $iterationCount);
                            $key = $crypto->decrypt($key);
                            if ($key === false) {
                                return false;
                            }
                            return $this->_parseKey($key, self::PRIVATE_FORMAT_PKCS1);
                        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($this->_string_shift($key)); // skip over the BIT STRING / OCTET STRING tag
                    $this->_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) {
                        $this->_string_shift($key);
                    }
                    if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
                        return false;
                    }
                    if ($this->_decodeLength($key) != strlen($key)) {
                        return false;
                    }
                    $tag = ord($this->_string_shift($key));
                }
                if ($tag != self::ASN1_INTEGER) {
                    return false;
                }

                $length = $this->_decodeLength($key);
                $temp = $this->_string_shift($key, $length);
                if (strlen($temp) != 1 || ord($temp) > 2) {
                    $components['modulus'] = new BigInteger($temp, 256);
                    $this->_string_shift($key); // skip over self::ASN1_INTEGER
                    $length = $this->_decodeLength($key);
                    $components[$type == self::PUBLIC_FORMAT_PKCS1 ? 'publicExponent' : 'privateExponent'] = new BigInteger($this->_string_shift($key, $length), 256);

                    return $components;
                }
                if (ord($this->_string_shift($key)) != self::ASN1_INTEGER) {
                    return false;
                }
                $length = $this->_decodeLength($key);
                $components['modulus'] = new BigInteger($this->_string_shift($key, $length), 256);
                $this->_string_shift($key);
                $length = $this->_decodeLength($key);
                $components['publicExponent'] = new BigInteger($this->_string_shift($key, $length), 256);
                $this->_string_shift($key);
                $length = $this->_decodeLength($key);
                $components['privateExponent'] = new BigInteger($this->_string_shift($key, $length), 256);
                $this->_string_shift($key);
                $length = $this->_decodeLength($key);
                $components['primes'] = array(1 => new BigInteger($this->_string_shift($key, $length), 256));
                $this->_string_shift($key);
                $length = $this->_decodeLength($key);
                $components['primes'][] = new BigInteger($this->_string_shift($key, $length), 256);
                $this->_string_shift($key);
                $length = $this->_decodeLength($key);
                $components['exponents'] = array(1 => new BigInteger($this->_string_shift($key, $length), 256));
                $this->_string_shift($key);
                $length = $this->_decodeLength($key);
                $components['exponents'][] = new BigInteger($this->_string_shift($key, $length), 256);
                $this->_string_shift($key);
                $length = $this->_decodeLength($key);
                $components['coefficients'] = array(2 => new BigInteger($this->_string_shift($key, $length), 256));

                if (!empty($key)) {
                    if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
                        return false;
                    }
                    $this->_decodeLength($key);
                    while (!empty($key)) {
                        if (ord($this->_string_shift($key)) != self::ASN1_SEQUENCE) {
                            return false;
                        }
                        $this->_decodeLength($key);
                        $key = substr($key, 1);
                        $length = $this->_decodeLength($key);
                        $components['primes'][] = new BigInteger($this->_string_shift($key, $length), 256);
                        $this->_string_shift($key);
                        $length = $this->_decodeLength($key);
                        $components['exponents'][] = new BigInteger($this->_string_shift($key, $length), 256);
                        $this->_string_shift($key);
                        $length = $this->_decodeLength($key);
                        $components['coefficients'][] = new BigInteger($this->_string_shift($key, $length), 256);
                    }
                }

                return $components;
            case self::PUBLIC_FORMAT_OPENSSH:
                $parts = explode(' ', $key, 3);

                $key = isset($parts[1]) ? base64_decode($parts[1]) : false;
                if ($key === false) {
                    return false;
                }

                $comment = isset($parts[2]) ? $parts[2] : false;

                $cleanup = substr($key, 0, 11) == "\0\0\0\7ssh-rsa";

                if (strlen($key) <= 4) {
                    return false;
                }
                extract(unpack('Nlength', $this->_string_shift($key, 4)));
                $publicExponent = new BigInteger($this->_string_shift($key, $length), -256);
                if (strlen($key) <= 4) {
                    return false;
                }
                extract(unpack('Nlength', $this->_string_shift($key, 4)));
                $modulus = new BigInteger($this->_string_shift($key, $length), -256);

                if ($cleanup && strlen($key)) {
                    if (strlen($key) <= 4) {
                        return false;
                    }
                    extract(unpack('Nlength', $this->_string_shift($key, 4)));
                    $realModulus = new BigInteger($this->_string_shift($key, $length), -256);
                    return strlen($key) ? false : array(
                        'modulus' => $realModulus,
                        'publicExponent' => $modulus,
                        'comment' => $comment
                    );
                } else {
                    return strlen($key) ? false : array(
                        'modulus' => $modulus,
                        'publicExponent' => $publicExponent,
                        'comment' => $comment
                    );
                }
            // http://www.w3.org/TR/xmldsig-core/#sec-RSAKeyValue
            // http://en.wikipedia.org/wiki/XML_Signature
            case self::PRIVATE_FORMAT_XML:
            case self::PUBLIC_FORMAT_XML:
                $this->components = array();

                $xml = xml_parser_create('UTF-8');
                xml_set_object($xml, $this);
                xml_set_element_handler($xml, '_start_element_handler', '_stop_element_handler');
                xml_set_character_data_handler($xml, '_data_handler');
                // add <xml></xml> to account for "dangling" tags like <BitStrength>...</BitStrength> that are sometimes added
                if (!xml_parse($xml, '<xml>' . $key . '</xml>')) {
                    return false;
                }

                return isset($this->components['modulus']) && isset($this->components['publicExponent']) ? $this->components : false;
            // from PuTTY's SSHPUBK.C
            case self::PRIVATE_FORMAT_PUTTY:
                $components = array();
                $key = preg_split('#\r\n|\r|\n#', $key);
                $type = trim(preg_replace('#PuTTY-User-Key-File-2: (.+)#', '$1', $key[0]));
                if ($type != 'ssh-rsa') {
                    return false;
                }
                $encryption = trim(preg_replace('#Encryption: (.+)#', '$1', $key[1]));
                $comment = trim(preg_replace('#Comment: (.+)#', '$1', $key[2]));

                $publicLength = trim(preg_replace('#Public-Lines: (\d+)#', '$1', $key[3]));
                $public = base64_decode(implode('', array_map('trim', array_slice($key, 4, $publicLength))));
                $public = substr($public, 11);
                extract(unpack('Nlength', $this->_string_shift($public, 4)));
                $components['publicExponent'] = new BigInteger($this->_string_shift($public, $length), -256);
                extract(unpack('Nlength', $this->_string_shift($public, 4)));
                $components['modulus'] = new BigInteger($this->_string_shift($public, $length), -256);

                $privateLength = trim(preg_replace('#Private-Lines: (\d+)#', '$1', $key[$publicLength + 4]));
                $private = base64_decode(implode('', array_map('trim', array_slice($key, $publicLength + 5, $privateLength))));

                switch ($encryption) {
                    case 'aes256-cbc':
                        $symkey = '';
                        $sequence = 0;
                        while (strlen($symkey) < 32) {
                            $temp = pack('Na*', $sequence++, $this->password);
                            $symkey.= pack('H*', sha1($temp));
                        }
                        $symkey = substr($symkey, 0, 32);
                        $crypto = new AES();
                }

                if ($encryption != 'none') {
                    $crypto->setKey($symkey);
                    $crypto->disablePadding();
                    $private = $crypto->decrypt($private);
                    if ($private === false) {
                        return false;
                    }
                }

                extract(unpack('Nlength', $this->_string_shift($private, 4)));
                if (strlen($private) < $length) {
                    return false;
                }
                $components['privateExponent'] = new BigInteger($this->_string_shift($private, $length), -256);
                extract(unpack('Nlength', $this->_string_shift($private, 4)));
                if (strlen($private) < $length) {
                    return false;
                }
                $components['primes'] = array(1 => new BigInteger($this->_string_shift($private, $length), -256));
                extract(unpack('Nlength', $this->_string_shift($private, 4)));
                if (strlen($private) < $length) {
                    return false;
                }
                $components['primes'][] = new BigInteger($this->_string_shift($private, $length), -256);

                $temp = $components['primes'][1]->subtract($this->one);
                $components['exponents'] = array(1 => $components['publicExponent']->modInverse($temp));
                $temp = $components['primes'][2]->subtract($this->one);
                $components['exponents'][] = $components['publicExponent']->modInverse($temp);

                extract(unpack('Nlength', $this->_string_shift($private, 4)));
                if (strlen($private) < $length) {
                    return false;
                }
                $components['coefficients'] = array(2 => new BigInteger($this->_string_shift($private, $length), -256));

                return $components;
        }
    }
Exemplo n.º 4
0
 /**
  * Convert a private key to the appropriate format.
  *
  * @access public
  * @param \phpseclib\Math\BigInteger $n
  * @param \phpseclib\Math\BigInteger $e
  * @param \phpseclib\Math\BigInteger $d
  * @param array $primes
  * @param array $exponents
  * @param array $coefficients
  * @param string $password optional
  * @return string
  */
 static function savePrivateKey(BigInteger $n, BigInteger $e, BigInteger $d, $primes, $exponents, $coefficients, $password = '')
 {
     $num_primes = count($primes);
     $raw = array('version' => $num_primes == 2 ? chr(0) : chr(1), 'modulus' => $n->toBytes(true), 'publicExponent' => $e->toBytes(true), 'privateExponent' => $d->toBytes(true), 'prime1' => $primes[1]->toBytes(true), 'prime2' => $primes[2]->toBytes(true), 'exponent1' => $exponents[1]->toBytes(true), 'exponent2' => $exponents[2]->toBytes(true), 'coefficient' => $coefficients[2]->toBytes(true));
     $components = array();
     foreach ($raw as $name => $value) {
         $components[$name] = pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($value)), $value);
     }
     $RSAPrivateKey = implode('', $components);
     if ($num_primes > 2) {
         $OtherPrimeInfos = '';
         for ($i = 3; $i <= $num_primes; $i++) {
             // OtherPrimeInfos ::= SEQUENCE SIZE(1..MAX) OF OtherPrimeInfo
             //
             // OtherPrimeInfo ::= SEQUENCE {
             //     prime             INTEGER,  -- ri
             //     exponent          INTEGER,  -- di
             //     coefficient       INTEGER   -- ti
             // }
             $OtherPrimeInfo = pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($primes[$i]->toBytes(true))), $primes[$i]->toBytes(true));
             $OtherPrimeInfo .= pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($exponents[$i]->toBytes(true))), $exponents[$i]->toBytes(true));
             $OtherPrimeInfo .= pack('Ca*a*', self::ASN1_INTEGER, ASN1::encodeLength(strlen($coefficients[$i]->toBytes(true))), $coefficients[$i]->toBytes(true));
             $OtherPrimeInfos .= pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($OtherPrimeInfo)), $OtherPrimeInfo);
         }
         $RSAPrivateKey .= pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($OtherPrimeInfos)), $OtherPrimeInfos);
     }
     $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
     $rsaOID = "0\r\t*†H†÷\r";
     // hex version of MA0GCSqGSIb3DQEBAQUA
     $RSAPrivateKey = pack('Ca*a*Ca*a*', self::ASN1_INTEGER, "", $rsaOID, 4, ASN1::encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
     $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
     if (!empty($password) || is_string($password)) {
         $salt = Random::string(8);
         $iterationCount = 2048;
         $crypto = new DES(DES::MODE_CBC);
         $crypto->setPassword($password, 'pbkdf1', 'md5', $salt, $iterationCount);
         $RSAPrivateKey = $crypto->encrypt($RSAPrivateKey);
         $parameters = pack('Ca*a*Ca*N', self::ASN1_OCTETSTRING, ASN1::encodeLength(strlen($salt)), $salt, self::ASN1_INTEGER, ASN1::encodeLength(4), $iterationCount);
         $pbeWithMD5AndDES_CBC = "*†H†÷\r";
         $encryptionAlgorithm = pack('Ca*a*Ca*a*', self::ASN1_OBJECT, ASN1::encodeLength(strlen($pbeWithMD5AndDES_CBC)), $pbeWithMD5AndDES_CBC, self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($parameters)), $parameters);
         $RSAPrivateKey = pack('Ca*a*Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($encryptionAlgorithm)), $encryptionAlgorithm, self::ASN1_OCTETSTRING, ASN1::encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
         $RSAPrivateKey = pack('Ca*a*', self::ASN1_SEQUENCE, ASN1::encodeLength(strlen($RSAPrivateKey)), $RSAPrivateKey);
         $RSAPrivateKey = "-----BEGIN ENCRYPTED PRIVATE KEY-----\r\n" . chunk_split(Base64::encode($RSAPrivateKey), 64) . '-----END ENCRYPTED PRIVATE KEY-----';
     } else {
         $RSAPrivateKey = "-----BEGIN PRIVATE KEY-----\r\n" . chunk_split(Base64::encode($RSAPrivateKey), 64) . '-----END PRIVATE KEY-----';
     }
     return $RSAPrivateKey;
 }