Skip to content

Commit 8102fc2

Browse files
committed
add support for ECDH SHA2 NIST key exchanges
1 parent 88ef307 commit 8102fc2

File tree

7 files changed

+152
-0
lines changed

7 files changed

+152
-0
lines changed

lib/net/ssh/transport/algorithms.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,12 @@ class Algorithms
3434
:language => %w()
3535
}
3636

37+
if defined?(OpenSSL::PKey::EC)
38+
ALGORITHMS[:kex] += %w(ecdh-sha2-nistp256
39+
ecdh-sha2-nistp384
40+
ecdh-sha2-nistp521)
41+
end
42+
3743
# The underlying transport layer session that supports this object
3844
attr_reader :session
3945

lib/net/ssh/transport/constants.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,5 +27,7 @@ module Constants
2727
KEXDH_INIT = 30
2828
KEXDH_REPLY = 31
2929

30+
KEXECDH_INIT = 30
31+
KEXECDH_REPLY = 31
3032
end
3133
end; end; end

lib/net/ssh/transport/kex.rb

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,14 @@ module Kex
1010
'diffie-hellman-group-exchange-sha1' => DiffieHellmanGroupExchangeSHA1,
1111
'diffie-hellman-group1-sha1' => DiffieHellmanGroup1SHA1
1212
}
13+
if defined?(OpenSSL::PKey::EC)
14+
require 'net/ssh/transport/kex/ecdh_sha2_nistp256'
15+
require 'net/ssh/transport/kex/ecdh_sha2_nistp384'
16+
require 'net/ssh/transport/kex/ecdh_sha2_nistp521'
17+
18+
MAP['ecdh-sha2-nistp256'] = EcdhSHA2NistP256
19+
MAP['ecdh-sha2-nistp384'] = EcdhSHA2NistP384
20+
MAP['ecdh-sha2-nistp521'] = EcdhSHA2NistP521
21+
end
1322
end
1423
end
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# -*- coding: binary -*-
2+
require 'net/ssh/transport/constants'
3+
require 'net/ssh/transport/kex/diffie_hellman_group1_sha1'
4+
5+
module Net; module SSH; module Transport; module Kex
6+
7+
# A key-exchange service implementing the "ecdh-sha2-nistp256"
8+
# key-exchange algorithm. (defined in RFC 5656)
9+
class EcdhSHA2NistP256 < DiffieHellmanGroup1SHA1
10+
include Constants, Loggable
11+
12+
attr_reader :ecdh
13+
14+
def digester
15+
OpenSSL::Digest::SHA256
16+
end
17+
18+
def curve_name
19+
OpenSSL::PKey::EC::CurveNameAlias['nistp256']
20+
end
21+
22+
def initialize(algorithms, connection, data)
23+
@algorithms = algorithms
24+
@connection = connection
25+
26+
@digester = digester
27+
@data = data.dup
28+
@ecdh = generate_key
29+
@logger = @data.delete(:logger)
30+
end
31+
32+
private
33+
34+
def get_message_types
35+
[KEXECDH_INIT, KEXECDH_REPLY]
36+
end
37+
38+
def build_signature_buffer(result)
39+
response = Net::SSH::Buffer.new
40+
response.write_string data[:client_version_string],
41+
data[:server_version_string],
42+
data[:client_algorithm_packet],
43+
data[:server_algorithm_packet],
44+
result[:key_blob],
45+
ecdh.public_key.to_bn.to_s(2),
46+
result[:server_ecdh_pubkey]
47+
response.write_bignum result[:shared_secret]
48+
response
49+
end
50+
51+
def generate_key #:nodoc:
52+
OpenSSL::PKey::EC.new(curve_name).generate_key
53+
end
54+
55+
def send_kexinit #:nodoc:
56+
init, reply = get_message_types
57+
58+
# send the KEXECDH_INIT message
59+
## byte SSH_MSG_KEX_ECDH_INIT
60+
## string Q_C, client's ephemeral public key octet string
61+
buffer = Net::SSH::Buffer.from(:byte, init, :string, ecdh.public_key.to_bn.to_s(2))
62+
connection.send_message(buffer)
63+
64+
# expect the following KEXECDH_REPLY message
65+
## byte SSH_MSG_KEX_ECDH_REPLY
66+
## string K_S, server's public host key
67+
## string Q_S, server's ephemeral public key octet string
68+
## string the signature on the exchange hash
69+
buffer = connection.next_message
70+
raise Net::SSH::Exception, "expected REPLY" unless buffer.type == reply
71+
72+
result = Hash.new
73+
result[:key_blob] = buffer.read_string
74+
result[:server_key] = Net::SSH::Buffer.new(result[:key_blob]).read_key
75+
result[:server_ecdh_pubkey] = buffer.read_string
76+
77+
# compute shared secret from server's public key and client's private key
78+
pk = OpenSSL::PKey::EC::Point.new(OpenSSL::PKey::EC.new(curve_name).group,
79+
OpenSSL::BN.new(result[:server_ecdh_pubkey], 2))
80+
result[:shared_secret] = OpenSSL::BN.new(ecdh.dh_compute_key(pk), 2)
81+
82+
sig_buffer = Net::SSH::Buffer.new(buffer.read_string)
83+
sig_type = sig_buffer.read_string
84+
if sig_type != algorithms.host_key
85+
raise Net::SSH::Exception,
86+
"host key algorithm mismatch for signature " +
87+
"'#{sig_type}' != '#{algorithms.host_key}'"
88+
end
89+
result[:server_sig] = sig_buffer.read_string
90+
91+
return result
92+
end
93+
end
94+
end; end; end; end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# -*- coding: binary -*-
2+
module Net; module SSH; module Transport; module Kex
3+
4+
# A key-exchange service implementing the "ecdh-sha2-nistp384"
5+
# key-exchange algorithm. (defined in RFC 5656)
6+
class EcdhSHA2NistP384 < EcdhSHA2NistP256
7+
def digester
8+
OpenSSL::Digest::SHA384
9+
end
10+
def curve_name
11+
OpenSSL::PKey::EC::CurveNameAlias['nistp384']
12+
end
13+
end
14+
end; end; end; end
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
# -*- coding: binary -*-
2+
module Net; module SSH; module Transport; module Kex
3+
4+
# A key-exchange service implementing the "ecdh-sha2-nistp521"
5+
# key-exchange algorithm. (defined in RFC 5656)
6+
class EcdhSHA2NistP521 < EcdhSHA2NistP256
7+
def digester
8+
OpenSSL::Digest::SHA512
9+
end
10+
def curve_name
11+
OpenSSL::PKey::EC::CurveNameAlias['nistp521']
12+
end
13+
end
14+
end; end; end; end

lib/net/ssh/transport/openssl.rb

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,19 @@ def ssh_do_sign(data)
124124
end
125125
end
126126

127+
if defined?(OpenSSL::PKey::EC)
128+
# This class is originally defined in the OpenSSL module. As needed, methods
129+
# have been added to it by the Net::SSH module for convenience in dealing
130+
# with SSH funcationality.
131+
class EC
132+
CurveNameAlias = {
133+
"nistp256" => "prime256v1",
134+
"nistp384" => "secp384r1",
135+
"nistp521" => "secp521r1",
136+
}
137+
end
138+
end
139+
127140
end
128141

129142
end

0 commit comments

Comments
 (0)