From 71ced6971432710d76c8f024ee1dd3d3c0133f5c Mon Sep 17 00:00:00 2001 From: terrafrost Date: Sun, 8 Sep 2019 17:51:53 -0500 Subject: [PATCH] SSH2: add support for EtM --- phpseclib/Crypt/Hash.php | 4 +-- phpseclib/Net/SSH2.php | 75 ++++++++++++++++++++++++++++++++-------- 2 files changed, 63 insertions(+), 16 deletions(-) diff --git a/phpseclib/Crypt/Hash.php b/phpseclib/Crypt/Hash.php index 2059c5cd..41615317 100644 --- a/phpseclib/Crypt/Hash.php +++ b/phpseclib/Crypt/Hash.php @@ -582,8 +582,8 @@ class Hash // $length = strlen($m[$i]); $pad = 32 - ($length % 32); - $pad%= 32; - $m[$i] = str_pad($m[$i], $length + $pad, "\0"); // zeropad + $pad = max(32, $length + $pad % 32); + $m[$i] = str_pad($m[$i], $pad, "\0"); // zeropad $m[$i] = pack('N*', ...unpack('V*', $m[$i])); // ENDIAN-SWAP $y.= static::nh($k, $m[$i], new BigInteger($length * 8)); diff --git a/phpseclib/Net/SSH2.php b/phpseclib/Net/SSH2.php index b6346d2c..411f93ac 100644 --- a/phpseclib/Net/SSH2.php +++ b/phpseclib/Net/SSH2.php @@ -1782,22 +1782,27 @@ class SSH2 $createKeyLength = 0; // ie. $mac_algorithm == 'none' switch ($mac_algorithm) { case 'umac-64@openssh.com': + case 'umac-64-etm@openssh.com': $this->hmac_create = new Hash('umac-64'); $createKeyLength = 16; break; case 'umac-128@openssh.com': + case 'umac-128-etm@openssh.com': $this->hmac_create = new Hash('umac-128'); $createKeyLength = 16; break; case 'hmac-sha2-512': + case 'hmac-sha2-512-etm@openssh.com': $this->hmac_create = new Hash('sha512'); $createKeyLength = 64; break; case 'hmac-sha2-256': + case 'hmac-sha2-256-etm@openssh.com': $this->hmac_create = new Hash('sha256'); $createKeyLength = 32; break; case 'hmac-sha1': + case 'hmac-sha1-etm@openssh.com': $this->hmac_create = new Hash('sha1'); $createKeyLength = 20; break; @@ -1821,6 +1826,7 @@ class SSH2 } $this->hmac_create->setKey(substr($key, 0, $createKeyLength)); $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); @@ -1839,26 +1845,31 @@ class SSH2 $this->hmac_size = 0; switch ($mac_algorithm) { case 'umac-64@openssh.com': + case 'umac-64-etm@openssh.com': $this->hmac_check = new Hash('umac-64'); $checkKeyLength = 16; $this->hmac_size = 8; break; case 'umac-128@openssh.com': + case 'umac-128-etm@openssh.com': $this->hmac_check = new Hash('umac-128'); $checkKeyLength = 16; $this->hmac_size = 16; break; case 'hmac-sha2-512': + case 'hmac-sha2-512-etm@openssh.com': $this->hmac_check = new Hash('sha512'); $checkKeyLength = 64; $this->hmac_size = 64; break; case 'hmac-sha2-256': + case 'hmac-sha2-256-etm@openssh.com': $this->hmac_check = new Hash('sha256'); $checkKeyLength = 32; $this->hmac_size = 32; break; case 'hmac-sha1': + case 'hmac-sha1-etm@openssh.com': $this->hmac_check = new Hash('sha1'); $checkKeyLength = 20; $this->hmac_size = 20; @@ -1886,6 +1897,7 @@ class SSH2 } $this->hmac_check->setKey(substr($key, 0, $checkKeyLength)); $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); @@ -3221,7 +3233,19 @@ class SSH2 $remaining_length = 0; break; 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'); } - $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') { $this->hmac_check->setNonce("\0\0\0\0" . pack('N', $this->get_seq_no)); if ($hmac != $this->hmac_check->hash($reconstructed)) { @@ -3304,10 +3330,11 @@ class SSH2 $adjustLength = false; if ($this->decrypt) { - switch ($this->decrypt->name) { - case 'aes128-gcm@openssh.com': - case 'aes256-gcm@openssh.com': - case 'chacha20-poly1305@openssh.com': + switch (true) { + case $this->decrypt->name == 'aes128-gcm@openssh.com': + case $this->decrypt->name == 'aes256-gcm@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; $adjustLength = true; } @@ -3820,18 +3847,20 @@ class SSH2 $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 $padding_length = $packet_length - strlen($data) - 5; - if ($this->encrypt && $this->encrypt->usesNonce()) { - $padding_length+= 4; - $packet_length+= 4; + switch (true) { + case $this->encrypt && $this->encrypt->usesNonce(): + case $this->hmac_create instanceof Hash && $this->hmac_create->etm: + $padding_length+= 4; + $packet_length+= 4; } + $padding = Random::string($padding_length); // 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); - if (!$this->hmac_create instanceof Hash) { - $hmac = ''; - } else { + $hmac = ''; + 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)); $hmac = $this->hmac_create->hash($packet); @@ -3839,6 +3868,7 @@ class SSH2 $hmac = $this->hmac_create->hash(pack('Na*', $this->send_seq_no, $packet)); } } + $this->send_seq_no++; if ($this->encrypt) { @@ -3873,7 +3903,18 @@ class SSH2 $packet = $length . $this->encrypt->encrypt(substr($packet, 4)); break; 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() { 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 : - 'hmac-sha2-512',// OPTIONAL HMAC-SHA512 (digest length = key length = 64) 'hmac-sha2-256',// RECOMMENDED HMAC-SHA256 (digest length = key length = 32) + 'hmac-sha2-512',// OPTIONAL HMAC-SHA512 (digest length = key length = 64) // from : 'umac-64@openssh.com',