From 43165d976ca63d03b989c5e0c5111f85fbd46d76 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sat, 24 Oct 2015 01:00:46 -0500 Subject: [PATCH] RSA: changes to OpenSSH and PuTTY plugins --- phpseclib/Crypt/RSA/OpenSSH.php | 40 ++++++------- phpseclib/Crypt/RSA/PKCS.php | 90 ++++++++++++++++++++++++---- phpseclib/Crypt/RSA/PuTTY.php | 49 +++++++++++++++ phpseclib/Net/SSH1.php | 2 +- phpseclib/Net/SSH2.php | 2 +- tests/Unit/Crypt/RSA/LoadKeyTest.php | 30 ++++++++++ 6 files changed, 176 insertions(+), 37 deletions(-) diff --git a/phpseclib/Crypt/RSA/OpenSSH.php b/phpseclib/Crypt/RSA/OpenSSH.php index 73447d2a..765976ba 100644 --- a/phpseclib/Crypt/RSA/OpenSSH.php +++ b/phpseclib/Crypt/RSA/OpenSSH.php @@ -62,46 +62,40 @@ class OpenSSH $parts = explode(' ', $key, 3); - $key = isset($parts[1]) ? base64_decode($parts[1]) : false; + $key = isset($parts[1]) ? base64_decode($parts[1]) : base64_decode($parts[0]); if ($key === false) { return false; } $comment = isset($parts[2]) ? $parts[2] : false; - $cleanup = substr($key, 0, 11) == "\0\0\0\7ssh-rsa"; - + if (substr($key, 0, 11) != "\0\0\0\7ssh-rsa") { + return false; + } + self::_string_shift($key, 11); if (strlen($key) <= 4) { return false; } extract(unpack('Nlength', self::_string_shift($key, 4))); + if (strlen($key) <= $length) { + return false; + } $publicExponent = new BigInteger(self::_string_shift($key, $length), -256); if (strlen($key) <= 4) { return false; } extract(unpack('Nlength', self::_string_shift($key, 4))); + if (strlen($key) != $length) { + return false; + } $modulus = new BigInteger(self::_string_shift($key, $length), -256); - if ($cleanup && strlen($key)) { - if (strlen($key) <= 4) { - return false; - } - extract(unpack('Nlength', self::_string_shift($key, 4))); - $realModulus = new BigInteger(self::_string_shift($key, $length), -256); - return strlen($key) ? false : array( - 'isPublicKey' => true, - 'modulus' => $realModulus, - 'publicExponent' => $modulus, - 'comment' => $comment - ); - } else { - return strlen($key) ? false : array( - 'isPublicKey' => true, - 'modulus' => $modulus, - 'publicExponent' => $publicExponent, - 'comment' => $comment - ); - } + return array( + 'isPublicKey' => true, + 'modulus' => $modulus, + 'publicExponent' => $publicExponent, + 'comment' => $comment + ); } /** diff --git a/phpseclib/Crypt/RSA/PKCS.php b/phpseclib/Crypt/RSA/PKCS.php index f56e6a85..6f5ba33b 100644 --- a/phpseclib/Crypt/RSA/PKCS.php +++ b/phpseclib/Crypt/RSA/PKCS.php @@ -32,29 +32,54 @@ 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 * @@ -166,13 +191,19 @@ abstract class PKCS $crypto = self::getEncryptionObject($matches[1]); $crypto->setKey(self::generateSymmetricKey($password, $iv, $crypto->getKeyLength() >> 3)); $crypto->setIV($iv); - $decoded = $crypto->decrypt($ciphertext); + $key = $crypto->decrypt($ciphertext); + if ($key === false) { + return false; + } } else { - $decoded = self::_extractBER($key); - } - - if ($decoded !== false) { - $key = $decoded; + 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) { @@ -332,6 +363,41 @@ abstract class PKCS 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 * diff --git a/phpseclib/Crypt/RSA/PuTTY.php b/phpseclib/Crypt/RSA/PuTTY.php index 6838ee35..5e8596db 100644 --- a/phpseclib/Crypt/RSA/PuTTY.php +++ b/phpseclib/Crypt/RSA/PuTTY.php @@ -17,6 +17,7 @@ namespace phpseclib\Crypt\RSA; use phpseclib\Math\BigInteger; use phpseclib\Crypt\AES; use phpseclib\Crypt\Hash; +use phpseclib\Crypt\RSA\OpenSSH; /** * PuTTY Formatted RSA Key Handler @@ -85,6 +86,24 @@ class PuTTY $one = new BigInteger(1); } + if (strpos($key, 'BEGIN SSH2 PUBLIC KEY')) { + $data = preg_split('#[\r\n]+#', $key); + $data = array_splice($data, 2, -1); + $data = implode('', $data); + + $components = OpenSSH::load($data); + if ($components === false) { + return false; + } + + if (!preg_match('#Comment: "(.+)"#', $key, $matches)) { + return false; + } + $components['comment'] = str_replace(array('\\\\', '\"'), array('\\', '"'), $matches[1]); + + return $components; + } + $components = array('isPublicKey' => false); $key = preg_split('#\r\n|\r|\n#', $key); $type = trim(preg_replace('#PuTTY-User-Key-File-2: (.+)#', '$1', $key[0])); @@ -258,4 +277,34 @@ class PuTTY return $key; } + + /** + * Convert a public key to the appropriate format + * + * @access public + * @param \phpseclib\Math\BigInteger $n + * @param \phpseclib\Math\BigInteger $e + * @return string + */ + static function savePublicKey(BigInteger $n, BigInteger $e) + { + $n = $n->toBytes(true); + $e = $e->toBytes(true); + + $key = pack( + 'Na*Na*Na*', + strlen('ssh-rsa'), + 'ssh-rsa', + strlen($e), + $e, + strlen($n), + $n + ); + $key = "---- BEGIN SSH2 PUBLIC KEY ----\r\n" . + 'Comment: "' . str_replace(array('\\', '"'), array('\\\\', '\"'), self::$comment) . "\"\r\n"; + chunk_split(base64_encode($key), 64) . + '---- END SSH2 PUBLIC KEY ----'; + + return $key; + } } diff --git a/phpseclib/Net/SSH1.php b/phpseclib/Net/SSH1.php index 2fb89744..b0903386 100644 --- a/phpseclib/Net/SSH1.php +++ b/phpseclib/Net/SSH1.php @@ -1301,7 +1301,7 @@ class SSH1 { /* $rsa = new RSA(); - $rsa->load($key, RSA::PUBLIC_FORMAT_RAW); + $rsa->load($key, 'raw'); $rsa->setEncryptionMode(RSA::ENCRYPTION_PKCS1); return $rsa->encrypt($m); */ diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index 82760e25..dd4f13ae 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -4040,7 +4040,7 @@ class SSH2 $rsa = new RSA(); $rsa->setSignatureMode(RSA::SIGNATURE_PKCS1); - $rsa->load(array('e' => $e, 'n' => $n), 'Raw'); + $rsa->load(array('e' => $e, 'n' => $n), 'raw'); if (!$rsa->verify($this->exchange_hash, $signature)) { //user_error('Bad server signature'); return $this->_disconnect(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE); diff --git a/tests/Unit/Crypt/RSA/LoadKeyTest.php b/tests/Unit/Crypt/RSA/LoadKeyTest.php index 769e6cb5..d1750609 100644 --- a/tests/Unit/Crypt/RSA/LoadKeyTest.php +++ b/tests/Unit/Crypt/RSA/LoadKeyTest.php @@ -441,4 +441,34 @@ Private-MAC: 35134b7434bf828b21404099861d455e660e8740'; $this->assertSame($privKey->decrypt($ciphertext), $plaintext); } + + public function testNakedOpenSSHKey() + { + $key = 'AAAAB3NzaC1yc2EAAAABIwAAAIEA/NcGSQFZ0ZgN1EbDusV6LLwLnQjs05ljKcVVP7Z6aKIJUyhUDHE30uJa5XfwPPBsZ3L3Q7S0yycVcuuHjdauugmpn9xx+gyoYs7UiV5G5rvxNcA/Tc+MofGhAMiTmNicorNAs5mv6fRoVbkpIONRXPz6WK0kjx/X04EV42Vm9Qk='; + + $rsa = new RSA(); + $rsa->load($key); + + $this->assertSame($rsa->getLoadedFormat(), 'OpenSSH'); + + $this->assertGreaterThanOrEqual(1, strlen("$rsa")); + } + + public function testPuttyPublicKey() + { + $key = '---- BEGIN SSH2 PUBLIC KEY ---- +Comment: "rsa-key-20151023" +AAAAB3NzaC1yc2EAAAABJQAAAIEAhC/CSqJ+8vgeQ4H7fJru29h/McqAC9zdGzw0 +9QsifLQ7s5MvXCavhjUPYIfV0KsdLQydNPLJcbKpXmpVD9azo61zLXwsYr8d1eHr +C/EwUYl8b0fAwEsEF3myb+ryzgA9ihY08Zs9NZdmt1Maa+I7lQcLX9F/65YdcAch +ILaEujU= +---- END SSH2 PUBLIC KEY ----'; + + $rsa = new RSA(); + $rsa->load($key); + + $this->assertSame($rsa->getLoadedFormat(), 'PuTTY'); + + $this->assertGreaterThanOrEqual(1, strlen("$rsa")); + } }