mirror of
https://github.com/phpseclib/phpseclib.git
synced 2024-12-27 19:54:57 +00:00
SSH2: add support for elliptic curve hostkeys
This commit is contained in:
parent
c9d4a89267
commit
1780bee619
@ -473,10 +473,10 @@ class ECDSA extends AsymmetricKey
|
||||
*/
|
||||
public function setHash($hash)
|
||||
{
|
||||
if ($this->curve instanceof Ed25519 && $this->hash != 'sha512') {
|
||||
if ($this->curve instanceof Ed25519 && $hash != 'sha512') {
|
||||
throw new UnsupportedAlgorithmException('Ed25519 only supports sha512 as a hash');
|
||||
}
|
||||
if ($this->curve instanceof Ed448 && $this->hash != 'shake256-912') {
|
||||
if ($this->curve instanceof Ed448 && $hash != 'shake256-912') {
|
||||
throw new UnsupportedAlgorithmException('Ed448 only supports shake256 with a length of 114 bytes');
|
||||
}
|
||||
|
||||
|
@ -57,11 +57,14 @@ abstract class SSH2
|
||||
return false;
|
||||
}
|
||||
|
||||
$length = ceil(substr($type, 16) / 8);
|
||||
$result = Strings::unpackSSH2('ii', $blob);
|
||||
if ($result === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [
|
||||
'r' => new BigInteger(substr($blob, 0, $length), 256),
|
||||
's' => new BigInteger(substr($blob, $length), 256)
|
||||
'r' => $result[0],
|
||||
's' => $result[1]
|
||||
];
|
||||
}
|
||||
|
||||
@ -85,11 +88,8 @@ abstract class SSH2
|
||||
return false;
|
||||
}
|
||||
|
||||
$length = ceil(substr($curve, 5) / 8);
|
||||
$blob = Strings::packSSH2('ii', $r, $s);
|
||||
|
||||
return Strings::packSSH2('ss', 'ecdsa-sha2-' . $curve,
|
||||
str_pad($r->toBytes(), $length, "\0", STR_PAD_LEFT) .
|
||||
str_pad($s->toBytes(), $length, "\0", STR_PAD_LEFT)
|
||||
);
|
||||
return Strings::packSSH2('ss', 'ecdsa-sha2-' . $curve, $blob);
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,8 @@ use phpseclib\Crypt\Random;
|
||||
use phpseclib\Crypt\RC4;
|
||||
use phpseclib\Crypt\Rijndael;
|
||||
use phpseclib\Crypt\RSA;
|
||||
use phpseclib\Crypt\DSA;
|
||||
use phpseclib\Crypt\ECDSA;
|
||||
use phpseclib\Crypt\TripleDES;
|
||||
use phpseclib\Crypt\Twofish;
|
||||
use phpseclib\Crypt\ChaCha20;
|
||||
@ -1719,8 +1721,8 @@ class SSH2
|
||||
if (strlen($this->signature) < 4) {
|
||||
return false;
|
||||
}
|
||||
$temp = unpack('Nlength', Strings::shift($this->signature, 4));
|
||||
$this->signature_format = Strings::shift($this->signature, $temp['length']);
|
||||
$temp = unpack('Nlength', substr($this->signature, 0, 4));
|
||||
$this->signature_format = substr($this->signature, 4, $temp['length']);
|
||||
if ($this->kex_algorithm === 'curve25519-sha256@libssh.org') {
|
||||
if (strlen($fBytes) !== 32) {
|
||||
throw new \RuntimeException('Received curve25519 public key of invalid length.');
|
||||
@ -1768,16 +1770,14 @@ class SSH2
|
||||
}
|
||||
|
||||
switch ($server_host_key_algorithm) {
|
||||
case 'ssh-dss':
|
||||
$expected_key_format = 'ssh-dss';
|
||||
break;
|
||||
//case 'rsa-sha2-256':
|
||||
//case 'rsa-sha2-512':
|
||||
case 'rsa-sha2-256':
|
||||
case 'rsa-sha2-512':
|
||||
//case 'ssh-rsa':
|
||||
default:
|
||||
$expected_key_format = 'ssh-rsa';
|
||||
break;
|
||||
default:
|
||||
$expected_key_format = $server_host_key_algorithm;
|
||||
}
|
||||
|
||||
if ($public_key_format != $expected_key_format || $this->signature_format != $server_host_key_algorithm) {
|
||||
switch (true) {
|
||||
case $this->signature_format == $server_host_key_algorithm:
|
||||
@ -4611,6 +4611,8 @@ class SSH2
|
||||
public static function getSupportedHostKeyAlgorithms()
|
||||
{
|
||||
return [
|
||||
'ssh-ed25519', // https://tools.ietf.org/html/draft-ietf-curdle-ssh-ed25519-02
|
||||
'ecdsa-sha2-nistp256', // RFC 5656
|
||||
'rsa-sha2-256', // RFC 8332
|
||||
'rsa-sha2-512', // RFC 8332
|
||||
'ssh-rsa', // RECOMMENDED sign Raw RSA Key
|
||||
@ -4902,15 +4904,7 @@ class SSH2
|
||||
}
|
||||
|
||||
$signature = $this->signature;
|
||||
$server_public_host_key = $this->server_public_host_key;
|
||||
|
||||
if (strlen($server_public_host_key) < 4) {
|
||||
return false;
|
||||
}
|
||||
extract(unpack('Nlength', Strings::shift($server_public_host_key, 4)));
|
||||
/** @var integer $length */
|
||||
|
||||
Strings::shift($server_public_host_key, $length);
|
||||
$server_public_host_key = base64_encode($this->server_public_host_key);
|
||||
|
||||
if ($this->signature_validated) {
|
||||
return $this->bitmap ?
|
||||
@ -4921,101 +4915,42 @@ class SSH2
|
||||
$this->signature_validated = true;
|
||||
|
||||
switch ($this->signature_format) {
|
||||
case 'ssh-dss':
|
||||
$zero = new BigInteger();
|
||||
|
||||
if (strlen($server_public_host_key) < 4) {
|
||||
return false;
|
||||
case 'ssh-ed25519':
|
||||
case 'ecdsa-sha2-nistp256':
|
||||
$ec = new ECDSA();
|
||||
$ec->load($server_public_host_key, 'OpenSSH');
|
||||
switch ($this->signature_format) {
|
||||
case 'ssh-ed25519':
|
||||
//$ec->setHash('sha512');
|
||||
Strings::shift($signature, 4 + strlen('ssh-ed25519') + 4);
|
||||
break;
|
||||
case 'ecdsa-sha2-nistp256':
|
||||
$ec->setHash('sha256');
|
||||
}
|
||||
$temp = unpack('Nlength', Strings::shift($server_public_host_key, 4));
|
||||
$p = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256);
|
||||
|
||||
if (strlen($server_public_host_key) < 4) {
|
||||
return false;
|
||||
}
|
||||
$temp = unpack('Nlength', Strings::shift($server_public_host_key, 4));
|
||||
$q = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256);
|
||||
|
||||
if (strlen($server_public_host_key) < 4) {
|
||||
return false;
|
||||
}
|
||||
$temp = unpack('Nlength', Strings::shift($server_public_host_key, 4));
|
||||
$g = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256);
|
||||
|
||||
if (strlen($server_public_host_key) < 4) {
|
||||
return false;
|
||||
}
|
||||
$temp = unpack('Nlength', Strings::shift($server_public_host_key, 4));
|
||||
$y = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256);
|
||||
|
||||
/* The value for 'dss_signature_blob' is encoded as a string containing
|
||||
r, followed by s (which are 160-bit integers, without lengths or
|
||||
padding, unsigned, and in network byte order). */
|
||||
$temp = unpack('Nlength', Strings::shift($signature, 4));
|
||||
if ($temp['length'] != 40) {
|
||||
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
|
||||
throw new \RuntimeException('Invalid signature');
|
||||
}
|
||||
|
||||
$r = new BigInteger(Strings::shift($signature, 20), 256);
|
||||
$s = new BigInteger(Strings::shift($signature, 20), 256);
|
||||
|
||||
switch (true) {
|
||||
case $r->equals($zero):
|
||||
case $r->compare($q) >= 0:
|
||||
case $s->equals($zero):
|
||||
case $s->compare($q) >= 0:
|
||||
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
|
||||
throw new \RuntimeException('Invalid signature');
|
||||
}
|
||||
|
||||
$w = $s->modInverse($q);
|
||||
|
||||
$u1 = $w->multiply(new BigInteger(sha1($this->exchange_hash), 16));
|
||||
list(, $u1) = $u1->divide($q);
|
||||
|
||||
$u2 = $w->multiply($r);
|
||||
list(, $u2) = $u2->divide($q);
|
||||
|
||||
$g = $g->modPow($u1, $p);
|
||||
$y = $y->modPow($u2, $p);
|
||||
|
||||
$v = $g->multiply($y);
|
||||
list(, $v) = $v->divide($p);
|
||||
list(, $v) = $v->divide($q);
|
||||
|
||||
if (!$v->equals($r)) {
|
||||
//user_error('Bad server signature');
|
||||
if (!$ec->verify($this->exchange_hash, $signature, 'SSH2')) {
|
||||
return $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
|
||||
}
|
||||
|
||||
};
|
||||
break;
|
||||
case 'ssh-dss':
|
||||
$dsa = new DSA();
|
||||
$dsa->load($server_public_host_key, 'OpenSSH');
|
||||
$dsa->setHash('sha1');
|
||||
if (!$dsa->verify($this->exchange_hash, $signature, 'SSH2')) {
|
||||
return $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
|
||||
};
|
||||
break;
|
||||
case 'ssh-rsa':
|
||||
case 'rsa-sha2-256':
|
||||
case 'rsa-sha2-512':
|
||||
if (strlen($server_public_host_key) < 4) {
|
||||
return false;
|
||||
}
|
||||
$temp = unpack('Nlength', Strings::shift($server_public_host_key, 4));
|
||||
$e = new BigInteger(Strings::shift($server_public_host_key, $temp['length']), -256);
|
||||
|
||||
if (strlen($server_public_host_key) < 4) {
|
||||
return false;
|
||||
}
|
||||
$temp = unpack('Nlength', Strings::shift($server_public_host_key, 4));
|
||||
$rawN = Strings::shift($server_public_host_key, $temp['length']);
|
||||
$n = new BigInteger($rawN, -256);
|
||||
$nLength = strlen(ltrim($rawN, "\0"));
|
||||
|
||||
/*
|
||||
if (strlen($signature) < 4) {
|
||||
if (strlen($signature) < 15) {
|
||||
return false;
|
||||
}
|
||||
Strings::shift($signature, 11);
|
||||
$temp = unpack('Nlength', Strings::shift($signature, 4));
|
||||
$signature = Strings::shift($signature, $temp['length']);
|
||||
|
||||
$rsa = new RSA();
|
||||
$rsa->load(['e' => $e, 'n' => $n], 'raw');
|
||||
$rsa->load($server_public_host_key, 'OpenSSH');
|
||||
switch ($this->signature_format) {
|
||||
case 'rsa-sha2-512':
|
||||
$hash = 'sha512';
|
||||
@ -5029,59 +4964,6 @@ class SSH2
|
||||
}
|
||||
$rsa->setHash($hash);
|
||||
if (!$rsa->verify($this->exchange_hash, $signature, RSA::PADDING_PKCS1)) {
|
||||
//user_error('Bad server signature');
|
||||
return $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
|
||||
}
|
||||
*/
|
||||
|
||||
if (strlen($signature) < 4) {
|
||||
return false;
|
||||
}
|
||||
$temp = unpack('Nlength', Strings::shift($signature, 4));
|
||||
$s = new BigInteger(Strings::shift($signature, $temp['length']), 256);
|
||||
|
||||
// validate an RSA signature per "8.2 RSASSA-PKCS1-v1_5", "5.2.2 RSAVP1", and "9.1 EMSA-PSS" in the
|
||||
// following URL:
|
||||
// ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1.pdf
|
||||
|
||||
// also, see SSHRSA.c (rsa2_verifysig) in PuTTy's source.
|
||||
|
||||
if ($s->compare(new BigInteger()) < 0 || $s->compare($n->subtract(new BigInteger(1))) > 0) {
|
||||
$this->disconnect_helper(NET_SSH2_DISCONNECT_KEY_EXCHANGE_FAILED);
|
||||
throw new \RuntimeException('Invalid signature');
|
||||
}
|
||||
|
||||
$s = $s->modPow($e, $n);
|
||||
$s = $s->toBytes();
|
||||
|
||||
switch ($this->signature_format) {
|
||||
case 'rsa-sha2-512':
|
||||
$hash = 'sha512';
|
||||
break;
|
||||
case 'rsa-sha2-256':
|
||||
$hash = 'sha256';
|
||||
break;
|
||||
//case 'ssh-rsa':
|
||||
default:
|
||||
$hash = 'sha1';
|
||||
}
|
||||
$hashObj = new Hash($hash);
|
||||
switch ($this->signature_format) {
|
||||
case 'rsa-sha2-512':
|
||||
$h = pack('N5a*', 0x00305130, 0x0D060960, 0x86480165, 0x03040203, 0x05000440, $hashObj->hash($this->exchange_hash));
|
||||
break;
|
||||
case 'rsa-sha2-256':
|
||||
$h = pack('N5a*', 0x00303130, 0x0D060960, 0x86480165, 0x03040201, 0x05000420, $hashObj->hash($this->exchange_hash));
|
||||
break;
|
||||
//case 'ssh-rsa':
|
||||
default:
|
||||
$hash = 'sha1';
|
||||
$h = pack('N4a*', 0x00302130, 0x0906052B, 0x0E03021A, 0x05000414, $hashObj->hash($this->exchange_hash));
|
||||
}
|
||||
$h = chr(0x01) . str_repeat(chr(0xFF), $nLength - 2 - strlen($h)) . $h;
|
||||
|
||||
if ($s != $h) {
|
||||
//user_error('Bad server signature');
|
||||
return $this->disconnect_helper(NET_SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE);
|
||||
}
|
||||
break;
|
||||
|
Loading…
Reference in New Issue
Block a user