From 7c7d500d80068b1fcca35fb9645d28d611169ef1 Mon Sep 17 00:00:00 2001 From: terrafrost Date: Thu, 23 May 2019 07:46:19 -0500 Subject: [PATCH] System/Agent: add support for DSA / ECDSA keys --- phpseclib/Crypt/ECDSA/Keys/Common.php | 50 +++++---- phpseclib/Crypt/ECDSA/Signature/SSH2.php | 11 +- phpseclib/System/SSH/Agent.php | 22 ++-- phpseclib/System/SSH/Agent/Identity.php | 134 +++++++++++++++++++---- tests/Unit/Crypt/ECDSA/KeyTest.php | 14 +-- 5 files changed, 166 insertions(+), 65 deletions(-) diff --git a/phpseclib/Crypt/ECDSA/Keys/Common.php b/phpseclib/Crypt/ECDSA/Keys/Common.php index 196a3348..048d49c9 100644 --- a/phpseclib/Crypt/ECDSA/Keys/Common.php +++ b/phpseclib/Crypt/ECDSA/Keys/Common.php @@ -70,6 +70,29 @@ trait Common // brainpool*r* curves are regular prime finite field curves // brainpool*t* curves are twisted versions of the brainpool*r* curves self::$curveOIDs = [ + 'prime192v1' => '1.2.840.10045.3.1.1', // J.5.1, example 1 (aka secp192r1) + 'prime192v2' => '1.2.840.10045.3.1.2', // J.5.1, example 2 + 'prime192v3' => '1.2.840.10045.3.1.3', // J.5.1, example 3 + 'prime239v1' => '1.2.840.10045.3.1.4', // J.5.2, example 1 + 'prime239v2' => '1.2.840.10045.3.1.5', // J.5.2, example 2 + 'prime239v3' => '1.2.840.10045.3.1.6', // J.5.2, example 3 + 'prime256v1' => '1.2.840.10045.3.1.7', // J.5.3, example 1 (aka secp256r1) + + // https://tools.ietf.org/html/rfc5656#section-10 + 'nistp256' => '1.2.840.10045.3.1.7', // aka secp256r1 + 'nistp384' => '1.3.132.0.34', // aka secp384r1 + 'nistp521' => '1.3.132.0.35', // aka secp521r1 + + 'nistk163' => '1.3.132.0.1', // aka sect163k1 + 'nistp192' => '1.2.840.10045.3.1.1', // aka secp192r1 + 'nistp224' => '1.3.132.0.33', // aka secp224r1 + 'nistk233' => '1.3.132.0.26', // aka sect233k1 + 'nistb233' => '1.3.132.0.27', // aka sect233r1 + 'nistk283' => '1.3.132.0.16', // aka sect283k1 + 'nistk409' => '1.3.132.0.36', // aka sect409k1 + 'nistb409' => '1.3.132.0.37', // aka sect409r1 + 'nistt571' => '1.3.132.0.38', // aka sect571k1 + // from https://tools.ietf.org/html/rfc5915 'secp192r1' => '1.2.840.10045.3.1.1', // aka prime192v1 'sect163k1' => '1.3.132.0.1', @@ -131,29 +154,6 @@ trait Common 'c2tnb431r1' => '1.2.840.10045.3.0.20', // J.4.10, example 1 */ - 'prime192v1' => '1.2.840.10045.3.1.1', // J.5.1, example 1 (aka secp192r1) - 'prime192v2' => '1.2.840.10045.3.1.2', // J.5.1, example 2 - 'prime192v3' => '1.2.840.10045.3.1.3', // J.5.1, example 3 - 'prime239v1' => '1.2.840.10045.3.1.4', // J.5.2, example 1 - 'prime239v2' => '1.2.840.10045.3.1.5', // J.5.2, example 2 - 'prime239v3' => '1.2.840.10045.3.1.6', // J.5.2, example 3 - 'prime256v1' => '1.2.840.10045.3.1.7', // J.5.3, example 1 (aka secp256r1) - - // https://tools.ietf.org/html/rfc5656#section-10 - 'nistp256' => '1.2.840.10045.3.1.7', // aka secp256r1 - 'nistp384' => '1.3.132.0.34', // aka secp384r1 - 'nistp521' => '1.3.132.0.35', // aka secp521r1 - - 'nistk163' => '1.3.132.0.1', // aka sect163k1 - 'nistp192' => '1.2.840.10045.3.1.1', // aka secp192r1 - 'nistp224' => '1.3.132.0.33', // aka secp224r1 - 'nistk233' => '1.3.132.0.26', // aka sect233k1 - 'nistb233' => '1.3.132.0.27', // aka sect233r1 - 'nistk283' => '1.3.132.0.16', // aka sect283k1 - 'nistk409' => '1.3.132.0.36', // aka sect409k1 - 'nistb409' => '1.3.132.0.37', // aka sect409r1 - 'nistt571' => '1.3.132.0.38', // aka sect571k1 - // http://www.ecc-brainpool.org/download/Domain-parameters.pdf // https://tools.ietf.org/html/rfc5639 'brainpoolP160r1' => '1.3.36.3.3.2.8.1.1.1', @@ -350,6 +350,10 @@ trait Common $reflect = new \ReflectionClass($curve); $name = $reflect->getShortName(); if (isset(self::$curveOIDs[$name]) && self::$useNamedCurves) { + if ($reflect->isFinal()) { + $reflect = $reflect->getParentClass(); + $name = $reflect->getShortName(); + } return $returnArray ? ['namedCurve' => $name] : ASN1::encodeDER(['namedCurve' => $name], Maps\ECParameters::MAP); diff --git a/phpseclib/Crypt/ECDSA/Signature/SSH2.php b/phpseclib/Crypt/ECDSA/Signature/SSH2.php index 1281d720..2ce21be6 100644 --- a/phpseclib/Crypt/ECDSA/Signature/SSH2.php +++ b/phpseclib/Crypt/ECDSA/Signature/SSH2.php @@ -80,9 +80,14 @@ abstract class SSH2 public static function save(BigInteger $r, BigInteger $s, $curve) { switch ($curve) { - case 'nistp256': - case 'nistp384': - case 'nistp521': + case 'secp256r1': + $curve = 'nistp256'; + break; + case 'secp384r1': + $curve = 'nistp384'; + break; + case 'secp521r1': + $curve = 'nistp521'; break; default: return false; diff --git a/phpseclib/System/SSH/Agent.php b/phpseclib/System/SSH/Agent.php index 1acb4ec2..c544ac5b 100644 --- a/phpseclib/System/SSH/Agent.php +++ b/phpseclib/System/SSH/Agent.php @@ -33,7 +33,6 @@ namespace phpseclib\System\SSH; -use ParagonIE\ConstantTime\Base64; use phpseclib\Crypt\RSA; use phpseclib\Exception\BadConfigurationException; use phpseclib\System\SSH\Agent\Identity; @@ -192,25 +191,22 @@ class Agent $identities = []; for ($i = 0; $i < $keyCount; $i++) { list($key_blob, $comment) = Strings::unpackSSH2('ss', $packet); - $key_str = 'ssh-rsa ' . base64_encode($key_blob); - if (strlen($comment)) { - $key_str.= " $comment"; - } $temp = $key_blob; list($key_type) = Strings::unpackSSH2('s', $temp); switch ($key_type) { - case 'ssh-rsa': - $key = PublicKeyLoader::load(base64_encode($key_blob)); - break; + case 'ssh-rsa': case 'ssh-dss': - // not currently supported - break; + case 'ssh-ed25519': + case 'ecdsa-sha2-nistp256': + case 'ecdsa-sha2-nistp384': + case 'ecdsa-sha2-nistp521': + $key = PublicKeyLoader::load($key_type . ' ' . base64_encode($key_blob)); } // resources are passed by reference by default if (isset($key)) { - $identity = new Identity($this->fsock); - $identity->setPublicKey($key); - $identity->setPublicKeyBlob($key_blob); + $identity = (new Identity($this->fsock)) + ->withPublicKey($key) + ->withPublicKeyBlob($key_blob); $identities[] = $identity; unset($key); } diff --git a/phpseclib/System/SSH/Agent/Identity.php b/phpseclib/System/SSH/Agent/Identity.php index b1c102f4..dbee3842 100644 --- a/phpseclib/System/SSH/Agent/Identity.php +++ b/phpseclib/System/SSH/Agent/Identity.php @@ -17,6 +17,8 @@ namespace phpseclib\System\SSH\Agent; use phpseclib\Crypt\RSA; +use phpseclib\Crypt\DSA; +use phpseclib\Crypt\ECDSA; use phpseclib\Exception\UnsupportedAlgorithmException; use phpseclib\System\SSH\Agent; use phpseclib\Common\Functions\Strings; @@ -84,7 +86,20 @@ class Identity implements PrivateKey * @see self::sign() * @see self::setHash() */ - var $flags = 0; + private $flags = 0; + + /** + * Curve Aliases + * + * @var array + * @access private + */ + private static $curveAliases = [ + 'secp256r1' => 'nistp256', + 'secp384r1' => 'nistp384', + 'secp521r1' => 'nistp521', + 'Ed25519' => 'Ed25519' + ]; /** * Default Constructor. @@ -103,12 +118,20 @@ class Identity implements PrivateKey * * Called by \phpseclib\System\SSH\Agent::requestIdentities() * - * @param \phpseclib\Crypt\RSA $key + * @param \phpseclib\Crypt\Common\PublicKey $key * @access private */ - public function setPublicKey($key) + public function withPublicKey($key) { - $this->key = $key; + if ($key instanceof ECDSA) { + if (is_array($key->getCurve()) || !isset(self::$curveAliases[$key->getCurve()])) { + throw new UnsupportedAlgorithmException('The only supported curves are nistp256, nistp384, nistp512 and Ed25519'); + } + } + + $new = clone $this; + $new->key = $key; + return $new; } /** @@ -120,9 +143,11 @@ class Identity implements PrivateKey * @param string $key_blob * @access private */ - public function setPublicKeyBlob($key_blob) + public function withPublicKeyBlob($key_blob) { - $this->key_blob = $key_blob; + $new = clone $this; + $new->key_blob = $key_blob; + return $new; } /** @@ -148,18 +173,45 @@ class Identity implements PrivateKey public function withHash($hash) { $new = clone $this; - $new->flags = 0; - switch ($hash) { - case 'sha1': - break; - case 'sha256': - $new->flags = self::SSH_AGENT_RSA2_256; - break; - case 'sha512': - $new->flags = self::SSH_AGENT_RSA2_512; - break; - default: - throw new UnsupportedAlgorithmException('The only supported hashes for RSA are sha1, sha256 and sha512'); + + $hash = strtolower($hash); + + if ($this->key instanceof RSA) { + $new->flags = 0; + switch ($hash) { + case 'sha1': + break; + case 'sha256': + $new->flags = self::SSH_AGENT_RSA2_256; + break; + case 'sha512': + $new->flags = self::SSH_AGENT_RSA2_512; + break; + default: + throw new UnsupportedAlgorithmException('The only supported hashes for RSA are sha1, sha256 and sha512'); + } + } + if ($this->key instanceof ECDSA) { + switch ($this->key->getCurve()) { + case 'secp256r1': + $expectedHash = 'sha256'; + break; + case 'secp384r1': + $expectedHash = 'sha384'; + break; + //case 'secp521r1': + //case 'Ed25519': + default: + $expectedHash = 'sha512'; + } + if ($hash != $expectedHash) { + throw new UnsupportedAlgorithmException('The only supported hash for ' . self::$curveAliases[$key->getCurve()] . ' is ' . $expectedHash); + } + } + if ($this->key instanceof DSA) { + if ($hash != 'sha1') { + throw new UnsupportedAlgorithmException('The only supported hash for DSA is sha1'); + } } return $new; } @@ -172,14 +224,54 @@ class Identity implements PrivateKey * @param string $padding * @access public */ - public function withPadding($padding = RSA::SIGNATURE_PKCS1) + public function withPadding($padding) { + if (!$this->key instanceof RSA) { + throw new UnsupportedAlgorithmException('Only RSA keys support padding'); + } if ($padding != RSA::SIGNATURE_PKCS1 && $padding != RSA::SIGNATURE_RELAXED_PKCS1) { throw new UnsupportedAlgorithmException('ssh-agent can only create PKCS1 signatures'); } return $this; } + /** + * Determines the signature padding mode + * + * Valid values are: ASN1, SSH2, Raw + * + * @access public + * @param string $padding + */ + public function withSignatureFormat($format) + { + if ($this->key instanceof RSA) { + throw new UnsupportedAlgorithmException('Only DSA and ECDSA keys support signature format setting'); + } + if ($format != 'SSH2') { + throw new UnsupportedAlgorithmException('ssh-agent can only create SSH2-formatted signatures'); + } + + return $this; + } + + /** + * Returns the curve + * + * Returns a string if it's a named curve, an array if not + * + * @access public + * @return string|array + */ + public function getCurve() + { + if (!$this->key instanceof ECDSA) { + throw new UnsupportedAlgorithmException('Only ECDSA keys have curves'); + } + + return $this->key->getCurve(); + } + /** * Create a signature * @@ -215,6 +307,10 @@ class Identity implements PrivateKey throw new \RuntimeException('Unable to retrieve signature'); } + if (!$this->key instanceof RSA) { + return $signature_blob; + } + list($type, $signature_blob) = Strings::unpackSSH2('ss', $signature_blob); return $signature_blob; diff --git a/tests/Unit/Crypt/ECDSA/KeyTest.php b/tests/Unit/Crypt/ECDSA/KeyTest.php index 2da73b0d..6149a041 100644 --- a/tests/Unit/Crypt/ECDSA/KeyTest.php +++ b/tests/Unit/Crypt/ECDSA/KeyTest.php @@ -276,13 +276,13 @@ Private-Lines: 1 AAAAIQDwaPlajbXY1SxhuwsUqN1CEZ5g4adsbmJsKm+ZbUVm4g== Private-MAC: b85ca0eb7c612df5d18af85128821bd53faaa3ef '); - $this->assertSame('nistp256', $key->getCurve()); + $this->assertSame('secp256r1', $key->getCurve()); PuTTY::setComment('ecdsa-key-20181105'); $this->assertSame($expected, $key->toString('PuTTY')); $key = PublicKeyLoader::load($expected = 'ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBJEXCsWA8s18m25MJlVE1urbXPYFi4q8oMbb2H0kE2f5WPxizsKXRmb1J68paXQizryL9fC4FTqICJ1+UnaPfk0= ecdsa-key-20181105'); - $this->assertSame('nistp256', $key->getCurve()); + $this->assertSame('secp256r1', $key->getCurve()); OpenSSH::setComment('ecdsa-key-20181105'); $this->assertSame($expected, $key->toString('OpenSSH')); @@ -302,13 +302,13 @@ AAAAMQCEMkGMDg6N7bUqdvLXe0YmY4qBSi8hmAuMvU38RDoVFVmV+R4RYmMueyrX be9Oyus= Private-MAC: 97a990a3d5f6b8f268d4be9c4ab9ebfd8fa79849 '); - $this->assertSame('nistp384', $key->getCurve()); + $this->assertSame('secp384r1', $key->getCurve()); PuTTY::setComment('ecdsa-key-20181105'); $this->assertSame($expected, $key->toString('PuTTY')); $key = PublicKeyLoader::load($expected = 'ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBOI53wHG3CdcAJZq5PXWZAEAxxsNVFQlQgOX9toWEOgqQF5LbK2nWLKRvaHMzocUXaTYZDccSS0ATZFPT3j1Er1LU9cu4PHpyS07v262jdzkxIvKCPcAeISuV80MC7rHog== ecdsa-key-20181105'); - $this->assertSame('nistp384', $key->getCurve()); + $this->assertSame('secp384r1', $key->getCurve()); OpenSSH::setComment('ecdsa-key-20181105'); $this->assertSame($expected, $key->toString('OpenSSH')); @@ -330,13 +330,13 @@ AAAAQgHJl8/dIArolFymdzhagXCfd2l8UF3CQXWGVGDQ0R04nnntlyztYiVdRXXK r84NnzS7dJcAsR9YaUOZ69NRKNiUAQ== Private-MAC: 6d49ce289b85549a43d74422dd8bb3ba8798c72c '); - $this->assertSame('nistp521', $key->getCurve()); + $this->assertSame('secp521r1', $key->getCurve()); PuTTY::setComment('ecdsa-key-20181105'); $this->assertSame($expected, $key->toString('PuTTY')); $key = PublicKeyLoader::load($expected = 'ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAF1Eg0MjaJwooFj6HCNh4RWbvmQRY+sdczJyBdT3EaTc/6IUcCfW7w7rAeRp2CDdE9RlAVD8IuLqW7DJH06Xeov8wBO5G6jUqXu0rlHsOSiC6VcCxBJuWVNB1IorHnS7PX0f6HdLlIEme73P77drqpn5YY0XLtP6hFrF7H5XfCxpNyaJA== ecdsa-key-20181105'); - $this->assertSame('nistp521', $key->getCurve()); + $this->assertSame('secp521r1', $key->getCurve()); OpenSSH::setComment('ecdsa-key-20181105'); $this->assertSame($expected, $key->toString('OpenSSH')); @@ -418,7 +418,7 @@ pomV7r6gmoMYteGVABfgAAAAD3ZhZ3JhbnRAdmFncmFudAECAwQFBg== '); - $this->assertSame('nistp256', $key->getCurve()); + $this->assertSame('secp256r1', $key->getCurve()); XML::enableRFC4050Syntax();