diff options
Diffstat (limited to 'lib/net/ssh')
-rw-r--r-- | lib/net/ssh/authentication/key_manager.rb | 2 | ||||
-rw-r--r-- | lib/net/ssh/authentication/session.rb | 16 | ||||
-rw-r--r-- | lib/net/ssh/buffer.rb | 14 | ||||
-rw-r--r-- | lib/net/ssh/key_factory.rb | 9 | ||||
-rw-r--r-- | lib/net/ssh/known_hosts.rb | 14 | ||||
-rw-r--r-- | lib/net/ssh/ruby_compat.rb | 8 | ||||
-rw-r--r-- | lib/net/ssh/transport/algorithms.rb | 23 | ||||
-rw-r--r-- | lib/net/ssh/transport/cipher_factory.rb | 37 | ||||
-rw-r--r-- | lib/net/ssh/transport/constants.rb | 4 | ||||
-rw-r--r-- | lib/net/ssh/transport/ctr.rb | 95 | ||||
-rw-r--r-- | lib/net/ssh/transport/hmac.rb | 13 | ||||
-rw-r--r-- | lib/net/ssh/transport/hmac/ripemd160.rb | 13 | ||||
-rw-r--r-- | lib/net/ssh/transport/kex.rb | 11 | ||||
-rw-r--r-- | lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb | 44 | ||||
-rw-r--r-- | lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb | 14 | ||||
-rw-r--r-- | lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb | 93 | ||||
-rw-r--r-- | lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb | 13 | ||||
-rw-r--r-- | lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb | 13 | ||||
-rw-r--r-- | lib/net/ssh/transport/openssl.rb | 112 |
19 files changed, 521 insertions, 27 deletions
diff --git a/lib/net/ssh/authentication/key_manager.rb b/lib/net/ssh/authentication/key_manager.rb index 6c1704e..80bff95 100644 --- a/lib/net/ssh/authentication/key_manager.rb +++ b/lib/net/ssh/authentication/key_manager.rb @@ -222,7 +222,7 @@ module Net identity end - rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError => e + rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError, OpenSSL::PKey::ECError => e if ask_passphrase process_identity_loading_error(identity, e) nil diff --git a/lib/net/ssh/authentication/session.rb b/lib/net/ssh/authentication/session.rb index 4470650..f3f18e7 100644 --- a/lib/net/ssh/authentication/session.rb +++ b/lib/net/ssh/authentication/session.rb @@ -128,13 +128,21 @@ module Net; module SSH; module Authentication private + # Returns an array of paths to the key files usually defined + # by system default. + def default_keys + if defined?(OpenSSL::PKey::EC) + %w(~/.ssh/id_dsa ~/.ssh/id_rsa ~/.ssh/id_ecdsa + ~/.ssh2/id_dsa ~/.ssh2/id_rsa ~/.ssh2/id_ecdsa) + else + %w(~/.ssh/id_dsa ~/.ssh/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_rsa) + end + end + # Returns an array of paths to the key files that should be used when # attempting any key-based authentication mechanism. def keys - Array( - options[:keys] || - %w(~/.ssh/id_dsa ~/.ssh/id_rsa ~/.ssh2/id_dsa ~/.ssh2/id_rsa) - ) + Array(options[:keys] || default_keys) end # Returns an array of the key data that should be used when diff --git a/lib/net/ssh/buffer.rb b/lib/net/ssh/buffer.rb index 51e1c4e..dae29be 100644 --- a/lib/net/ssh/buffer.rb +++ b/lib/net/ssh/buffer.rb @@ -240,7 +240,7 @@ module Net; module SSH end # Read a keyblob of the given type from the buffer, and return it as - # a key. Only RSA and DSA keys are supported. + # a key. Only RSA, DSA, and ECDSA keys are supported. def read_keyblob(type) case type when "ssh-dss" @@ -255,6 +255,16 @@ module Net; module SSH key.e = read_bignum key.n = read_bignum + when /^ecdsa\-sha2\-(\w*)$/ + unless defined?(OpenSSL::PKey::EC) + raise NotImplementedError, "unsupported key type `#{type}'" + else + begin + key = OpenSSL::PKey::EC.read_keyblob($1, self) + rescue OpenSSL::PKey::ECError => e + raise NotImplementedError, "unsupported key type `#{type}'" + end + end else raise NotImplementedError, "unsupported key type `#{type}'" end @@ -337,4 +347,4 @@ module Net; module SSH self end end -end; end;
\ No newline at end of file +end; end; diff --git a/lib/net/ssh/key_factory.rb b/lib/net/ssh/key_factory.rb index d9d48d0..2072188 100644 --- a/lib/net/ssh/key_factory.rb +++ b/lib/net/ssh/key_factory.rb @@ -17,8 +17,11 @@ module Net; module SSH MAP = { "dh" => OpenSSL::PKey::DH, "rsa" => OpenSSL::PKey::RSA, - "dsa" => OpenSSL::PKey::DSA + "dsa" => OpenSSL::PKey::DSA, } + if defined?(OpenSSL::PKey::EC) + MAP["ecdsa"] = OpenSSL::PKey::EC + end class <<self include Prompt @@ -49,6 +52,8 @@ module Net; module SSH key_type = OpenSSL::PKey::DSA elsif data.match(/-----BEGIN RSA PRIVATE KEY-----/) key_type = OpenSSL::PKey::RSA + elsif data.match(/-----BEGIN EC PRIVATE KEY-----/) && defined?(OpenSSL::PKey::EC) + key_type = OpenSSL::PKey::EC elsif data.match(/-----BEGIN (.*) PRIVATE KEY-----/) raise OpenSSL::PKey::PKeyError, "not a supported key type '#{$1}'" else @@ -60,7 +65,7 @@ module Net; module SSH begin return key_type.new(data, passphrase || 'invalid') - rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError => e + rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError, OpenSSL::PKey::ECError => e if encrypted_key && ask_passphrase tries += 1 if tries <= 3 diff --git a/lib/net/ssh/known_hosts.rb b/lib/net/ssh/known_hosts.rb index 7b153d4..7cbbdaf 100644 --- a/lib/net/ssh/known_hosts.rb +++ b/lib/net/ssh/known_hosts.rb @@ -11,6 +11,16 @@ module Net; module SSH # by consumers of the library. class KnownHosts class <<self + + if defined?(OpenSSL::PKey::EC) + SUPPORTED_TYPE = %w(ssh-rsa ssh-dss + ecdsa-sha2-nistp256 + ecdsa-sha2-nistp384 + ecdsa-sha2-nistp521) + else + SUPPORTED_TYPE = %w(ssh-rsa ssh-dss) + end + # Searches all known host files (see KnownHosts.hostfiles) for all keys # of the given host. Returns an array of keys found. def search_for(host, options={}) @@ -104,7 +114,7 @@ module Net; module SSH scanner.skip(/\s*/) type = scanner.scan(/\S+/) - next unless %w(ssh-rsa ssh-dss).include?(type) + next unless SUPPORTED_TYPE.include?(type) scanner.skip(/\s*/) blob = scanner.rest.unpack("m*").first @@ -126,4 +136,4 @@ module Net; module SSH end end -end; end
\ No newline at end of file +end; end diff --git a/lib/net/ssh/ruby_compat.rb b/lib/net/ssh/ruby_compat.rb index 8a1c8f2..cd66c58 100644 --- a/lib/net/ssh/ruby_compat.rb +++ b/lib/net/ssh/ruby_compat.rb @@ -5,6 +5,14 @@ class String def getbyte(index) self[index] end + def setbyte(index, c) + self[index] = c + end + end + if RUBY_VERSION < "1.8.7" + def bytesize + self.size + end end end diff --git a/lib/net/ssh/transport/algorithms.rb b/lib/net/ssh/transport/algorithms.rb index 1c0c8ce..de084ff 100644 --- a/lib/net/ssh/transport/algorithms.rb +++ b/lib/net/ssh/transport/algorithms.rb @@ -25,16 +25,37 @@ module Net; module SSH; module Transport :host_key => %w(ssh-rsa ssh-dss), :kex => %w(diffie-hellman-group-exchange-sha1 diffie-hellman-group1-sha1 + diffie-hellman-group14-sha1 diffie-hellman-group-exchange-sha256), :encryption => %w(aes128-cbc 3des-cbc blowfish-cbc cast128-cbc aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se - idea-cbc none arcfour128 arcfour256), + idea-cbc none arcfour128 arcfour256 arcfour + aes128-ctr aes192-ctr aes256-ctr + camellia128-cbc camellia192-cbc camellia256-cbc + camellia128-cbc@openssh.org + camellia192-cbc@openssh.org + camellia256-cbc@openssh.org + camellia128-ctr camellia192-ctr camellia256-ctr + camellia128-ctr@openssh.org + camellia192-ctr@openssh.org + camellia256-ctr@openssh.org + cast128-ctr blowfish-ctr 3des-ctr + ), :hmac => %w(hmac-sha1 hmac-md5 hmac-sha1-96 hmac-md5-96 + hmac-ripemd160 hmac-ripemd160@openssh.com hmac-sha2-256 hmac-sha2-512 hmac-sha2-256-96 hmac-sha2-512-96 none), :compression => %w(none zlib@openssh.com zlib), :language => %w() } + if defined?(OpenSSL::PKey::EC) + ALGORITHMS[:host_key] += %w(ecdsa-sha2-nistp256 + ecdsa-sha2-nistp384 + ecdsa-sha2-nistp521) + ALGORITHMS[:kex] += %w(ecdh-sha2-nistp256 + ecdh-sha2-nistp384 + ecdh-sha2-nistp521) + end # The underlying transport layer session that supports this object attr_reader :session diff --git a/lib/net/ssh/transport/cipher_factory.rb b/lib/net/ssh/transport/cipher_factory.rb index 4a07aa2..4d9ab45 100644 --- a/lib/net/ssh/transport/cipher_factory.rb +++ b/lib/net/ssh/transport/cipher_factory.rb @@ -1,4 +1,5 @@ require 'openssl' +require 'net/ssh/transport/ctr.rb' require 'net/ssh/transport/key_expander' require 'net/ssh/transport/identity_cipher' @@ -19,9 +20,30 @@ module Net; module SSH; module Transport "arcfour128" => "rc4", "arcfour256" => "rc4", "arcfour512" => "rc4", - "none" => "none" + "arcfour" => "rc4", + "camellia128-cbc" => "camellia-128-cbc", + "camellia192-cbc" => "camellia-192-cbc", + "camellia256-cbc" => "camellia-256-cbc", + "camellia128-cbc@openssh.org" => "camellia-128-cbc", + "camellia192-cbc@openssh.org" => "camellia-192-cbc", + "camellia256-cbc@openssh.org" => "camellia-256-cbc", + + "3des-ctr" => "des-ede3", + "blowfish-ctr" => "bf-ecb", + "aes256-ctr" => "aes-256-ecb", + "aes192-ctr" => "aes-192-ecb", + "aes128-ctr" => "aes-128-ecb", + "cast128-ctr" => "cast5-ecb", + "camellia128-ctr" => "camellia-128-ecb", + "camellia192-ctr" => "camellia-192-ecb", + "camellia256-ctr" => "camellia-256-ecb", + "camellia128-ctr@openssh.org" => "camellia-128-ecb", + "camellia192-ctr@openssh.org" => "camellia-192-ecb", + "camellia256-ctr@openssh.org" => "camellia-256-ecb", + + "none" => "none", } - + # Ruby's OpenSSL bindings always return a key length of 16 for RC4 ciphers # resulting in the error: OpenSSL::CipherError: key length too short. # The following ciphers will override this key length. @@ -29,7 +51,8 @@ module Net; module SSH; module Transport "arcfour256" => 32, "arcfour512" => 64 } - + + # Returns true if the underlying OpenSSL library supports the given cipher, # and false otherwise. def self.supported?(name) @@ -46,16 +69,20 @@ module Net; module SSH; module Transport def self.get(name, options={}) ossl_name = SSH_TO_OSSL[name] or raise NotImplementedError, "unimplemented cipher `#{name}'" return IdentityCipher if ossl_name == "none" - cipher = OpenSSL::Cipher::Cipher.new(ossl_name) + cipher.send(options[:encrypt] ? :encrypt : :decrypt) cipher.padding = 0 + + cipher.extend(Net::SSH::Transport::CTR) if (name =~ /-ctr(@openssh.org)?$/) + cipher.iv = Net::SSH::Transport::KeyExpander.expand_key(cipher.iv_len, options[:iv], options) if ossl_name != "rc4" + key_len = KEY_LEN_OVERRIDE[name] || cipher.key_len cipher.key_len = key_len cipher.key = Net::SSH::Transport::KeyExpander.expand_key(key_len, options[:key], options) - cipher.update(" " * 1536) if ossl_name == "rc4" + cipher.update(" " * 1536) if (ossl_name == "rc4" && name != "arcfour") return cipher end diff --git a/lib/net/ssh/transport/constants.rb b/lib/net/ssh/transport/constants.rb index c87ce4c..0a20430 100644 --- a/lib/net/ssh/transport/constants.rb +++ b/lib/net/ssh/transport/constants.rb @@ -26,5 +26,7 @@ module Net; module SSH; module Transport KEXDH_INIT = 30 KEXDH_REPLY = 31 + KEXECDH_INIT = 30 + KEXECDH_REPLY = 31 end -end; end; end
\ No newline at end of file +end; end; end diff --git a/lib/net/ssh/transport/ctr.rb b/lib/net/ssh/transport/ctr.rb new file mode 100644 index 0000000..2b5c9c9 --- /dev/null +++ b/lib/net/ssh/transport/ctr.rb @@ -0,0 +1,95 @@ +require 'openssl' + +module Net::SSH::Transport + + # Pure-Ruby implementation of Stateful Decryption Counter(SDCTR) Mode + # for Block Ciphers. See RFC4344 for detail. + module CTR + def self.extended(orig) + orig.instance_eval { + @remaining = "" + @counter = nil + @counter_len = orig.block_size + orig.encrypt + orig.padding = 0 + } + + class <<orig + alias :_update :update + private :_update + undef :update + + def iv + @counter + end + + def iv_len + block_size + end + + def iv=(iv_s) + @counter = iv_s if @counter.nil? + end + + def encrypt + # DO NOTHING (always set to "encrypt") + end + + def decrypt + # DO NOTHING (always set to "encrypt") + end + + def padding=(pad) + # DO NOTHING (always 0) + end + + def reset + @remaining = "" + end + + def update(data) + @remaining += data + + encrypted = "" + + while @remaining.bytesize >= block_size + encrypted += xor!(@remaining.slice!(0, block_size), + _update(@counter)) + increment_counter! + end + + encrypted + end + + def final + unless @remaining.empty? + s = xor!(@remaining, _update(@counter)) + else + s = "" + end + + @remaining = "" + + s + end + + private + + def xor!(s1, s2) + s = [] + s1.unpack('Q*').zip(s2.unpack('Q*')) {|a,b| s.push(a^b) } + s.pack('Q*') + end + + def increment_counter! + c = @counter_len + while ((c -= 1) > 0) + if @counter.setbyte(c, (@counter.getbyte(c) + 1) & 0xff) != 0 + break + end + end + end + end + end + end +end diff --git a/lib/net/ssh/transport/hmac.rb b/lib/net/ssh/transport/hmac.rb index 8452046..af929f7 100644 --- a/lib/net/ssh/transport/hmac.rb +++ b/lib/net/ssh/transport/hmac.rb @@ -7,6 +7,7 @@ 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/ripemd160' require 'net/ssh/transport/hmac/none' # Implements a simple factory interface for fetching hmac implementations, or @@ -14,11 +15,13 @@ 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, - 'none' => None + 'hmac-md5' => MD5, + 'hmac-md5-96' => MD5_96, + 'hmac-sha1' => SHA1, + 'hmac-sha1-96' => SHA1_96, + 'hmac-ripemd160' => RIPEMD160, + 'hmac-ripemd160@openssh.com' => RIPEMD160, + 'none' => None } # add mapping to sha2 hmac algorithms if they're available diff --git a/lib/net/ssh/transport/hmac/ripemd160.rb b/lib/net/ssh/transport/hmac/ripemd160.rb new file mode 100644 index 0000000..a77e4cd --- /dev/null +++ b/lib/net/ssh/transport/hmac/ripemd160.rb @@ -0,0 +1,13 @@ +require 'net/ssh/transport/hmac/abstract' + +module Net::SSH::Transport::HMAC + + # The RIPEMD-160 HMAC algorithm. This has a mac and key length of 20, and + # uses the RIPEMD-160 digest algorithm. + class RIPEMD160 < Abstract + mac_length 20 + key_length 20 + digest_class OpenSSL::Digest::RIPEMD160 + end + +end diff --git a/lib/net/ssh/transport/kex.rb b/lib/net/ssh/transport/kex.rb index 79a46a1..1cd059a 100644 --- a/lib/net/ssh/transport/kex.rb +++ b/lib/net/ssh/transport/kex.rb @@ -1,4 +1,5 @@ require 'net/ssh/transport/kex/diffie_hellman_group1_sha1' +require 'net/ssh/transport/kex/diffie_hellman_group14_sha1' require 'net/ssh/transport/kex/diffie_hellman_group_exchange_sha1' require 'net/ssh/transport/kex/diffie_hellman_group_exchange_sha256' @@ -9,9 +10,19 @@ module Net::SSH::Transport MAP = { 'diffie-hellman-group-exchange-sha1' => DiffieHellmanGroupExchangeSHA1, 'diffie-hellman-group1-sha1' => DiffieHellmanGroup1SHA1, + 'diffie-hellman-group14-sha1' => DiffieHellmanGroup14SHA1, } if defined?(DiffieHellmanGroupExchangeSHA256) MAP['diffie-hellman-group-exchange-sha256'] = DiffieHellmanGroupExchangeSHA256 end + if defined?(OpenSSL::PKey::EC) + require 'net/ssh/transport/kex/ecdh_sha2_nistp256' + require 'net/ssh/transport/kex/ecdh_sha2_nistp384' + require 'net/ssh/transport/kex/ecdh_sha2_nistp521' + + MAP['ecdh-sha2-nistp256'] = EcdhSHA2NistP256 + MAP['ecdh-sha2-nistp384'] = EcdhSHA2NistP384 + MAP['ecdh-sha2-nistp521'] = EcdhSHA2NistP521 + end end end diff --git a/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb b/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb new file mode 100644 index 0000000..28a8553 --- /dev/null +++ b/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb @@ -0,0 +1,44 @@ +require 'net/ssh/transport/kex/diffie_hellman_group1_sha1' + +module Net; module SSH; module Transport; module Kex + + # A key-exchange service implementing the "diffie-hellman-group14-sha1" + # key-exchange algorithm. (defined in RFC 4253) + class DiffieHellmanGroup14SHA1 < DiffieHellmanGroup1SHA1 + include Constants, Loggable + + # The value of 'P', as a string, in hexadecimal + P_s = "FFFFFFFF" "FFFFFFFF" "C90FDAA2" "2168C234" + + "C4C6628B" "80DC1CD1" "29024E08" "8A67CC74" + + "020BBEA6" "3B139B22" "514A0879" "8E3404DD" + + "EF9519B3" "CD3A431B" "302B0A6D" "F25F1437" + + "4FE1356D" "6D51C245" "E485B576" "625E7EC6" + + "F44C42E9" "A637ED6B" "0BFF5CB6" "F406B7ED" + + "EE386BFB" "5A899FA5" "AE9F2411" "7C4B1FE6" + + "49286651" "ECE45B3D" "C2007CB8" "A163BF05" + + "98DA4836" "1C55D39A" "69163FA8" "FD24CF5F" + + "83655D23" "DCA3AD96" "1C62F356" "208552BB" + + "9ED52907" "7096966D" "670C354E" "4ABC9804" + + "F1746C08" "CA18217C" "32905E46" "2E36CE3B" + + "E39E772C" "180E8603" "9B2783A2" "EC07A28F" + + "B5C55DF0" "6F4C52C9" "DE2BCBF6" "95581718" + + "3995497C" "EA956AE5" "15D22618" "98FA0510" + + "15728E5A" "8AACAA68" "FFFFFFFF" "FFFFFFFF" + + # The radix in which P_s represents the value of P + P_r = 16 + + # The group constant + G = 2 + + private + + def get_p + OpenSSL::BN.new(P_s, P_r) + end + + def get_g + G + end + end +end; end; end; end diff --git a/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb b/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb index a9875ac..7379e01 100644 --- a/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +++ b/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb @@ -40,8 +40,8 @@ module Net; module SSH; module Transport; module Kex # required by this algorithm, which was acquired during earlier # processing. def initialize(algorithms, connection, data) - @p = OpenSSL::BN.new(P_s, P_r) - @g = G + @p = get_p + @g = get_g @digester = OpenSSL::Digest::SHA1 @algorithms = algorithms @@ -76,7 +76,15 @@ module Net; module SSH; module Transport; module Kex end private - + + def get_p + OpenSSL::BN.new(P_s, P_r) + end + + def get_g + G + end + # Returns the DH key parameters for the current connection. def get_parameters [p, g] diff --git a/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb b/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb new file mode 100644 index 0000000..f860016 --- /dev/null +++ b/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb @@ -0,0 +1,93 @@ +require 'net/ssh/transport/constants' +require 'net/ssh/transport/kex/diffie_hellman_group1_sha1' + +module Net; module SSH; module Transport; module Kex + + # A key-exchange service implementing the "ecdh-sha2-nistp256" + # key-exchange algorithm. (defined in RFC 5656) + class EcdhSHA2NistP256 < DiffieHellmanGroup1SHA1 + include Constants, Loggable + + attr_reader :ecdh + + def digester + OpenSSL::Digest::SHA256 + end + + def curve_name + OpenSSL::PKey::EC::CurveNameAlias['nistp256'] + end + + def initialize(algorithms, connection, data) + @algorithms = algorithms + @connection = connection + + @digester = digester + @data = data.dup + @ecdh = generate_key + @logger = @data.delete(:logger) + end + + private + + def get_message_types + [KEXECDH_INIT, KEXECDH_REPLY] + end + + def build_signature_buffer(result) + response = Net::SSH::Buffer.new + response.write_string data[:client_version_string], + data[:server_version_string], + data[:client_algorithm_packet], + data[:server_algorithm_packet], + result[:key_blob], + ecdh.public_key.to_bn.to_s(2), + result[:server_ecdh_pubkey] + response.write_bignum result[:shared_secret] + response + end + + def generate_key #:nodoc: + OpenSSL::PKey::EC.new(curve_name).generate_key + end + + def send_kexinit #:nodoc: + init, reply = get_message_types + + # send the KEXECDH_INIT message + ## byte SSH_MSG_KEX_ECDH_INIT + ## string Q_C, client's ephemeral public key octet string + buffer = Net::SSH::Buffer.from(:byte, init, :string, ecdh.public_key.to_bn.to_s(2)) + connection.send_message(buffer) + + # expect the following KEXECDH_REPLY message + ## byte SSH_MSG_KEX_ECDH_REPLY + ## string K_S, server's public host key + ## string Q_S, server's ephemeral public key octet string + ## string the signature on the exchange hash + buffer = connection.next_message + raise Net::SSH::Exception, "expected REPLY" unless buffer.type == reply + + result = Hash.new + result[:key_blob] = buffer.read_string + result[:server_key] = Net::SSH::Buffer.new(result[:key_blob]).read_key + result[:server_ecdh_pubkey] = buffer.read_string + + # compute shared secret from server's public key and client's private key + pk = OpenSSL::PKey::EC::Point.new(OpenSSL::PKey::EC.new(curve_name).group, + OpenSSL::BN.new(result[:server_ecdh_pubkey], 2)) + result[:shared_secret] = OpenSSL::BN.new(ecdh.dh_compute_key(pk), 2) + + sig_buffer = Net::SSH::Buffer.new(buffer.read_string) + sig_type = sig_buffer.read_string + if sig_type != algorithms.host_key + raise Net::SSH::Exception, + "host key algorithm mismatch for signature " + + "'#{sig_type}' != '#{algorithms.host_key}'" + end + result[:server_sig] = sig_buffer.read_string + + return result + end + end +end; end; end; end diff --git a/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb b/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb new file mode 100644 index 0000000..b792410 --- /dev/null +++ b/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb @@ -0,0 +1,13 @@ +module Net; module SSH; module Transport; module Kex + + # A key-exchange service implementing the "ecdh-sha2-nistp256" + # key-exchange algorithm. (defined in RFC 5656) + class EcdhSHA2NistP384 < EcdhSHA2NistP256 + def digester + OpenSSL::Digest::SHA384 + end + def curve_name + OpenSSL::PKey::EC::CurveNameAlias['nistp384'] + end + end +end; end; end; end diff --git a/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb b/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb new file mode 100644 index 0000000..6623559 --- /dev/null +++ b/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb @@ -0,0 +1,13 @@ +module Net; module SSH; module Transport; module Kex + + # A key-exchange service implementing the "ecdh-sha2-nistp521" + # key-exchange algorithm. (defined in RFC 5656) + class EcdhSHA2NistP521 < EcdhSHA2NistP256 + def digester + OpenSSL::Digest::SHA512 + end + def curve_name + OpenSSL::PKey::EC::CurveNameAlias['nistp521'] + end + end +end; end; end; end diff --git a/lib/net/ssh/transport/openssl.rb b/lib/net/ssh/transport/openssl.rb index 1289b36..3364b98 100644 --- a/lib/net/ssh/transport/openssl.rb +++ b/lib/net/ssh/transport/openssl.rb @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- require 'openssl' module OpenSSL @@ -122,6 +123,115 @@ module OpenSSL end end - end + if defined?(OpenSSL::PKey::EC) + # This class is originally defined in the OpenSSL module. As needed, methods + # have been added to it by the Net::SSH module for convenience in dealing + # with SSH functionality. + class EC + CurveNameAlias = { + "nistp256" => "prime256v1", + "nistp384" => "secp384r1", + "nistp521" => "secp521r1", + } + + CurveNameAliasInv = { + "prime256v1" => "nistp256", + "secp384r1" => "nistp384", + "secp521r1" => "nistp521", + } + + def self.read_keyblob(curve_name_in_type, buffer) + curve_name_in_key = buffer.read_string + unless curve_name_in_type == curve_name_in_key + raise Net::SSH::Exception, "curve name mismatched (`#{curve_name_in_key}' with `#{curve_name_in_type}')" + end + public_key_oct = buffer.read_string + begin + key = OpenSSL::PKey::EC.new(OpenSSL::PKey::EC::CurveNameAlias[curve_name_in_key]) + group = key.group + point = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(public_key_oct, 2)) + key.public_key = point + + return key + rescue OpenSSL::PKey::ECError => e + raise NotImplementedError, "unsupported key type `#{type}'" + end + + end + + # Returns the description of this key type used by the + # SSH2 protocol, like "ecdsa-sha2-nistp256" + def ssh_type + "ecdsa-sha2-#{CurveNameAliasInv[self.group.curve_name]}" + end + def digester + if self.group.curve_name =~ /^[a-z]+(\d+)\w*\z/ + curve_size = $1.to_i + if curve_size <= 256 + OpenSSL::Digest::SHA256.new + elsif curve_size <= 384 + OpenSSL::Digest::SHA384.new + else + OpenSSL::Digest::SHA512.new + end + else + OpenSSL::Digest::SHA256.new + end + end + private :digester + + # Converts the key to a blob, according to the SSH2 protocol. + def to_blob + @blob ||= Net::SSH::Buffer.from(:string, ssh_type, + :string, CurveNameAliasInv[self.group.curve_name], + :string, self.public_key.to_bn.to_s(2)).to_s + @blob + end + + # Verifies the given signature matches the given data. + def ssh_do_verify(sig, data) + digest = digester.digest(data) + a1sig = nil + + begin + sig_r_len = sig[0,4].unpack("H*")[0].to_i(16) + sig_l_len = sig[4+sig_r_len,4].unpack("H*")[0].to_i(16) + + sig_r = sig[4,sig_r_len].unpack("H*")[0] + sig_s = sig[4+sig_r_len+4,sig_l_len].unpack("H*")[0] + + a1sig = OpenSSL::ASN1::Sequence([ + OpenSSL::ASN1::Integer(sig_r.to_i(16)), + OpenSSL::ASN1::Integer(sig_s.to_i(16)), + ]) + rescue + end + + if a1sig == nil + return false + else + dsa_verify_asn1(digest, a1sig.to_der) + end + end + + # Returns the signature for the given data. + def ssh_do_sign(data) + digest = digester.digest(data) + sig = dsa_sign_asn1(digest) + a1sig = OpenSSL::ASN1.decode( sig ) + + sig_r = a1sig.value[0].value + sig_s = a1sig.value[1].value + + return Net::SSH::Buffer.from(:bignum, sig_r, :bignum, sig_s).to_s + end + end + else + class OpenSSL::PKey::ECError < RuntimeError + # for compatibility with interpreters + # without EC support (i.e. JRuby) + end + end + end end |