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
|
@@ -5,13 +5,13 @@ require 'net/ssh/transport/cipher_factory'
|
|
|
5
5
|
require 'net/ssh/transport/constants'
|
|
6
6
|
require 'net/ssh/transport/hmac'
|
|
7
7
|
require 'net/ssh/transport/kex'
|
|
8
|
+
require 'net/ssh/transport/kex/curve25519_sha256_loader'
|
|
8
9
|
require 'net/ssh/transport/server_version'
|
|
9
10
|
require 'net/ssh/authentication/ed25519_loader'
|
|
10
11
|
|
|
11
|
-
module Net
|
|
12
|
-
module SSH
|
|
12
|
+
module Net
|
|
13
|
+
module SSH
|
|
13
14
|
module Transport
|
|
14
|
-
|
|
15
15
|
# Implements the higher-level logic behind an SSH key-exchange. It handles
|
|
16
16
|
# both the initial exchange, as well as subsequent re-exchanges (as needed).
|
|
17
17
|
# It also encapsulates the negotiation of the algorithms, and provides a
|
|
@@ -22,95 +22,131 @@ module Net
|
|
|
22
22
|
class Algorithms
|
|
23
23
|
include Loggable
|
|
24
24
|
include Constants
|
|
25
|
-
|
|
26
|
-
# Define the default algorithms, in order of preference, supported by
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
25
|
+
|
|
26
|
+
# Define the default algorithms, in order of preference, supported by Net::SSH.
|
|
27
|
+
DEFAULT_ALGORITHMS = {
|
|
28
|
+
host_key: %w[ecdsa-sha2-nistp521-cert-v01@openssh.com
|
|
29
|
+
ecdsa-sha2-nistp384-cert-v01@openssh.com
|
|
30
|
+
ecdsa-sha2-nistp256-cert-v01@openssh.com
|
|
31
|
+
ecdsa-sha2-nistp521
|
|
32
|
+
ecdsa-sha2-nistp384
|
|
33
|
+
ecdsa-sha2-nistp256
|
|
30
34
|
ssh-rsa-cert-v01@openssh.com
|
|
31
|
-
ssh-rsa-cert-v00@openssh.com
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
ssh-rsa-cert-v00@openssh.com
|
|
36
|
+
ssh-rsa
|
|
37
|
+
rsa-sha2-256
|
|
38
|
+
rsa-sha2-512],
|
|
39
|
+
|
|
40
|
+
kex: %w[ecdh-sha2-nistp521
|
|
41
|
+
ecdh-sha2-nistp384
|
|
42
|
+
ecdh-sha2-nistp256
|
|
43
|
+
diffie-hellman-group-exchange-sha256
|
|
44
|
+
diffie-hellman-group14-sha256
|
|
45
|
+
diffie-hellman-group14-sha1],
|
|
46
|
+
|
|
47
|
+
encryption: %w[aes256-ctr aes192-ctr aes128-ctr],
|
|
48
|
+
|
|
49
|
+
hmac: %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com
|
|
50
|
+
hmac-sha2-512 hmac-sha2-256
|
|
51
|
+
hmac-sha1]
|
|
52
|
+
}.freeze
|
|
53
|
+
|
|
54
|
+
if Net::SSH::Authentication::ED25519Loader::LOADED
|
|
55
|
+
DEFAULT_ALGORITHMS[:host_key].unshift(
|
|
56
|
+
'ssh-ed25519-cert-v01@openssh.com',
|
|
57
|
+
'ssh-ed25519'
|
|
58
|
+
)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
if Net::SSH::Transport::Kex::Curve25519Sha256Loader::LOADED
|
|
62
|
+
DEFAULT_ALGORITHMS[:kex].unshift(
|
|
63
|
+
'curve25519-sha256',
|
|
64
|
+
'curve25519-sha256@libssh.org'
|
|
65
|
+
)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# Define all algorithms, with the deprecated, supported by Net::SSH.
|
|
69
|
+
ALGORITHMS = {
|
|
70
|
+
host_key: DEFAULT_ALGORITHMS[:host_key] + %w[ssh-dss],
|
|
71
|
+
|
|
72
|
+
kex: DEFAULT_ALGORITHMS[:kex] +
|
|
73
|
+
%w[diffie-hellman-group-exchange-sha1
|
|
74
|
+
diffie-hellman-group1-sha1],
|
|
75
|
+
|
|
76
|
+
encryption: DEFAULT_ALGORITHMS[:encryption] +
|
|
77
|
+
%w[aes256-cbc aes192-cbc aes128-cbc
|
|
78
|
+
rijndael-cbc@lysator.liu.se
|
|
79
|
+
blowfish-ctr blowfish-cbc
|
|
80
|
+
cast128-ctr cast128-cbc
|
|
81
|
+
3des-ctr 3des-cbc
|
|
82
|
+
idea-cbc
|
|
83
|
+
none],
|
|
84
|
+
|
|
85
|
+
hmac: DEFAULT_ALGORITHMS[:hmac] +
|
|
86
|
+
%w[hmac-sha2-512-96 hmac-sha2-256-96
|
|
87
|
+
hmac-sha1-96
|
|
43
88
|
hmac-ripemd160 hmac-ripemd160@openssh.com
|
|
44
|
-
hmac-
|
|
45
|
-
|
|
46
|
-
|
|
89
|
+
hmac-md5 hmac-md5-96
|
|
90
|
+
none],
|
|
91
|
+
|
|
47
92
|
compression: %w[none zlib@openssh.com zlib],
|
|
48
93
|
language: %w[]
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
ALGORITHMS[:host_key] += %w[ecdsa-sha2-nistp256
|
|
52
|
-
ecdsa-sha2-nistp384
|
|
53
|
-
ecdsa-sha2-nistp521]
|
|
54
|
-
ALGORITHMS[:host_key] += %w[ssh-ed25519] if Net::SSH::Authentication::ED25519Loader::LOADED
|
|
55
|
-
ALGORITHMS[:kex] += %w[ecdh-sha2-nistp256
|
|
56
|
-
ecdh-sha2-nistp384
|
|
57
|
-
ecdh-sha2-nistp521]
|
|
58
|
-
end
|
|
59
|
-
|
|
94
|
+
}.freeze
|
|
95
|
+
|
|
60
96
|
# The underlying transport layer session that supports this object
|
|
61
97
|
attr_reader :session
|
|
62
|
-
|
|
98
|
+
|
|
63
99
|
# The hash of options used to initialize this object
|
|
64
100
|
attr_reader :options
|
|
65
|
-
|
|
101
|
+
|
|
66
102
|
# The kex algorithm to use settled on between the client and server.
|
|
67
103
|
attr_reader :kex
|
|
68
|
-
|
|
104
|
+
|
|
69
105
|
# The type of host key that will be used for this session.
|
|
70
106
|
attr_reader :host_key
|
|
71
|
-
|
|
107
|
+
|
|
72
108
|
# The type of the cipher to use to encrypt packets sent from the client to
|
|
73
109
|
# the server.
|
|
74
110
|
attr_reader :encryption_client
|
|
75
|
-
|
|
111
|
+
|
|
76
112
|
# The type of the cipher to use to decrypt packets arriving from the server.
|
|
77
113
|
attr_reader :encryption_server
|
|
78
|
-
|
|
114
|
+
|
|
79
115
|
# The type of HMAC to use to sign packets sent by the client.
|
|
80
116
|
attr_reader :hmac_client
|
|
81
|
-
|
|
117
|
+
|
|
82
118
|
# The type of HMAC to use to validate packets arriving from the server.
|
|
83
119
|
attr_reader :hmac_server
|
|
84
|
-
|
|
120
|
+
|
|
85
121
|
# The type of compression to use to compress packets being sent by the client.
|
|
86
122
|
attr_reader :compression_client
|
|
87
|
-
|
|
123
|
+
|
|
88
124
|
# The type of compression to use to decompress packets arriving from the server.
|
|
89
125
|
attr_reader :compression_server
|
|
90
|
-
|
|
126
|
+
|
|
91
127
|
# The language that will be used in messages sent by the client.
|
|
92
128
|
attr_reader :language_client
|
|
93
|
-
|
|
129
|
+
|
|
94
130
|
# The language that will be used in messages sent from the server.
|
|
95
131
|
attr_reader :language_server
|
|
96
|
-
|
|
132
|
+
|
|
97
133
|
# The hash of algorithms preferred by the client, which will be told to
|
|
98
134
|
# the server during algorithm negotiation.
|
|
99
135
|
attr_reader :algorithms
|
|
100
|
-
|
|
136
|
+
|
|
101
137
|
# The session-id for this session, as decided during the initial key exchange.
|
|
102
138
|
attr_reader :session_id
|
|
103
|
-
|
|
139
|
+
|
|
104
140
|
# Returns true if the given packet can be processed during a key-exchange.
|
|
105
141
|
def self.allowed_packet?(packet)
|
|
106
142
|
(1..4).include?(packet.type) ||
|
|
107
143
|
(6..19).include?(packet.type) ||
|
|
108
144
|
(21..49).include?(packet.type)
|
|
109
145
|
end
|
|
110
|
-
|
|
146
|
+
|
|
111
147
|
# Instantiates a new Algorithms object, and prepares the hash of preferred
|
|
112
148
|
# algorithms based on the options parameter and the ALGORITHMS constant.
|
|
113
|
-
def initialize(session, options={})
|
|
149
|
+
def initialize(session, options = {})
|
|
114
150
|
@session = session
|
|
115
151
|
@logger = session.logger
|
|
116
152
|
@options = options
|
|
@@ -119,13 +155,14 @@ module Net
|
|
|
119
155
|
@client_packet = @server_packet = nil
|
|
120
156
|
prepare_preferred_algorithms!
|
|
121
157
|
end
|
|
122
|
-
|
|
158
|
+
|
|
123
159
|
# Start the algorithm negotation
|
|
124
160
|
def start
|
|
125
161
|
raise ArgumentError, "Cannot call start if it's negotiation started or done" if @pending || @initialized
|
|
162
|
+
|
|
126
163
|
send_kexinit
|
|
127
164
|
end
|
|
128
|
-
|
|
165
|
+
|
|
129
166
|
# Request a rekey operation. This will return immediately, and does not
|
|
130
167
|
# actually perform the rekey operation. It does cause the session to change
|
|
131
168
|
# state, however--until the key exchange finishes, no new packets will be
|
|
@@ -135,7 +172,7 @@ module Net
|
|
|
135
172
|
@initialized = false
|
|
136
173
|
send_kexinit
|
|
137
174
|
end
|
|
138
|
-
|
|
175
|
+
|
|
139
176
|
# Called by the transport layer when a KEXINIT packet is received, indicating
|
|
140
177
|
# that the server wants to exchange keys. This can be spontaneous, or it
|
|
141
178
|
# can be in response to a client-initiated rekey request (see #rekey!). Either
|
|
@@ -150,13 +187,13 @@ module Net
|
|
|
150
187
|
proceed!
|
|
151
188
|
end
|
|
152
189
|
end
|
|
153
|
-
|
|
190
|
+
|
|
154
191
|
# A convenience method for accessing the list of preferred types for a
|
|
155
192
|
# specific algorithm (see #algorithms).
|
|
156
193
|
def [](key)
|
|
157
194
|
algorithms[key]
|
|
158
195
|
end
|
|
159
|
-
|
|
196
|
+
|
|
160
197
|
# Returns +true+ if a key-exchange is pending. This will be true from the
|
|
161
198
|
# moment either the client or server requests the key exchange, until the
|
|
162
199
|
# exchange completes. While an exchange is pending, only a limited number
|
|
@@ -180,8 +217,8 @@ module Net
|
|
|
180
217
|
|
|
181
218
|
def host_key_format
|
|
182
219
|
case host_key
|
|
183
|
-
when
|
|
184
|
-
|
|
220
|
+
when /^([a-z0-9-]+)-cert-v\d{2}@openssh.com$/
|
|
221
|
+
Regexp.last_match[1]
|
|
185
222
|
else
|
|
186
223
|
host_key
|
|
187
224
|
end
|
|
@@ -201,7 +238,7 @@ module Net
|
|
|
201
238
|
session.send_message(packet)
|
|
202
239
|
proceed! if @server_packet
|
|
203
240
|
end
|
|
204
|
-
|
|
241
|
+
|
|
205
242
|
# After both client and server have sent their KEXINIT packets, this
|
|
206
243
|
# will do the algorithm negotiation and key exchange. Once both finish,
|
|
207
244
|
# the object leaves the pending state and the method returns.
|
|
@@ -211,7 +248,7 @@ module Net
|
|
|
211
248
|
exchange_keys
|
|
212
249
|
@pending = false
|
|
213
250
|
end
|
|
214
|
-
|
|
251
|
+
|
|
215
252
|
# Prepares the list of preferred algorithms, based on the options hash
|
|
216
253
|
# that was given when the object was constructed, and the ALGORITHMS
|
|
217
254
|
# constant. Also, when determining the host_key type to use, the known
|
|
@@ -220,70 +257,87 @@ module Net
|
|
|
220
257
|
# communicating with this server.
|
|
221
258
|
def prepare_preferred_algorithms!
|
|
222
259
|
options[:compression] = %w[zlib@openssh.com zlib] if options[:compression] == true
|
|
223
|
-
|
|
260
|
+
|
|
224
261
|
ALGORITHMS.each do |algorithm, supported|
|
|
225
|
-
algorithms[algorithm] = compose_algorithm_list(
|
|
262
|
+
algorithms[algorithm] = compose_algorithm_list(
|
|
263
|
+
supported, options[algorithm] || DEFAULT_ALGORITHMS[algorithm],
|
|
264
|
+
options[:append_all_supported_algorithms]
|
|
265
|
+
)
|
|
226
266
|
end
|
|
227
|
-
|
|
267
|
+
|
|
228
268
|
# for convention, make sure our list has the same keys as the server
|
|
229
269
|
# list
|
|
230
|
-
|
|
270
|
+
|
|
231
271
|
algorithms[:encryption_client ] = algorithms[:encryption_server ] = algorithms[:encryption]
|
|
232
272
|
algorithms[:hmac_client ] = algorithms[:hmac_server ] = algorithms[:hmac]
|
|
233
273
|
algorithms[:compression_client] = algorithms[:compression_server] = algorithms[:compression]
|
|
234
274
|
algorithms[:language_client ] = algorithms[:language_server ] = algorithms[:language]
|
|
235
|
-
|
|
275
|
+
|
|
236
276
|
if !options.key?(:host_key)
|
|
237
277
|
# make sure the host keys are specified in preference order, where any
|
|
238
278
|
# existing known key for the host has preference.
|
|
239
|
-
|
|
279
|
+
|
|
240
280
|
existing_keys = session.host_keys
|
|
241
|
-
host_keys = existing_keys.
|
|
281
|
+
host_keys = existing_keys.flat_map { |key| key.respond_to?(:ssh_types) ? key.ssh_types : [key.ssh_type] }.uniq
|
|
242
282
|
algorithms[:host_key].each do |name|
|
|
243
283
|
host_keys << name unless host_keys.include?(name)
|
|
244
284
|
end
|
|
245
285
|
algorithms[:host_key] = host_keys
|
|
246
286
|
end
|
|
247
287
|
end
|
|
248
|
-
|
|
288
|
+
|
|
249
289
|
# Composes the list of algorithms by taking supported algorithms and matching with supplied options.
|
|
250
290
|
def compose_algorithm_list(supported, option, append_all_supported_algorithms = false)
|
|
251
291
|
return supported.dup unless option
|
|
252
|
-
|
|
292
|
+
|
|
253
293
|
list = []
|
|
254
294
|
option = Array(option).compact.uniq
|
|
255
|
-
|
|
256
|
-
if option.first && option.first.start_with?('+')
|
|
295
|
+
|
|
296
|
+
if option.first && option.first.start_with?('+', '-')
|
|
257
297
|
list = supported.dup
|
|
258
|
-
|
|
259
|
-
|
|
298
|
+
|
|
299
|
+
appends = option.select { |opt| opt.start_with?('+') }.map { |opt| opt[1..-1] }
|
|
300
|
+
deletions = option.select { |opt| opt.start_with?('-') }.map { |opt| opt[1..-1] }
|
|
301
|
+
|
|
302
|
+
list.concat(appends)
|
|
303
|
+
|
|
304
|
+
deletions.each do |opt|
|
|
305
|
+
if opt.include?('*')
|
|
306
|
+
opt_escaped = Regexp.escape(opt)
|
|
307
|
+
algo_re = /\A#{opt_escaped.gsub('\*', '[A-Za-z\d\-@\.]*')}\z/
|
|
308
|
+
list.delete_if { |existing_opt| algo_re.match(existing_opt) }
|
|
309
|
+
else
|
|
310
|
+
list.delete(opt)
|
|
311
|
+
end
|
|
312
|
+
end
|
|
313
|
+
|
|
260
314
|
list.uniq!
|
|
261
315
|
else
|
|
262
316
|
list = option
|
|
263
|
-
|
|
317
|
+
|
|
264
318
|
if append_all_supported_algorithms
|
|
265
319
|
supported.each { |name| list << name unless list.include?(name) }
|
|
266
320
|
end
|
|
267
321
|
end
|
|
268
|
-
|
|
322
|
+
|
|
269
323
|
unsupported = []
|
|
270
324
|
list.select! do |name|
|
|
271
325
|
is_supported = supported.include?(name)
|
|
272
326
|
unsupported << name unless is_supported
|
|
273
327
|
is_supported
|
|
274
328
|
end
|
|
275
|
-
|
|
329
|
+
|
|
276
330
|
lwarn { %(unsupported algorithm: `#{unsupported}') } unless unsupported.empty?
|
|
277
|
-
|
|
331
|
+
|
|
278
332
|
list
|
|
279
333
|
end
|
|
280
|
-
|
|
334
|
+
|
|
281
335
|
# Parses a KEXINIT packet from the server.
|
|
282
336
|
def parse_server_algorithm_packet(packet)
|
|
283
337
|
data = { raw: packet.content }
|
|
284
|
-
|
|
338
|
+
|
|
285
339
|
packet.read(16) # skip the cookie value
|
|
286
|
-
|
|
340
|
+
|
|
287
341
|
data[:kex] = packet.read_string.split(/,/)
|
|
288
342
|
data[:host_key] = packet.read_string.split(/,/)
|
|
289
343
|
data[:encryption_client] = packet.read_string.split(/,/)
|
|
@@ -294,15 +348,15 @@ module Net
|
|
|
294
348
|
data[:compression_server] = packet.read_string.split(/,/)
|
|
295
349
|
data[:language_client] = packet.read_string.split(/,/)
|
|
296
350
|
data[:language_server] = packet.read_string.split(/,/)
|
|
297
|
-
|
|
351
|
+
|
|
298
352
|
# TODO: if first_kex_packet_follows, we need to try to skip the
|
|
299
353
|
# actual kexinit stuff and try to guess what the server is doing...
|
|
300
354
|
# need to read more about this scenario.
|
|
301
355
|
# first_kex_packet_follows = packet.read_bool
|
|
302
|
-
|
|
356
|
+
|
|
303
357
|
return data
|
|
304
358
|
end
|
|
305
|
-
|
|
359
|
+
|
|
306
360
|
# Given the #algorithms map of preferred algorithm types, this constructs
|
|
307
361
|
# a KEXINIT packet to send to the server. It does not actually send it,
|
|
308
362
|
# it simply builds the packet and returns it.
|
|
@@ -313,14 +367,14 @@ module Net
|
|
|
313
367
|
hmac = algorithms[:hmac].join(",")
|
|
314
368
|
compression = algorithms[:compression].join(",")
|
|
315
369
|
language = algorithms[:language].join(",")
|
|
316
|
-
|
|
370
|
+
|
|
317
371
|
Net::SSH::Buffer.from(:byte, KEXINIT,
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
372
|
+
:long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)],
|
|
373
|
+
:mstring, [kex, host_key, encryption, encryption, hmac, hmac],
|
|
374
|
+
:mstring, [compression, compression, language, language],
|
|
375
|
+
:bool, false, :long, 0)
|
|
322
376
|
end
|
|
323
|
-
|
|
377
|
+
|
|
324
378
|
# Given the parsed server KEX packet, and the client's preferred algorithm
|
|
325
379
|
# lists in #algorithms, determine which preferred algorithms each has
|
|
326
380
|
# in common and set those as the selected algorithms. If, for any algorithm,
|
|
@@ -336,97 +390,102 @@ module Net
|
|
|
336
390
|
@compression_server = negotiate(:compression_server)
|
|
337
391
|
@language_client = negotiate(:language_client) rescue ""
|
|
338
392
|
@language_server = negotiate(:language_server) rescue ""
|
|
339
|
-
|
|
393
|
+
|
|
340
394
|
debug do
|
|
341
395
|
"negotiated:\n" +
|
|
342
|
-
%i[kex host_key encryption_server encryption_client hmac_client hmac_server
|
|
396
|
+
%i[kex host_key encryption_server encryption_client hmac_client hmac_server
|
|
397
|
+
compression_client compression_server language_client language_server].map do |key|
|
|
343
398
|
"* #{key}: #{instance_variable_get("@#{key}")}"
|
|
344
399
|
end.join("\n")
|
|
345
400
|
end
|
|
346
401
|
end
|
|
347
|
-
|
|
402
|
+
|
|
348
403
|
# Negotiates a single algorithm based on the preferences reported by the
|
|
349
404
|
# server and those set by the client. This is called by
|
|
350
405
|
# #negotiate_algorithms.
|
|
351
406
|
def negotiate(algorithm)
|
|
352
407
|
match = self[algorithm].find { |item| @server_data[algorithm].include?(item) }
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
408
|
+
|
|
409
|
+
if match.nil?
|
|
410
|
+
raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm\n"\
|
|
411
|
+
"Server #{algorithm} preferences: #{@server_data[algorithm].join(',')}\n"\
|
|
412
|
+
"Client #{algorithm} preferences: #{self[algorithm].join(',')}"
|
|
413
|
+
end
|
|
414
|
+
|
|
356
415
|
return match
|
|
357
416
|
end
|
|
358
|
-
|
|
417
|
+
|
|
359
418
|
# Considers the sizes of the keys and block-sizes for the selected ciphers,
|
|
360
419
|
# and the lengths of the hmacs, and returns the largest as the byte requirement
|
|
361
420
|
# for the key-exchange algorithm.
|
|
362
421
|
def kex_byte_requirement
|
|
363
422
|
sizes = [8] # require at least 8 bytes
|
|
364
|
-
|
|
423
|
+
|
|
365
424
|
sizes.concat(CipherFactory.get_lengths(encryption_client))
|
|
366
425
|
sizes.concat(CipherFactory.get_lengths(encryption_server))
|
|
367
|
-
|
|
426
|
+
|
|
368
427
|
sizes << HMAC.key_length(hmac_client)
|
|
369
428
|
sizes << HMAC.key_length(hmac_server)
|
|
370
|
-
|
|
429
|
+
|
|
371
430
|
sizes.max
|
|
372
431
|
end
|
|
373
|
-
|
|
432
|
+
|
|
374
433
|
# Instantiates one of the Transport::Kex classes (based on the negotiated
|
|
375
434
|
# kex algorithm), and uses it to exchange keys. Then, the ciphers and
|
|
376
435
|
# HMACs are initialized and fed to the transport layer, to be used in
|
|
377
436
|
# further communication with the server.
|
|
378
437
|
def exchange_keys
|
|
379
438
|
debug { "exchanging keys" }
|
|
380
|
-
|
|
439
|
+
|
|
381
440
|
algorithm = Kex::MAP[kex].new(self, session,
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
441
|
+
client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION,
|
|
442
|
+
server_version_string: session.server_version.version,
|
|
443
|
+
server_algorithm_packet: @server_packet,
|
|
444
|
+
client_algorithm_packet: @client_packet,
|
|
445
|
+
need_bytes: kex_byte_requirement,
|
|
446
|
+
minimum_dh_bits: options[:minimum_dh_bits],
|
|
447
|
+
logger: logger)
|
|
389
448
|
result = algorithm.exchange_keys
|
|
390
|
-
|
|
449
|
+
|
|
391
450
|
secret = result[:shared_secret].to_ssh
|
|
392
451
|
hash = result[:session_id]
|
|
393
452
|
digester = result[:hashing_algorithm]
|
|
394
|
-
|
|
453
|
+
|
|
395
454
|
@session_id ||= hash
|
|
396
|
-
|
|
455
|
+
|
|
397
456
|
key = Proc.new { |salt| digester.digest(secret + hash + salt + @session_id) }
|
|
398
|
-
|
|
457
|
+
|
|
399
458
|
iv_client = key["A"]
|
|
400
459
|
iv_server = key["B"]
|
|
401
460
|
key_client = key["C"]
|
|
402
461
|
key_server = key["D"]
|
|
403
462
|
mac_key_client = key["E"]
|
|
404
463
|
mac_key_server = key["F"]
|
|
405
|
-
|
|
464
|
+
|
|
406
465
|
parameters = { shared: secret, hash: hash, digester: digester }
|
|
407
|
-
|
|
466
|
+
|
|
408
467
|
cipher_client = CipherFactory.get(encryption_client, parameters.merge(iv: iv_client, key: key_client, encrypt: true))
|
|
409
468
|
cipher_server = CipherFactory.get(encryption_server, parameters.merge(iv: iv_server, key: key_server, decrypt: true))
|
|
410
|
-
|
|
469
|
+
|
|
411
470
|
mac_client = HMAC.get(hmac_client, mac_key_client, parameters)
|
|
412
471
|
mac_server = HMAC.get(hmac_server, mac_key_server, parameters)
|
|
413
|
-
|
|
472
|
+
|
|
414
473
|
session.configure_client cipher: cipher_client, hmac: mac_client,
|
|
415
474
|
compression: normalize_compression_name(compression_client),
|
|
416
475
|
compression_level: options[:compression_level],
|
|
417
476
|
rekey_limit: options[:rekey_limit],
|
|
418
477
|
max_packets: options[:rekey_packet_limit],
|
|
419
478
|
max_blocks: options[:rekey_blocks_limit]
|
|
420
|
-
|
|
479
|
+
|
|
421
480
|
session.configure_server cipher: cipher_server, hmac: mac_server,
|
|
422
481
|
compression: normalize_compression_name(compression_server),
|
|
423
482
|
rekey_limit: options[:rekey_limit],
|
|
424
483
|
max_packets: options[:rekey_packet_limit],
|
|
425
484
|
max_blocks: options[:rekey_blocks_limit]
|
|
426
|
-
|
|
485
|
+
|
|
427
486
|
@initialized = true
|
|
428
487
|
end
|
|
429
|
-
|
|
488
|
+
|
|
430
489
|
# Given the SSH name for some compression algorithm, return a normalized
|
|
431
490
|
# name as a symbol.
|
|
432
491
|
def normalize_compression_name(name)
|