SSH2: add support for EtM

This commit is contained in:
terrafrost 2019-09-08 17:51:53 -05:00
parent 580eb94fd3
commit 71ced69714
2 changed files with 63 additions and 16 deletions

View File

@ -582,8 +582,8 @@ class Hash
// //
$length = strlen($m[$i]); $length = strlen($m[$i]);
$pad = 32 - ($length % 32); $pad = 32 - ($length % 32);
$pad%= 32; $pad = max(32, $length + $pad % 32);
$m[$i] = str_pad($m[$i], $length + $pad, "\0"); // zeropad $m[$i] = str_pad($m[$i], $pad, "\0"); // zeropad
$m[$i] = pack('N*', ...unpack('V*', $m[$i])); // ENDIAN-SWAP $m[$i] = pack('N*', ...unpack('V*', $m[$i])); // ENDIAN-SWAP
$y.= static::nh($k, $m[$i], new BigInteger($length * 8)); $y.= static::nh($k, $m[$i], new BigInteger($length * 8));

View File

@ -1782,22 +1782,27 @@ class SSH2
$createKeyLength = 0; // ie. $mac_algorithm == 'none' $createKeyLength = 0; // ie. $mac_algorithm == 'none'
switch ($mac_algorithm) { switch ($mac_algorithm) {
case 'umac-64@openssh.com': case 'umac-64@openssh.com':
case 'umac-64-etm@openssh.com':
$this->hmac_create = new Hash('umac-64'); $this->hmac_create = new Hash('umac-64');
$createKeyLength = 16; $createKeyLength = 16;
break; break;
case 'umac-128@openssh.com': case 'umac-128@openssh.com':
case 'umac-128-etm@openssh.com':
$this->hmac_create = new Hash('umac-128'); $this->hmac_create = new Hash('umac-128');
$createKeyLength = 16; $createKeyLength = 16;
break; break;
case 'hmac-sha2-512': case 'hmac-sha2-512':
case 'hmac-sha2-512-etm@openssh.com':
$this->hmac_create = new Hash('sha512'); $this->hmac_create = new Hash('sha512');
$createKeyLength = 64; $createKeyLength = 64;
break; break;
case 'hmac-sha2-256': case 'hmac-sha2-256':
case 'hmac-sha2-256-etm@openssh.com':
$this->hmac_create = new Hash('sha256'); $this->hmac_create = new Hash('sha256');
$createKeyLength = 32; $createKeyLength = 32;
break; break;
case 'hmac-sha1': case 'hmac-sha1':
case 'hmac-sha1-etm@openssh.com':
$this->hmac_create = new Hash('sha1'); $this->hmac_create = new Hash('sha1');
$createKeyLength = 20; $createKeyLength = 20;
break; break;
@ -1821,6 +1826,7 @@ class SSH2
} }
$this->hmac_create->setKey(substr($key, 0, $createKeyLength)); $this->hmac_create->setKey(substr($key, 0, $createKeyLength));
$this->hmac_create->name = $mac_algorithm; $this->hmac_create->name = $mac_algorithm;
$this->hmac_create->etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm);
} }
$mac_algorithm = $this->array_intersect_first($s2c_mac_algorithms, $this->mac_algorithms_server_to_client); $mac_algorithm = $this->array_intersect_first($s2c_mac_algorithms, $this->mac_algorithms_server_to_client);
@ -1839,26 +1845,31 @@ class SSH2
$this->hmac_size = 0; $this->hmac_size = 0;
switch ($mac_algorithm) { switch ($mac_algorithm) {
case 'umac-64@openssh.com': case 'umac-64@openssh.com':
case 'umac-64-etm@openssh.com':
$this->hmac_check = new Hash('umac-64'); $this->hmac_check = new Hash('umac-64');
$checkKeyLength = 16; $checkKeyLength = 16;
$this->hmac_size = 8; $this->hmac_size = 8;
break; break;
case 'umac-128@openssh.com': case 'umac-128@openssh.com':
case 'umac-128-etm@openssh.com':
$this->hmac_check = new Hash('umac-128'); $this->hmac_check = new Hash('umac-128');
$checkKeyLength = 16; $checkKeyLength = 16;
$this->hmac_size = 16; $this->hmac_size = 16;
break; break;
case 'hmac-sha2-512': case 'hmac-sha2-512':
case 'hmac-sha2-512-etm@openssh.com':
$this->hmac_check = new Hash('sha512'); $this->hmac_check = new Hash('sha512');
$checkKeyLength = 64; $checkKeyLength = 64;
$this->hmac_size = 64; $this->hmac_size = 64;
break; break;
case 'hmac-sha2-256': case 'hmac-sha2-256':
case 'hmac-sha2-256-etm@openssh.com':
$this->hmac_check = new Hash('sha256'); $this->hmac_check = new Hash('sha256');
$checkKeyLength = 32; $checkKeyLength = 32;
$this->hmac_size = 32; $this->hmac_size = 32;
break; break;
case 'hmac-sha1': case 'hmac-sha1':
case 'hmac-sha1-etm@openssh.com':
$this->hmac_check = new Hash('sha1'); $this->hmac_check = new Hash('sha1');
$checkKeyLength = 20; $checkKeyLength = 20;
$this->hmac_size = 20; $this->hmac_size = 20;
@ -1886,6 +1897,7 @@ class SSH2
} }
$this->hmac_check->setKey(substr($key, 0, $checkKeyLength)); $this->hmac_check->setKey(substr($key, 0, $checkKeyLength));
$this->hmac_check->name = $mac_algorithm; $this->hmac_check->name = $mac_algorithm;
$this->hmac_check->etm = preg_match('#-etm@openssh\.com$#', $mac_algorithm);
} }
$compression_algorithm = $this->array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_server_to_client); $compression_algorithm = $this->array_intersect_first($s2c_compression_algorithms, $this->compression_algorithms_server_to_client);
@ -3221,7 +3233,19 @@ class SSH2
$remaining_length = 0; $remaining_length = 0;
break; break;
default: default:
$raw = $this->decrypt->decrypt($raw); if (!$this->hmac_check instanceof Hash || !$this->hmac_check->etm) {
$raw = $this->decrypt->decrypt($raw);
break;
}
extract(unpack('Npacket_length', $temp = Strings::shift($raw, 4)));
/**
* @var integer $packet_length
*/
$raw.= $this->read_remaining_bytes($packet_length - $this->decrypt_block_size + 4);
$stop = microtime(true);
$encrypted = $temp . $raw;
$raw = $temp . $this->decrypt->decrypt($raw);
$remaining_length = 0;
} }
} }
@ -3257,7 +3281,9 @@ class SSH2
throw new \RuntimeException('Error reading socket'); throw new \RuntimeException('Error reading socket');
} }
$reconstructed = pack('NCa*', $packet_length, $padding_length, $payload . $padding); $reconstructed = !$this->hmac_check->etm ?
pack('NCa*', $packet_length, $padding_length, $payload . $padding) :
$encrypted;
if (($this->hmac_check->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') { if (($this->hmac_check->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
$this->hmac_check->setNonce("\0\0\0\0" . pack('N', $this->get_seq_no)); $this->hmac_check->setNonce("\0\0\0\0" . pack('N', $this->get_seq_no));
if ($hmac != $this->hmac_check->hash($reconstructed)) { if ($hmac != $this->hmac_check->hash($reconstructed)) {
@ -3304,10 +3330,11 @@ class SSH2
$adjustLength = false; $adjustLength = false;
if ($this->decrypt) { if ($this->decrypt) {
switch ($this->decrypt->name) { switch (true) {
case 'aes128-gcm@openssh.com': case $this->decrypt->name == 'aes128-gcm@openssh.com':
case 'aes256-gcm@openssh.com': case $this->decrypt->name == 'aes256-gcm@openssh.com':
case 'chacha20-poly1305@openssh.com': case $this->decrypt->name == 'chacha20-poly1305@openssh.com':
case $this->hmac_check instanceof Hash && $this->hmac_check->etm:
$remaining_length+= $this->decrypt_block_size - 4; $remaining_length+= $this->decrypt_block_size - 4;
$adjustLength = true; $adjustLength = true;
} }
@ -3820,18 +3847,20 @@ class SSH2
$packet_length+= (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size; $packet_length+= (($this->encrypt_block_size - 1) * $packet_length) % $this->encrypt_block_size;
// subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length // subtracting strlen($data) is obvious - subtracting 5 is necessary because of packet_length and padding_length
$padding_length = $packet_length - strlen($data) - 5; $padding_length = $packet_length - strlen($data) - 5;
if ($this->encrypt && $this->encrypt->usesNonce()) { switch (true) {
$padding_length+= 4; case $this->encrypt && $this->encrypt->usesNonce():
$packet_length+= 4; case $this->hmac_create instanceof Hash && $this->hmac_create->etm:
$padding_length+= 4;
$packet_length+= 4;
} }
$padding = Random::string($padding_length); $padding = Random::string($padding_length);
// we subtract 4 from packet_length because the packet_length field isn't supposed to include itself // we subtract 4 from packet_length because the packet_length field isn't supposed to include itself
$packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding); $packet = pack('NCa*', $packet_length - 4, $padding_length, $data . $padding);
if (!$this->hmac_create instanceof Hash) { $hmac = '';
$hmac = ''; if ($this->hmac_create instanceof Hash && !$this->hmac_create->etm) {
} else {
if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') { if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
$this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no)); $this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no));
$hmac = $this->hmac_create->hash($packet); $hmac = $this->hmac_create->hash($packet);
@ -3839,6 +3868,7 @@ class SSH2
$hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet)); $hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet));
} }
} }
$this->send_seq_no++; $this->send_seq_no++;
if ($this->encrypt) { if ($this->encrypt) {
@ -3873,7 +3903,18 @@ class SSH2
$packet = $length . $this->encrypt->encrypt(substr($packet, 4)); $packet = $length . $this->encrypt->encrypt(substr($packet, 4));
break; break;
default: default:
$packet = $this->encrypt->encrypt($packet); $packet = $this->hmac_create instanceof Hash && $this->hmac_create->etm ?
($packet & "\xFF\xFF\xFF\xFF") . $this->encrypt->encrypt(substr($packet, 4)) :
$this->encrypt->encrypt($packet);
}
}
if ($this->hmac_create instanceof Hash && $this->hmac_create->etm) {
if (($this->hmac_create->getHash() & "\xFF\xFF\xFF\xFF") == 'umac') {
$this->hmac_create->setNonce("\0\0\0\0" . pack('N', $this->send_seq_no - 1));
$hmac = $this->hmac_create->hash($packet);
} else {
$hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no - 1, $packet));
} }
} }
@ -4441,9 +4482,15 @@ class SSH2
public static function getSupportedMACAlgorithms() public static function getSupportedMACAlgorithms()
{ {
return [ return [
'hmac-sha2-256-etm@openssh.com',
'hmac-sha2-512-etm@openssh.com',
'umac-64-etm@openssh.com',
'umac-128-etm@openssh.com',
'hmac-sha1-etm@openssh.com',
// from <http://www.ietf.org/rfc/rfc6668.txt>: // from <http://www.ietf.org/rfc/rfc6668.txt>:
'hmac-sha2-512',// OPTIONAL HMAC-SHA512 (digest length = key length = 64)
'hmac-sha2-256',// RECOMMENDED HMAC-SHA256 (digest length = key length = 32) 'hmac-sha2-256',// RECOMMENDED HMAC-SHA256 (digest length = key length = 32)
'hmac-sha2-512',// OPTIONAL HMAC-SHA512 (digest length = key length = 64)
// from <https://tools.ietf.org/html/draft-miller-secsh-umac-01>: // from <https://tools.ietf.org/html/draft-miller-secsh-umac-01>:
'umac-64@openssh.com', 'umac-64@openssh.com',