summaryrefslogtreecommitdiff
path: root/lib/net/ssh
diff options
context:
space:
mode:
Diffstat (limited to 'lib/net/ssh')
-rw-r--r--lib/net/ssh/authentication/key_manager.rb2
-rw-r--r--lib/net/ssh/authentication/session.rb16
-rw-r--r--lib/net/ssh/buffer.rb14
-rw-r--r--lib/net/ssh/key_factory.rb9
-rw-r--r--lib/net/ssh/known_hosts.rb14
-rw-r--r--lib/net/ssh/ruby_compat.rb8
-rw-r--r--lib/net/ssh/transport/algorithms.rb23
-rw-r--r--lib/net/ssh/transport/cipher_factory.rb37
-rw-r--r--lib/net/ssh/transport/constants.rb4
-rw-r--r--lib/net/ssh/transport/ctr.rb95
-rw-r--r--lib/net/ssh/transport/hmac.rb13
-rw-r--r--lib/net/ssh/transport/hmac/ripemd160.rb13
-rw-r--r--lib/net/ssh/transport/kex.rb11
-rw-r--r--lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb44
-rw-r--r--lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb14
-rw-r--r--lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb93
-rw-r--r--lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb13
-rw-r--r--lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb13
-rw-r--r--lib/net/ssh/transport/openssl.rb112
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