summaryrefslogtreecommitdiff
path: root/lib/net/ssh/transport
diff options
context:
space:
mode:
authorHans de Graaff <hans@degraaff.org>2019-09-13 09:56:21 +0200
committerHans de Graaff <hans@degraaff.org>2019-09-17 07:10:13 +0200
commit1dd04c3a1023eaa72f824d85829bc251279bb64d (patch)
tree42eb0f1d833c92513a397cbc80f529d7ab5efaff /lib/net/ssh/transport
parent1dd787785fe84e08326c0796685466491c66911f (diff)
downloadnet-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.rb3
-rw-r--r--lib/net/ssh/transport/hmac.rb26
-rw-r--r--lib/net/ssh/transport/hmac/abstract.rb16
-rw-r--r--lib/net/ssh/transport/hmac/sha2_256_etm.rb12
-rw-r--r--lib/net/ssh/transport/hmac/sha2_512_etm.rb12
-rw-r--r--lib/net/ssh/transport/packet_stream.rb54
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