net-ssh 5.0.2 → 7.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.dockerignore +6 -0
- data/.github/config/rubocop_linter_action.yml +4 -0
- data/.github/workflows/ci-with-docker.yml +44 -0
- data/.github/workflows/ci.yml +87 -0
- data/.github/workflows/rubocop.yml +13 -0
- data/.gitignore +3 -0
- data/.rubocop.yml +19 -2
- data/.rubocop_todo.yml +623 -511
- data/CHANGES.txt +76 -0
- data/Dockerfile +27 -0
- data/Dockerfile.openssl3 +17 -0
- data/Gemfile +2 -0
- data/Gemfile.noed25519 +2 -0
- data/Manifest +0 -1
- data/README.md +293 -0
- data/Rakefile +6 -2
- data/appveyor.yml +4 -2
- data/docker-compose.yml +23 -0
- data/lib/net/ssh/authentication/agent.rb +36 -14
- data/lib/net/ssh/authentication/certificate.rb +19 -7
- data/lib/net/ssh/authentication/constants.rb +0 -1
- data/lib/net/ssh/authentication/ed25519.rb +83 -50
- data/lib/net/ssh/authentication/ed25519_loader.rb +5 -8
- data/lib/net/ssh/authentication/key_manager.rb +74 -33
- data/lib/net/ssh/authentication/methods/abstract.rb +12 -3
- data/lib/net/ssh/authentication/methods/hostbased.rb +3 -5
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +5 -3
- data/lib/net/ssh/authentication/methods/none.rb +6 -9
- data/lib/net/ssh/authentication/methods/password.rb +2 -3
- data/lib/net/ssh/authentication/methods/publickey.rb +58 -16
- data/lib/net/ssh/authentication/pageant.rb +97 -97
- data/lib/net/ssh/authentication/pub_key_fingerprint.rb +2 -3
- data/lib/net/ssh/authentication/session.rb +27 -23
- data/lib/net/ssh/buffer.rb +91 -40
- data/lib/net/ssh/buffered_io.rb +24 -26
- data/lib/net/ssh/config.rb +99 -53
- data/lib/net/ssh/connection/channel.rb +101 -87
- data/lib/net/ssh/connection/constants.rb +0 -4
- data/lib/net/ssh/connection/event_loop.rb +30 -25
- data/lib/net/ssh/connection/keepalive.rb +12 -12
- data/lib/net/ssh/connection/session.rb +115 -111
- data/lib/net/ssh/connection/term.rb +56 -58
- data/lib/net/ssh/errors.rb +12 -12
- data/lib/net/ssh/key_factory.rb +108 -22
- data/lib/net/ssh/known_hosts.rb +120 -36
- data/lib/net/ssh/loggable.rb +10 -11
- data/lib/net/ssh/packet.rb +1 -1
- data/lib/net/ssh/prompt.rb +9 -11
- data/lib/net/ssh/proxy/command.rb +1 -2
- data/lib/net/ssh/proxy/errors.rb +2 -4
- data/lib/net/ssh/proxy/http.rb +18 -20
- data/lib/net/ssh/proxy/https.rb +8 -10
- data/lib/net/ssh/proxy/jump.rb +8 -10
- data/lib/net/ssh/proxy/socks4.rb +2 -4
- data/lib/net/ssh/proxy/socks5.rb +3 -6
- data/lib/net/ssh/service/forward.rb +9 -8
- data/lib/net/ssh/test/channel.rb +24 -26
- data/lib/net/ssh/test/extensions.rb +37 -35
- data/lib/net/ssh/test/kex.rb +6 -8
- data/lib/net/ssh/test/local_packet.rb +0 -2
- data/lib/net/ssh/test/packet.rb +3 -3
- data/lib/net/ssh/test/remote_packet.rb +6 -8
- data/lib/net/ssh/test/script.rb +25 -27
- data/lib/net/ssh/test/socket.rb +12 -15
- data/lib/net/ssh/test.rb +12 -12
- data/lib/net/ssh/transport/algorithms.rb +177 -118
- data/lib/net/ssh/transport/cipher_factory.rb +34 -50
- data/lib/net/ssh/transport/constants.rb +13 -9
- data/lib/net/ssh/transport/ctr.rb +8 -14
- data/lib/net/ssh/transport/hmac/abstract.rb +20 -5
- data/lib/net/ssh/transport/hmac/md5.rb +0 -2
- data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
- data/lib/net/ssh/transport/hmac/none.rb +0 -2
- data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
- data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
- data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
- data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
- data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
- data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
- data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
- data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac.rb +13 -11
- data/lib/net/ssh/transport/identity_cipher.rb +11 -13
- data/lib/net/ssh/transport/kex/abstract.rb +130 -0
- data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +5 -19
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +30 -139
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +1 -8
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +20 -81
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +5 -4
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +5 -4
- data/lib/net/ssh/transport/kex.rb +15 -10
- data/lib/net/ssh/transport/key_expander.rb +7 -8
- data/lib/net/ssh/transport/openssl.rb +149 -111
- data/lib/net/ssh/transport/packet_stream.rb +53 -22
- data/lib/net/ssh/transport/server_version.rb +17 -16
- data/lib/net/ssh/transport/session.rb +35 -11
- data/lib/net/ssh/transport/state.rb +44 -44
- data/lib/net/ssh/verifiers/accept_new.rb +7 -2
- data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +1 -2
- data/lib/net/ssh/verifiers/always.rb +10 -4
- data/lib/net/ssh/verifiers/never.rb +4 -2
- data/lib/net/ssh/version.rb +2 -2
- data/lib/net/ssh.rb +17 -9
- data/net-ssh-public_cert.pem +18 -19
- data/net-ssh.gemspec +9 -7
- data/support/ssh_tunnel_bug.rb +3 -3
- data.tar.gz.sig +0 -0
- metadata +65 -41
- metadata.gz.sig +0 -0
- data/.travis.yml +0 -52
- data/Gemfile.noed25519.lock +0 -41
- data/README.rdoc +0 -169
- data/lib/net/ssh/ruby_compat.rb +0 -13
- data/support/arcfour_check.rb +0 -20
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
require 'net/ssh/buffer'
|
|
2
|
+
require 'net/ssh/errors'
|
|
3
|
+
require 'net/ssh/loggable'
|
|
4
|
+
require 'net/ssh/transport/openssl'
|
|
5
|
+
require 'net/ssh/transport/constants'
|
|
6
|
+
|
|
7
|
+
module Net
|
|
8
|
+
module SSH
|
|
9
|
+
module Transport
|
|
10
|
+
module Kex
|
|
11
|
+
# Abstract class that implement Diffie-Hellman Key Exchange
|
|
12
|
+
# See https://tools.ietf.org/html/rfc4253#page-21
|
|
13
|
+
class Abstract
|
|
14
|
+
include Loggable
|
|
15
|
+
include Constants
|
|
16
|
+
|
|
17
|
+
attr_reader :algorithms
|
|
18
|
+
attr_reader :connection
|
|
19
|
+
attr_reader :data
|
|
20
|
+
attr_reader :dh
|
|
21
|
+
|
|
22
|
+
# Create a new instance of the Diffie-Hellman Key Exchange algorithm.
|
|
23
|
+
# The Diffie-Hellman (DH) key exchange provides a shared secret that
|
|
24
|
+
# cannot be determined by either party alone. The key exchange is
|
|
25
|
+
# combined with a signature with the host key to provide host
|
|
26
|
+
# authentication.
|
|
27
|
+
def initialize(algorithms, connection, data)
|
|
28
|
+
@algorithms = algorithms
|
|
29
|
+
@connection = connection
|
|
30
|
+
|
|
31
|
+
@data = data.dup
|
|
32
|
+
@dh = generate_key
|
|
33
|
+
@logger = @data.delete(:logger)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Perform the key-exchange for the given session, with the given
|
|
37
|
+
# data. This method will return a hash consisting of the
|
|
38
|
+
# following keys:
|
|
39
|
+
#
|
|
40
|
+
# * :session_id
|
|
41
|
+
# * :server_key
|
|
42
|
+
# * :shared_secret
|
|
43
|
+
# * :hashing_algorithm
|
|
44
|
+
#
|
|
45
|
+
# The caller is expected to be able to understand how to use these
|
|
46
|
+
# deliverables.
|
|
47
|
+
def exchange_keys
|
|
48
|
+
result = send_kexinit
|
|
49
|
+
verify_server_key(result[:server_key])
|
|
50
|
+
session_id = verify_signature(result)
|
|
51
|
+
confirm_newkeys
|
|
52
|
+
|
|
53
|
+
{
|
|
54
|
+
session_id: session_id,
|
|
55
|
+
server_key: result[:server_key],
|
|
56
|
+
shared_secret: result[:shared_secret],
|
|
57
|
+
hashing_algorithm: digester
|
|
58
|
+
}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def digester
|
|
62
|
+
raise NotImplementedError, 'abstract class: digester not implemented'
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def matching?(key_ssh_type, host_key_alg)
|
|
68
|
+
return true if key_ssh_type == host_key_alg
|
|
69
|
+
return true if key_ssh_type == 'ssh-rsa' && ['rsa-sha2-512', 'rsa-sha2-256'].include?(host_key_alg)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Verify that the given key is of the expected type, and that it
|
|
73
|
+
# really is the key for the session's host. Raise Net::SSH::Exception
|
|
74
|
+
# if it is not.
|
|
75
|
+
def verify_server_key(key) # :nodoc:
|
|
76
|
+
unless matching?(key.ssh_type, algorithms.host_key)
|
|
77
|
+
raise Net::SSH::Exception, "host key algorithm mismatch '#{key.ssh_type}' != '#{algorithms.host_key}'"
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
blob, fingerprint = generate_key_fingerprint(key)
|
|
81
|
+
|
|
82
|
+
unless connection.host_key_verifier.verify(key: key, key_blob: blob, fingerprint: fingerprint, session: connection)
|
|
83
|
+
raise Net::SSH::Exception, 'host key verification failed'
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def generate_key_fingerprint(key)
|
|
88
|
+
blob = Net::SSH::Buffer.from(:key, key).to_s
|
|
89
|
+
|
|
90
|
+
fingerprint = Net::SSH::Authentication::PubKeyFingerprint.fingerprint(blob, @connection.options[:fingerprint_hash] || 'SHA256')
|
|
91
|
+
|
|
92
|
+
[blob, fingerprint]
|
|
93
|
+
rescue StandardError => e
|
|
94
|
+
[nil, "(could not generate fingerprint: #{e.message})"]
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Verify the signature that was received. Raise Net::SSH::Exception
|
|
98
|
+
# if the signature could not be verified. Otherwise, return the new
|
|
99
|
+
# session-id.
|
|
100
|
+
def verify_signature(result) # :nodoc:
|
|
101
|
+
response = build_signature_buffer(result)
|
|
102
|
+
|
|
103
|
+
hash = digester.digest(response.to_s)
|
|
104
|
+
|
|
105
|
+
server_key = result[:server_key]
|
|
106
|
+
server_sig = result[:server_sig]
|
|
107
|
+
unless connection.host_key_verifier.verify_signature { server_key.ssh_do_verify(server_sig, hash, host_key: algorithms.host_key) }
|
|
108
|
+
raise Net::SSH::Exception, 'could not verify server signature'
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
hash
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
# Send the NEWKEYS message, and expect the NEWKEYS message in
|
|
115
|
+
# reply.
|
|
116
|
+
def confirm_newkeys # :nodoc:
|
|
117
|
+
# send own NEWKEYS message first (the wodSSHServer won't send first)
|
|
118
|
+
response = Net::SSH::Buffer.new
|
|
119
|
+
response.write_byte(NEWKEYS)
|
|
120
|
+
connection.send_message(response)
|
|
121
|
+
|
|
122
|
+
# wait for the server's NEWKEYS message
|
|
123
|
+
buffer = connection.next_message
|
|
124
|
+
raise Net::SSH::Exception, 'expected NEWKEYS' unless buffer.type == NEWKEYS
|
|
125
|
+
end
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'net/ssh/transport/kex/abstract'
|
|
2
|
+
|
|
3
|
+
module Net
|
|
4
|
+
module SSH
|
|
5
|
+
module Transport
|
|
6
|
+
module Kex
|
|
7
|
+
# Implement key-exchange algorithm from Elliptic Curve Algorithm Integration
|
|
8
|
+
# in the Secure Shell Transport Layer (RFC 5656)
|
|
9
|
+
class Abstract5656 < Abstract
|
|
10
|
+
alias ecdh dh
|
|
11
|
+
|
|
12
|
+
def curve_name
|
|
13
|
+
raise NotImplementedError, 'abstract class: curve_name not implemented'
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def get_message_types
|
|
19
|
+
[KEXECDH_INIT, KEXECDH_REPLY]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def build_signature_buffer(result)
|
|
23
|
+
response = Net::SSH::Buffer.new
|
|
24
|
+
response.write_string data[:client_version_string],
|
|
25
|
+
data[:server_version_string],
|
|
26
|
+
data[:client_algorithm_packet],
|
|
27
|
+
data[:server_algorithm_packet],
|
|
28
|
+
result[:key_blob],
|
|
29
|
+
ecdh_public_key_bytes,
|
|
30
|
+
result[:server_ecdh_pubkey]
|
|
31
|
+
response.write_bignum result[:shared_secret]
|
|
32
|
+
response
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def send_kexinit # :nodoc:
|
|
36
|
+
init, reply = get_message_types
|
|
37
|
+
|
|
38
|
+
# send the KEXECDH_INIT message
|
|
39
|
+
## byte SSH_MSG_KEX_ECDH_INIT
|
|
40
|
+
## string Q_C, client's ephemeral public key octet string
|
|
41
|
+
buffer = Net::SSH::Buffer.from(:byte, init, :mstring, ecdh_public_key_bytes)
|
|
42
|
+
connection.send_message(buffer)
|
|
43
|
+
|
|
44
|
+
# expect the following KEXECDH_REPLY message
|
|
45
|
+
## byte SSH_MSG_KEX_ECDH_REPLY
|
|
46
|
+
## string K_S, server's public host key
|
|
47
|
+
## string Q_S, server's ephemeral public key octet string
|
|
48
|
+
## string the signature on the exchange hash
|
|
49
|
+
buffer = connection.next_message
|
|
50
|
+
raise Net::SSH::Exception, 'expected REPLY' unless buffer.type == reply
|
|
51
|
+
|
|
52
|
+
result = {}
|
|
53
|
+
result[:key_blob] = buffer.read_string
|
|
54
|
+
result[:server_key] = Net::SSH::Buffer.new(result[:key_blob]).read_key
|
|
55
|
+
result[:server_ecdh_pubkey] = buffer.read_string
|
|
56
|
+
result[:shared_secret] = compute_shared_secret(result[:server_ecdh_pubkey])
|
|
57
|
+
|
|
58
|
+
sig_buffer = Net::SSH::Buffer.new(buffer.read_string)
|
|
59
|
+
sig_type = sig_buffer.read_string
|
|
60
|
+
if sig_type != algorithms.host_key_format
|
|
61
|
+
raise Net::SSH::Exception, "host key algorithm mismatch for signature '#{sig_type}' != '#{algorithms.host_key_format}'"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
result[:server_sig] = sig_buffer.read_string
|
|
65
|
+
|
|
66
|
+
result
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
gem 'x25519' # raise if the gem x25519 is not installed
|
|
2
|
+
|
|
3
|
+
require 'x25519'
|
|
4
|
+
|
|
5
|
+
require 'net/ssh/transport/constants'
|
|
6
|
+
require 'net/ssh/transport/kex/abstract5656'
|
|
7
|
+
|
|
8
|
+
module Net
|
|
9
|
+
module SSH
|
|
10
|
+
module Transport
|
|
11
|
+
module Kex
|
|
12
|
+
# A key-exchange service implementing the "curve25519-sha256@libssh.org"
|
|
13
|
+
# key-exchange algorithm. (defined in https://tools.ietf.org/html/draft-ietf-curdle-ssh-curves-06)
|
|
14
|
+
class Curve25519Sha256 < Abstract5656
|
|
15
|
+
def digester
|
|
16
|
+
OpenSSL::Digest::SHA256
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def generate_key # :nodoc:
|
|
22
|
+
::X25519::Scalar.generate
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
## string Q_C, client's ephemeral public key octet string
|
|
26
|
+
def ecdh_public_key_bytes
|
|
27
|
+
ecdh.public_key.to_bytes
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# compute shared secret from server's public key and client's private key
|
|
31
|
+
def compute_shared_secret(server_ecdh_pubkey)
|
|
32
|
+
pk = ::X25519::MontgomeryU.new(server_ecdh_pubkey)
|
|
33
|
+
OpenSSL::BN.new(ecdh.diffie_hellman(pk).to_bytes, 2)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
module Net
|
|
2
|
+
module SSH
|
|
3
|
+
module Transport
|
|
4
|
+
module Kex
|
|
5
|
+
# Loads Curve25519Sha256 support which requires optinal dependencies
|
|
6
|
+
module Curve25519Sha256Loader
|
|
7
|
+
begin
|
|
8
|
+
require 'net/ssh/transport/kex/curve25519_sha256'
|
|
9
|
+
LOADED = true
|
|
10
|
+
ERROR = nil
|
|
11
|
+
rescue LoadError => e
|
|
12
|
+
ERROR = e
|
|
13
|
+
LOADED = false
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def self.raiseUnlessLoaded(message)
|
|
17
|
+
description = ERROR.is_a?(LoadError) ? dependenciesRequiredForX25519 : ''
|
|
18
|
+
description << "#{ERROR.class} : \"#{ERROR.message}\"\n" if ERROR
|
|
19
|
+
raise NotImplementedError, "#{message}\n#{description}" unless LOADED
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.dependenciesRequiredForX25519
|
|
23
|
+
result = "net-ssh requires the following gems for x25519 support:\n"
|
|
24
|
+
result << " * x25519\n"
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
require 'net/ssh/transport/kex/diffie_hellman_group1_sha1'
|
|
2
2
|
|
|
3
|
-
module Net
|
|
4
|
-
module SSH
|
|
5
|
-
module Transport
|
|
3
|
+
module Net
|
|
4
|
+
module SSH
|
|
5
|
+
module Transport
|
|
6
6
|
module Kex
|
|
7
|
-
|
|
8
7
|
# A key-exchange service implementing the "diffie-hellman-group14-sha1"
|
|
9
8
|
# key-exchange algorithm. (defined in RFC 4253)
|
|
10
9
|
class DiffieHellmanGroup14SHA1 < DiffieHellmanGroup1SHA1
|
|
11
|
-
include Loggable
|
|
12
|
-
include Constants
|
|
13
|
-
|
|
14
10
|
# The value of 'P', as a string, in hexadecimal
|
|
15
11
|
P_s = "FFFFFFFF" "FFFFFFFF" "C90FDAA2" "2168C234" +
|
|
16
12
|
"C4C6628B" "80DC1CD1" "29024E08" "8A67CC74" +
|
|
@@ -28,22 +24,12 @@ module Net
|
|
|
28
24
|
"B5C55DF0" "6F4C52C9" "DE2BCBF6" "95581718" +
|
|
29
25
|
"3995497C" "EA956AE5" "15D22618" "98FA0510" +
|
|
30
26
|
"15728E5A" "8AACAA68" "FFFFFFFF" "FFFFFFFF"
|
|
31
|
-
|
|
27
|
+
|
|
32
28
|
# The radix in which P_s represents the value of P
|
|
33
29
|
P_r = 16
|
|
34
|
-
|
|
30
|
+
|
|
35
31
|
# The group constant
|
|
36
32
|
G = 2
|
|
37
|
-
|
|
38
|
-
private
|
|
39
|
-
|
|
40
|
-
def get_p
|
|
41
|
-
OpenSSL::BN.new(P_s, P_r)
|
|
42
|
-
end
|
|
43
|
-
|
|
44
|
-
def get_g
|
|
45
|
-
G
|
|
46
|
-
end
|
|
47
33
|
end
|
|
48
34
|
end
|
|
49
35
|
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
require 'net/ssh/transport/kex/diffie_hellman_group14_sha1'
|
|
2
|
+
|
|
3
|
+
module Net::SSH::Transport::Kex
|
|
4
|
+
# A key-exchange service implementing the "diffie-hellman-group14-sha256"
|
|
5
|
+
# key-exchange algorithm.
|
|
6
|
+
class DiffieHellmanGroup14SHA256 < DiffieHellmanGroup14SHA1
|
|
7
|
+
def digester
|
|
8
|
+
OpenSSL::Digest::SHA256
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -1,20 +1,12 @@
|
|
|
1
|
-
require 'net/ssh/
|
|
2
|
-
require 'net/ssh/errors'
|
|
3
|
-
require 'net/ssh/loggable'
|
|
4
|
-
require 'net/ssh/transport/openssl'
|
|
5
|
-
require 'net/ssh/transport/constants'
|
|
1
|
+
require 'net/ssh/transport/kex/abstract'
|
|
6
2
|
|
|
7
3
|
module Net
|
|
8
4
|
module SSH
|
|
9
5
|
module Transport
|
|
10
6
|
module Kex
|
|
11
|
-
|
|
12
7
|
# A key-exchange service implementing the "diffie-hellman-group1-sha1"
|
|
13
8
|
# key-exchange algorithm.
|
|
14
|
-
class DiffieHellmanGroup1SHA1
|
|
15
|
-
include Loggable
|
|
16
|
-
include Constants
|
|
17
|
-
|
|
9
|
+
class DiffieHellmanGroup1SHA1 < Abstract
|
|
18
10
|
# The value of 'P', as a string, in hexadecimal
|
|
19
11
|
P_s = "FFFFFFFF" "FFFFFFFF" "C90FDAA2" "2168C234" +
|
|
20
12
|
"C4C6628B" "80DC1CD1" "29024E08" "8A67CC74" +
|
|
@@ -31,67 +23,18 @@ module Net
|
|
|
31
23
|
# The group constant
|
|
32
24
|
G = 2
|
|
33
25
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
attr_reader :digester
|
|
37
|
-
attr_reader :algorithms
|
|
38
|
-
attr_reader :connection
|
|
39
|
-
attr_reader :data
|
|
40
|
-
attr_reader :dh
|
|
41
|
-
|
|
42
|
-
# Create a new instance of the DiffieHellmanGroup1SHA1 algorithm.
|
|
43
|
-
# The data is a Hash of symbols representing information
|
|
44
|
-
# required by this algorithm, which was acquired during earlier
|
|
45
|
-
# processing.
|
|
46
|
-
def initialize(algorithms, connection, data)
|
|
47
|
-
@p = get_p
|
|
48
|
-
@g = get_g
|
|
49
|
-
|
|
50
|
-
@digester = OpenSSL::Digest::SHA1
|
|
51
|
-
@algorithms = algorithms
|
|
52
|
-
@connection = connection
|
|
53
|
-
|
|
54
|
-
@data = data.dup
|
|
55
|
-
@dh = generate_key
|
|
56
|
-
@logger = @data.delete(:logger)
|
|
57
|
-
end
|
|
58
|
-
|
|
59
|
-
# Perform the key-exchange for the given session, with the given
|
|
60
|
-
# data. This method will return a hash consisting of the
|
|
61
|
-
# following keys:
|
|
62
|
-
#
|
|
63
|
-
# * :session_id
|
|
64
|
-
# * :server_key
|
|
65
|
-
# * :shared_secret
|
|
66
|
-
# * :hashing_algorithm
|
|
67
|
-
#
|
|
68
|
-
# The caller is expected to be able to understand how to use these
|
|
69
|
-
# deliverables.
|
|
70
|
-
def exchange_keys
|
|
71
|
-
result = send_kexinit
|
|
72
|
-
verify_server_key(result[:server_key])
|
|
73
|
-
session_id = verify_signature(result)
|
|
74
|
-
confirm_newkeys
|
|
75
|
-
|
|
76
|
-
return { session_id: session_id,
|
|
77
|
-
server_key: result[:server_key],
|
|
78
|
-
shared_secret: result[:shared_secret],
|
|
79
|
-
hashing_algorithm: digester }
|
|
26
|
+
def digester
|
|
27
|
+
OpenSSL::Digest::SHA1
|
|
80
28
|
end
|
|
81
29
|
|
|
82
30
|
private
|
|
83
31
|
|
|
84
|
-
|
|
85
|
-
OpenSSL::BN.new(P_s, P_r)
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def get_g
|
|
89
|
-
G
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
# Returns the DH key parameters for the current connection.
|
|
32
|
+
# Returns the DH key parameters for the current connection. [p, q]
|
|
93
33
|
def get_parameters
|
|
94
|
-
[
|
|
34
|
+
[
|
|
35
|
+
OpenSSL::BN.new(self.class::P_s, self.class::P_r),
|
|
36
|
+
self.class::G
|
|
37
|
+
]
|
|
95
38
|
end
|
|
96
39
|
|
|
97
40
|
# Returns the INIT/REPLY constants used by this algorithm.
|
|
@@ -116,26 +59,26 @@ module Net
|
|
|
116
59
|
|
|
117
60
|
# Generate a DH key with a private key consisting of the given
|
|
118
61
|
# number of bytes.
|
|
119
|
-
def generate_key
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
62
|
+
def generate_key # :nodoc:
|
|
63
|
+
p, g = get_parameters
|
|
64
|
+
|
|
65
|
+
asn1 = OpenSSL::ASN1::Sequence(
|
|
66
|
+
[
|
|
67
|
+
OpenSSL::ASN1::Integer(p),
|
|
68
|
+
OpenSSL::ASN1::Integer(g)
|
|
69
|
+
]
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
dh_params = OpenSSL::PKey::DH.new(asn1.to_der)
|
|
73
|
+
# XXX No private key size check! In theory the latter call should work but fails on OpenSSL 3.0 as
|
|
74
|
+
# dh_paramgen_subprime_len is now reserved for DHX algorithm
|
|
75
|
+
# key = OpenSSL::PKey.generate_key(dh_params, "dh_paramgen_subprime_len" => data[:need_bytes]/8)
|
|
76
|
+
if OpenSSL::PKey.respond_to?(:generate_key)
|
|
77
|
+
OpenSSL::PKey.generate_key(dh_params)
|
|
125
78
|
else
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
dh.generate_key!
|
|
130
|
-
until dh.valid? && dh.priv_key.num_bytes == data[:need_bytes]
|
|
131
|
-
if dh.respond_to?(:set_key)
|
|
132
|
-
dh.set_key(nil, OpenSSL::BN.rand(data[:need_bytes] * 8))
|
|
133
|
-
else
|
|
134
|
-
dh.priv_key = OpenSSL::BN.rand(data[:need_bytes] * 8)
|
|
135
|
-
end
|
|
136
|
-
dh.generate_key!
|
|
79
|
+
dh_params.generate_key!
|
|
80
|
+
dh_params
|
|
137
81
|
end
|
|
138
|
-
dh
|
|
139
82
|
end
|
|
140
83
|
|
|
141
84
|
# Send the KEXDH_INIT message, and expect the KEXDH_REPLY. Return the
|
|
@@ -143,7 +86,7 @@ module Net
|
|
|
143
86
|
#
|
|
144
87
|
# Parse the buffer from a KEXDH_REPLY message, returning a hash of
|
|
145
88
|
# the extracted values.
|
|
146
|
-
def send_kexinit
|
|
89
|
+
def send_kexinit # :nodoc:
|
|
147
90
|
init, reply = get_message_types
|
|
148
91
|
|
|
149
92
|
# send the KEXDH_INIT message
|
|
@@ -165,66 +108,14 @@ module Net
|
|
|
165
108
|
sig_type = sig_buffer.read_string
|
|
166
109
|
if sig_type != algorithms.host_key_format
|
|
167
110
|
raise Net::SSH::Exception,
|
|
168
|
-
|
|
169
|
-
|
|
111
|
+
"host key algorithm mismatch for signature " +
|
|
112
|
+
"'#{sig_type}' != '#{algorithms.host_key_format}'"
|
|
170
113
|
end
|
|
171
114
|
result[:server_sig] = sig_buffer.read_string
|
|
172
115
|
|
|
173
116
|
return result
|
|
174
117
|
end
|
|
175
|
-
|
|
176
|
-
# Verify that the given key is of the expected type, and that it
|
|
177
|
-
# really is the key for the session's host. Raise Net::SSH::Exception
|
|
178
|
-
# if it is not.
|
|
179
|
-
def verify_server_key(key) #:nodoc:
|
|
180
|
-
if key.ssh_type != algorithms.host_key
|
|
181
|
-
raise Net::SSH::Exception,
|
|
182
|
-
"host key algorithm mismatch " +
|
|
183
|
-
"'#{key.ssh_type}' != '#{algorithms.host_key}'"
|
|
184
|
-
end
|
|
185
|
-
|
|
186
|
-
blob, fingerprint = generate_key_fingerprint(key)
|
|
187
|
-
|
|
188
|
-
raise Net::SSH::Exception, "host key verification failed" unless connection.host_key_verifier.verify(key: key, key_blob: blob, fingerprint: fingerprint, session: connection)
|
|
189
|
-
end
|
|
190
|
-
|
|
191
|
-
def generate_key_fingerprint(key)
|
|
192
|
-
blob = Net::SSH::Buffer.from(:key, key).to_s
|
|
193
|
-
|
|
194
|
-
fingerprint = Net::SSH::Authentication::PubKeyFingerprint.fingerprint(blob, @connection.options[:fingerprint_hash] || 'SHA256')
|
|
195
|
-
|
|
196
|
-
[blob, fingerprint]
|
|
197
|
-
rescue ::Exception => e
|
|
198
|
-
[nil, "(could not generate fingerprint: #{e.message})"]
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
# Verify the signature that was received. Raise Net::SSH::Exception
|
|
202
|
-
# if the signature could not be verified. Otherwise, return the new
|
|
203
|
-
# session-id.
|
|
204
|
-
def verify_signature(result) #:nodoc:
|
|
205
|
-
response = build_signature_buffer(result)
|
|
206
|
-
|
|
207
|
-
hash = @digester.digest(response.to_s)
|
|
208
|
-
|
|
209
|
-
raise Net::SSH::Exception, "could not verify server signature" unless result[:server_key].ssh_do_verify(result[:server_sig], hash)
|
|
210
|
-
|
|
211
|
-
return hash
|
|
212
|
-
end
|
|
213
|
-
|
|
214
|
-
# Send the NEWKEYS message, and expect the NEWKEYS message in
|
|
215
|
-
# reply.
|
|
216
|
-
def confirm_newkeys #:nodoc:
|
|
217
|
-
# send own NEWKEYS message first (the wodSSHServer won't send first)
|
|
218
|
-
response = Net::SSH::Buffer.new
|
|
219
|
-
response.write_byte(NEWKEYS)
|
|
220
|
-
connection.send_message(response)
|
|
221
|
-
|
|
222
|
-
# wait for the server's NEWKEYS message
|
|
223
|
-
buffer = connection.next_message
|
|
224
|
-
raise Net::SSH::Exception, "expected NEWKEYS" unless buffer.type == NEWKEYS
|
|
225
|
-
end
|
|
226
118
|
end
|
|
227
|
-
|
|
228
119
|
end
|
|
229
120
|
end
|
|
230
121
|
end
|
|
@@ -3,18 +3,12 @@ require 'net/ssh/transport/constants'
|
|
|
3
3
|
require 'net/ssh/transport/kex/diffie_hellman_group1_sha1'
|
|
4
4
|
|
|
5
5
|
module Net::SSH::Transport::Kex
|
|
6
|
-
|
|
7
6
|
# A key-exchange service implementing the
|
|
8
7
|
# "diffie-hellman-group-exchange-sha1" key-exchange algorithm.
|
|
9
8
|
class DiffieHellmanGroupExchangeSHA1 < DiffieHellmanGroup1SHA1
|
|
10
9
|
MINIMUM_BITS = 1024
|
|
11
10
|
MAXIMUM_BITS = 8192
|
|
12
11
|
|
|
13
|
-
KEXDH_GEX_GROUP = 31
|
|
14
|
-
KEXDH_GEX_INIT = 32
|
|
15
|
-
KEXDH_GEX_REPLY = 33
|
|
16
|
-
KEXDH_GEX_REQUEST = 34
|
|
17
|
-
|
|
18
12
|
private
|
|
19
13
|
|
|
20
14
|
# Compute the number of bits needed for the given number of bytes.
|
|
@@ -40,7 +34,7 @@ module Net::SSH::Transport::Kex
|
|
|
40
34
|
|
|
41
35
|
# request the DH key parameters for the given number of bits.
|
|
42
36
|
buffer = Net::SSH::Buffer.from(:byte, KEXDH_GEX_REQUEST, :long, data[:minimum_dh_bits],
|
|
43
|
-
|
|
37
|
+
:long, data[:need_bits], :long, MAXIMUM_BITS)
|
|
44
38
|
connection.send_message(buffer)
|
|
45
39
|
|
|
46
40
|
buffer = connection.next_message
|
|
@@ -75,5 +69,4 @@ module Net::SSH::Transport::Kex
|
|
|
75
69
|
response
|
|
76
70
|
end
|
|
77
71
|
end
|
|
78
|
-
|
|
79
72
|
end
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
require 'net/ssh/transport/kex/diffie_hellman_group_exchange_sha1'
|
|
2
2
|
|
|
3
3
|
module Net::SSH::Transport::Kex
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
super(*args)
|
|
10
|
-
|
|
11
|
-
@digester = OpenSSL::Digest::SHA256
|
|
12
|
-
end
|
|
4
|
+
# A key-exchange service implementing the
|
|
5
|
+
# "diffie-hellman-group-exchange-sha256" key-exchange algorithm.
|
|
6
|
+
class DiffieHellmanGroupExchangeSHA256 < DiffieHellmanGroupExchangeSHA1
|
|
7
|
+
def digester
|
|
8
|
+
OpenSSL::Digest::SHA256
|
|
13
9
|
end
|
|
14
10
|
end
|
|
15
11
|
end
|