diff options
author | Hans de Graaff <hans@degraaff.org> | 2019-09-13 09:56:21 +0200 |
---|---|---|
committer | Hans de Graaff <hans@degraaff.org> | 2019-09-17 07:10:13 +0200 |
commit | 1dd04c3a1023eaa72f824d85829bc251279bb64d (patch) | |
tree | 42eb0f1d833c92513a397cbc80f529d7ab5efaff /lib/net/ssh/transport | |
parent | 1dd787785fe84e08326c0796685466491c66911f (diff) | |
download | net-ssh-1dd04c3a1023eaa72f824d85829bc251279bb64d.tar.gz |
Add sha2-{256,512}-etm@openssh.com MAC algorithms
Implement the Encrypt-Then-Mac versions of the SHA2-256 and SHA2-512
MACs. These MACs are implemented by openssh and may be the only MACs
available on a hardened installation of openssh.
With EtM the MAC is calculated over the unencrypted packet length and
the encrypted payload (which includes padding length and padding).
The main benefit of EtM schemes is that it allows the encrypted
payload to be authenticated before it gets passed to the encryption
engine. This patch does not implement that mechanism, but this can be
added later to the poll_next_packet method. Note that all current MACs
already pass unauthenticated data to the encryption engine.
Diffstat (limited to 'lib/net/ssh/transport')
-rw-r--r-- | lib/net/ssh/transport/algorithms.rb | 3 | ||||
-rw-r--r-- | lib/net/ssh/transport/hmac.rb | 26 | ||||
-rw-r--r-- | lib/net/ssh/transport/hmac/abstract.rb | 16 | ||||
-rw-r--r-- | lib/net/ssh/transport/hmac/sha2_256_etm.rb | 12 | ||||
-rw-r--r-- | lib/net/ssh/transport/hmac/sha2_512_etm.rb | 12 | ||||
-rw-r--r-- | lib/net/ssh/transport/packet_stream.rb | 54 |
6 files changed, 101 insertions, 22 deletions
diff --git a/lib/net/ssh/transport/algorithms.rb b/lib/net/ssh/transport/algorithms.rb index 830fa71..86059dd 100644 --- a/lib/net/ssh/transport/algorithms.rb +++ b/lib/net/ssh/transport/algorithms.rb @@ -43,7 +43,8 @@ module Net encryption: %w[aes256-ctr aes192-ctr aes128-ctr], - hmac: %w[hmac-sha2-512 hmac-sha2-256 + hmac: %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com + hmac-sha2-512 hmac-sha2-256 hmac-sha1] }.freeze diff --git a/lib/net/ssh/transport/hmac.rb b/lib/net/ssh/transport/hmac.rb index d8560eb..0905050 100644 --- a/lib/net/ssh/transport/hmac.rb +++ b/lib/net/ssh/transport/hmac.rb @@ -7,6 +7,8 @@ require 'net/ssh/transport/hmac/sha2_256' require 'net/ssh/transport/hmac/sha2_256_96' require 'net/ssh/transport/hmac/sha2_512' require 'net/ssh/transport/hmac/sha2_512_96' +require 'net/ssh/transport/hmac/sha2_256_etm' +require 'net/ssh/transport/hmac/sha2_512_etm' require 'net/ssh/transport/hmac/ripemd160' require 'net/ssh/transport/hmac/none' @@ -15,17 +17,19 @@ require 'net/ssh/transport/hmac/none' module Net::SSH::Transport::HMAC # The mapping of SSH hmac algorithms to their implementations MAP = { - 'hmac-md5' => MD5, - 'hmac-md5-96' => MD5_96, - 'hmac-sha1' => SHA1, - 'hmac-sha1-96' => SHA1_96, - 'hmac-sha2-256' => SHA2_256, - 'hmac-sha2-256-96' => SHA2_256_96, - 'hmac-sha2-512' => SHA2_512, - 'hmac-sha2-512-96' => SHA2_512_96, - 'hmac-ripemd160' => RIPEMD160, - 'hmac-ripemd160@openssh.com' => RIPEMD160, - 'none' => None + 'hmac-md5' => MD5, + 'hmac-md5-96' => MD5_96, + 'hmac-sha1' => SHA1, + 'hmac-sha1-96' => SHA1_96, + 'hmac-sha2-256' => SHA2_256, + 'hmac-sha2-256-96' => SHA2_256_96, + 'hmac-sha2-512' => SHA2_512, + 'hmac-sha2-512-96' => SHA2_512_96, + 'hmac-sha2-256-etm@openssh.com' => SHA2_256_Etm, + 'hmac-sha2-512-etm@openssh.com' => SHA2_512_Etm, + 'hmac-ripemd160' => RIPEMD160, + 'hmac-ripemd160@openssh.com' => RIPEMD160, + 'none' => None } # Retrieves a new hmac instance of the given SSH type (+name+). If +key+ is diff --git a/lib/net/ssh/transport/hmac/abstract.rb b/lib/net/ssh/transport/hmac/abstract.rb index 22ad9a6..f8efa3e 100644 --- a/lib/net/ssh/transport/hmac/abstract.rb +++ b/lib/net/ssh/transport/hmac/abstract.rb @@ -9,6 +9,18 @@ module Net # The base class of all OpenSSL-based HMAC algorithm wrappers. class Abstract class <<self + def etm(*v) + @etm = false if !defined?(@etm) + if v.empty? + @etm = superclass.etm if @etm.nil? && superclass.respond_to?(:etm) + return @etm + elsif v.length == 1 + @etm = v.first + else + raise ArgumentError, "wrong number of arguments (#{v.length} for 1)" + end + end + def key_length(*v) @key_length = nil if !defined?(@key_length) if v.empty? @@ -46,6 +58,10 @@ module Net end end + def etm + self.class.etm + end + def key_length self.class.key_length end diff --git a/lib/net/ssh/transport/hmac/sha2_256_etm.rb b/lib/net/ssh/transport/hmac/sha2_256_etm.rb new file mode 100644 index 0000000..d7ac12b --- /dev/null +++ b/lib/net/ssh/transport/hmac/sha2_256_etm.rb @@ -0,0 +1,12 @@ +require 'net/ssh/transport/hmac/abstract' + +module Net::SSH::Transport::HMAC + # The SHA-256 Encrypt-Then-Mac HMAC algorithm. This has a mac and + # key length of 32, and uses the SHA-256 digest algorithm. + class SHA2_256_Etm < Abstract + etm true + mac_length 32 + key_length 32 + digest_class OpenSSL::Digest::SHA256 + end +end diff --git a/lib/net/ssh/transport/hmac/sha2_512_etm.rb b/lib/net/ssh/transport/hmac/sha2_512_etm.rb new file mode 100644 index 0000000..8b7af6d --- /dev/null +++ b/lib/net/ssh/transport/hmac/sha2_512_etm.rb @@ -0,0 +1,12 @@ +require 'net/ssh/transport/hmac/abstract' + +module Net::SSH::Transport::HMAC + # The SHA-512 Encrypt-Then-Mac HMAC algorithm. This has a mac and + # key length of 64, and uses the SHA-512 digest algorithm. + class SHA2_512_Etm < Abstract + etm true + mac_length 64 + key_length 64 + digest_class OpenSSL::Digest::SHA512 + end +end diff --git a/lib/net/ssh/transport/packet_stream.rb b/lib/net/ssh/transport/packet_stream.rb index a290316..65b55f2 100644 --- a/lib/net/ssh/transport/packet_stream.rb +++ b/lib/net/ssh/transport/packet_stream.rb @@ -130,7 +130,7 @@ module Net payload = client.compress(payload) # the length of the packet, minus the padding - actual_length = 4 + payload.bytesize + 1 + actual_length = (client.hmac.etm ? 0 : 4) + payload.bytesize + 1 # compute the padding length padding_length = client.block_size - (actual_length % client.block_size) @@ -146,11 +146,32 @@ module Net padding = Array.new(padding_length) { rand(256) }.pack("C*") - unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*") - mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*")) + if client.hmac.etm + debug { "using encrypt-then-mac" } - encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher - message = encrypted_data + mac + # Encrypt padding_length, payload, and padding. Take MAC + # from the unencrypted packet_lenght and the encrypted + # data. + length_data = [packet_length].pack("N") + + unencrypted_data = [padding_length, payload, padding].pack("CA*A*") + + encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher + + mac_data = length_data + encrypted_data + + mac = client.hmac.digest([client.sequence_number, mac_data].pack("NA*")) + + message = mac_data + mac + else + unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*") + + mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*")) + + encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher + + message = encrypted_data + mac + end debug { "queueing packet nr #{client.sequence_number} type #{payload.getbyte(0)} len #{packet_length}" } enqueue(message) @@ -196,17 +217,25 @@ module Net # algorithms specified in the server state object, and returned as a # new Packet object. def poll_next_packet + aad_length = server.hmac.etm ? 4 : 0 + if @packet.nil? minimum = server.block_size < 4 ? 4 : server.block_size return nil if available < minimum - data = read_available(minimum) + data = read_available(minimum + aad_length) # decipher it - @packet = Net::SSH::Buffer.new(server.update_cipher(data)) - @packet_length = @packet.read_long + if server.hmac.etm + @packet_length = data.unpack("N").first + @mac_data = data + @packet = Net::SSH::Buffer.new(server.update_cipher(data[aad_length..-1])) + else + @packet = Net::SSH::Buffer.new(server.update_cipher(data)) + @packet_length = @packet.read_long + end end - need = @packet_length + 4 - server.block_size + need = @packet_length + 4 - aad_length - server.block_size raise Net::SSH::Exception, "padding error, need #{need} block #{server.block_size}" if need % server.block_size != 0 return nil if available < need + server.hmac.mac_length @@ -214,6 +243,7 @@ module Net if need > 0 # read the remainder of the packet and decrypt it. data = read_available(need) + @mac_data += data if server.hmac.etm @packet.append(server.update_cipher(data)) end @@ -226,7 +256,11 @@ module Net payload = @packet.read(@packet_length - padding_length - 1) - my_computed_hmac = server.hmac.digest([server.sequence_number, @packet.content].pack("NA*")) + my_computed_hmac = if server.hmac.etm + server.hmac.digest([server.sequence_number, @mac_data].pack("NA*")) + else + server.hmac.digest([server.sequence_number, @packet.content].pack("NA*")) + end raise Net::SSH::Exception, "corrupted hmac detected" if real_hmac != my_computed_hmac # try to decompress the payload, in case compression is active |