System/Agent: add support for DSA / ECDSA keys

This commit is contained in:
terrafrost 2019-05-23 07:46:19 -05:00
parent 85e2bd4811
commit 7c7d500d80
5 changed files with 166 additions and 65 deletions

View File

@ -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);

View File

@ -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;

View File

@ -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);
}

View File

@ -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;

View File

@ -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==
<Y Value="102403352136827775240910267217779508359028642524881540878079119895764161434936" />
</PublicKey>
</ECDSAKeyValue>');
$this->assertSame('nistp256', $key->getCurve());
$this->assertSame('secp256r1', $key->getCurve());
XML::enableRFC4050Syntax();