net-ssh 5.0.0.beta1 → 5.0.0.beta2

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.
Files changed (87) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.rubocop_todo.yml +98 -258
  5. data/CHANGES.txt +8 -0
  6. data/Gemfile +1 -3
  7. data/Rakefile +37 -39
  8. data/lib/net/ssh.rb +26 -25
  9. data/lib/net/ssh/authentication/agent.rb +228 -225
  10. data/lib/net/ssh/authentication/certificate.rb +166 -164
  11. data/lib/net/ssh/authentication/constants.rb +17 -14
  12. data/lib/net/ssh/authentication/ed25519.rb +107 -104
  13. data/lib/net/ssh/authentication/ed25519_loader.rb +32 -28
  14. data/lib/net/ssh/authentication/key_manager.rb +5 -3
  15. data/lib/net/ssh/authentication/methods/abstract.rb +53 -47
  16. data/lib/net/ssh/authentication/methods/hostbased.rb +32 -33
  17. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +2 -4
  18. data/lib/net/ssh/authentication/methods/none.rb +10 -10
  19. data/lib/net/ssh/authentication/methods/password.rb +13 -13
  20. data/lib/net/ssh/authentication/methods/publickey.rb +54 -55
  21. data/lib/net/ssh/authentication/pageant.rb +468 -465
  22. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +44 -0
  23. data/lib/net/ssh/authentication/session.rb +127 -123
  24. data/lib/net/ssh/buffer.rb +305 -303
  25. data/lib/net/ssh/buffered_io.rb +163 -162
  26. data/lib/net/ssh/config.rb +230 -227
  27. data/lib/net/ssh/connection/channel.rb +659 -654
  28. data/lib/net/ssh/connection/constants.rb +30 -26
  29. data/lib/net/ssh/connection/event_loop.rb +108 -104
  30. data/lib/net/ssh/connection/keepalive.rb +54 -50
  31. data/lib/net/ssh/connection/session.rb +677 -678
  32. data/lib/net/ssh/connection/term.rb +180 -176
  33. data/lib/net/ssh/errors.rb +101 -99
  34. data/lib/net/ssh/key_factory.rb +108 -108
  35. data/lib/net/ssh/known_hosts.rb +148 -154
  36. data/lib/net/ssh/loggable.rb +56 -54
  37. data/lib/net/ssh/packet.rb +82 -78
  38. data/lib/net/ssh/prompt.rb +55 -53
  39. data/lib/net/ssh/proxy/command.rb +103 -102
  40. data/lib/net/ssh/proxy/errors.rb +12 -8
  41. data/lib/net/ssh/proxy/http.rb +92 -91
  42. data/lib/net/ssh/proxy/https.rb +42 -39
  43. data/lib/net/ssh/proxy/jump.rb +50 -47
  44. data/lib/net/ssh/proxy/socks4.rb +0 -2
  45. data/lib/net/ssh/proxy/socks5.rb +11 -11
  46. data/lib/net/ssh/ruby_compat.rb +1 -0
  47. data/lib/net/ssh/service/forward.rb +364 -362
  48. data/lib/net/ssh/test.rb +85 -83
  49. data/lib/net/ssh/test/channel.rb +146 -142
  50. data/lib/net/ssh/test/extensions.rb +148 -146
  51. data/lib/net/ssh/test/kex.rb +35 -31
  52. data/lib/net/ssh/test/local_packet.rb +48 -44
  53. data/lib/net/ssh/test/packet.rb +87 -84
  54. data/lib/net/ssh/test/remote_packet.rb +35 -31
  55. data/lib/net/ssh/test/script.rb +173 -171
  56. data/lib/net/ssh/test/socket.rb +59 -55
  57. data/lib/net/ssh/transport/algorithms.rb +413 -412
  58. data/lib/net/ssh/transport/cipher_factory.rb +108 -105
  59. data/lib/net/ssh/transport/constants.rb +35 -31
  60. data/lib/net/ssh/transport/ctr.rb +1 -1
  61. data/lib/net/ssh/transport/hmac.rb +1 -1
  62. data/lib/net/ssh/transport/hmac/abstract.rb +67 -64
  63. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +1 -1
  64. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +1 -1
  65. data/lib/net/ssh/transport/identity_cipher.rb +55 -51
  66. data/lib/net/ssh/transport/kex.rb +2 -4
  67. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +47 -40
  68. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +201 -197
  69. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -56
  70. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +94 -87
  71. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +17 -10
  72. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +17 -10
  73. data/lib/net/ssh/transport/key_expander.rb +29 -25
  74. data/lib/net/ssh/transport/openssl.rb +17 -30
  75. data/lib/net/ssh/transport/packet_stream.rb +193 -192
  76. data/lib/net/ssh/transport/server_version.rb +64 -66
  77. data/lib/net/ssh/transport/session.rb +286 -284
  78. data/lib/net/ssh/transport/state.rb +198 -196
  79. data/lib/net/ssh/verifiers/lenient.rb +29 -25
  80. data/lib/net/ssh/verifiers/null.rb +13 -9
  81. data/lib/net/ssh/verifiers/secure.rb +45 -45
  82. data/lib/net/ssh/verifiers/strict.rb +20 -16
  83. data/lib/net/ssh/version.rb +55 -53
  84. data/net-ssh.gemspec +4 -4
  85. data/support/ssh_tunnel_bug.rb +2 -2
  86. metadata +25 -24
  87. metadata.gz.sig +0 -0
@@ -3,62 +3,66 @@ require 'stringio'
3
3
  require 'net/ssh/test/extensions'
4
4
  require 'net/ssh/test/script'
5
5
 
6
- module Net; module SSH; module Test
6
+ module Net
7
+ module SSH
8
+ module Test
7
9
 
8
- # A mock socket implementation for use in testing. It implements the minimum
9
- # necessary interface for interacting with the rest of the Net::SSH::Test
10
- # system.
11
- class Socket < StringIO
12
- attr_reader :host, :port
13
-
14
- # The Net::SSH::Test::Script object in use by this socket. This is the
15
- # canonical script instance that should be used for any test depending on
16
- # this socket instance.
17
- attr_reader :script
18
-
19
- # Create a new test socket. This will also instantiate a new Net::SSH::Test::Script
20
- # and seed it with the necessary events to power the initialization of the
21
- # connection.
22
- def initialize
23
- extend(Net::SSH::Transport::PacketStream)
24
- super "SSH-2.0-Test\r\n"
25
-
26
- @script = Script.new
27
-
28
- script.sends(:kexinit)
29
- script.gets(:kexinit, 1, 2, 3, 4, "test", "ssh-rsa", "none", "none", "none", "none", "none", "none", "", "", false)
30
- script.sends(:newkeys)
31
- script.gets(:newkeys)
32
- end
33
-
34
- # This doesn't actually do anything, since we don't really care what gets
35
- # written.
36
- def write(data)
37
- # black hole, because we don't actually care about what gets written
38
- end
39
-
40
- # Allows the socket to also mimic a socket factory, simply returning
41
- # +self+.
42
- def open(host, port, options={})
43
- @host, @port = host, port
44
- self
45
- end
46
-
47
- # Returns a sockaddr struct for the port and host that were used when the
48
- # socket was instantiated.
49
- def getpeername
50
- ::Socket.sockaddr_in(port, host)
51
- end
52
-
53
- # Alias to #read, but never returns nil (returns an empty string instead).
54
- def recv(n)
55
- read(n) || ""
56
- end
10
+ # A mock socket implementation for use in testing. It implements the minimum
11
+ # necessary interface for interacting with the rest of the Net::SSH::Test
12
+ # system.
13
+ class Socket < StringIO
14
+ attr_reader :host, :port
15
+
16
+ # The Net::SSH::Test::Script object in use by this socket. This is the
17
+ # canonical script instance that should be used for any test depending on
18
+ # this socket instance.
19
+ attr_reader :script
20
+
21
+ # Create a new test socket. This will also instantiate a new Net::SSH::Test::Script
22
+ # and seed it with the necessary events to power the initialization of the
23
+ # connection.
24
+ def initialize
25
+ extend(Net::SSH::Transport::PacketStream)
26
+ super "SSH-2.0-Test\r\n"
27
+
28
+ @script = Script.new
29
+
30
+ script.sends(:kexinit)
31
+ script.gets(:kexinit, 1, 2, 3, 4, "test", "ssh-rsa", "none", "none", "none", "none", "none", "none", "", "", false)
32
+ script.sends(:newkeys)
33
+ script.gets(:newkeys)
34
+ end
35
+
36
+ # This doesn't actually do anything, since we don't really care what gets
37
+ # written.
38
+ def write(data)
39
+ # black hole, because we don't actually care about what gets written
40
+ end
41
+
42
+ # Allows the socket to also mimic a socket factory, simply returning
43
+ # +self+.
44
+ def open(host, port, options={})
45
+ @host, @port = host, port
46
+ self
47
+ end
48
+
49
+ # Returns a sockaddr struct for the port and host that were used when the
50
+ # socket was instantiated.
51
+ def getpeername
52
+ ::Socket.sockaddr_in(port, host)
53
+ end
54
+
55
+ # Alias to #read, but never returns nil (returns an empty string instead).
56
+ def recv(n)
57
+ read(n) || ""
58
+ end
59
+
60
+ def readpartial(n)
61
+ recv(n)
62
+ end
63
+
64
+ end
57
65
 
58
- def readpartial(n)
59
- recv(n)
60
66
  end
61
-
62
67
  end
63
-
64
- end; end; end
68
+ end
@@ -8,426 +8,427 @@ require 'net/ssh/transport/kex'
8
8
  require 'net/ssh/transport/server_version'
9
9
  require 'net/ssh/authentication/ed25519_loader'
10
10
 
11
- module Net; module SSH; module Transport
12
-
13
- # Implements the higher-level logic behind an SSH key-exchange. It handles
14
- # both the initial exchange, as well as subsequent re-exchanges (as needed).
15
- # It also encapsulates the negotiation of the algorithms, and provides a
16
- # single point of access to the negotiated algorithms.
17
- #
18
- # You will never instantiate or reference this directly. It is used
19
- # internally by the transport layer.
20
- class Algorithms
21
- include Constants, Loggable
22
-
23
- # Define the default algorithms, in order of preference, supported by
24
- # Net::SSH.
25
- ALGORITHMS = {
26
- host_key: %w(ssh-rsa ssh-dss
27
- ssh-rsa-cert-v01@openssh.com
28
- ssh-rsa-cert-v00@openssh.com),
29
- kex: %w(diffie-hellman-group-exchange-sha1
30
- diffie-hellman-group1-sha1
31
- diffie-hellman-group14-sha1
32
- diffie-hellman-group-exchange-sha256),
33
- encryption: %w(aes128-cbc 3des-cbc blowfish-cbc cast128-cbc
34
- aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se
35
- idea-cbc arcfour128 arcfour256 arcfour
36
- aes128-ctr aes192-ctr aes256-ctr
37
- cast128-ctr blowfish-ctr 3des-ctr none),
38
-
39
- hmac: %w(hmac-sha1 hmac-md5 hmac-sha1-96 hmac-md5-96
40
- hmac-ripemd160 hmac-ripemd160@openssh.com
41
- hmac-sha2-256 hmac-sha2-512 hmac-sha2-256-96
42
- hmac-sha2-512-96 none),
43
-
44
- compression: %w(none zlib@openssh.com zlib),
45
- language: %w()
46
- }
47
- if defined?(OpenSSL::PKey::EC)
48
- ALGORITHMS[:host_key] += %w(ecdsa-sha2-nistp256
49
- ecdsa-sha2-nistp384
50
- ecdsa-sha2-nistp521)
51
- if Net::SSH::Authentication::ED25519Loader::LOADED
52
- ALGORITHMS[:host_key] += %w(ssh-ed25519)
53
- end
54
- ALGORITHMS[:kex] += %w(ecdh-sha2-nistp256
55
- ecdh-sha2-nistp384
56
- ecdh-sha2-nistp521)
57
- end
58
-
59
- # The underlying transport layer session that supports this object
60
- attr_reader :session
61
-
62
- # The hash of options used to initialize this object
63
- attr_reader :options
64
-
65
- # The kex algorithm to use settled on between the client and server.
66
- attr_reader :kex
67
-
68
- # The type of host key that will be used for this session.
69
- attr_reader :host_key
70
-
71
- # The type of the cipher to use to encrypt packets sent from the client to
72
- # the server.
73
- attr_reader :encryption_client
74
-
75
- # The type of the cipher to use to decrypt packets arriving from the server.
76
- attr_reader :encryption_server
77
-
78
- # The type of HMAC to use to sign packets sent by the client.
79
- attr_reader :hmac_client
80
-
81
- # The type of HMAC to use to validate packets arriving from the server.
82
- attr_reader :hmac_server
83
-
84
- # The type of compression to use to compress packets being sent by the client.
85
- attr_reader :compression_client
86
-
87
- # The type of compression to use to decompress packets arriving from the server.
88
- attr_reader :compression_server
89
-
90
- # The language that will be used in messages sent by the client.
91
- attr_reader :language_client
92
-
93
- # The language that will be used in messages sent from the server.
94
- attr_reader :language_server
95
-
96
- # The hash of algorithms preferred by the client, which will be told to
97
- # the server during algorithm negotiation.
98
- attr_reader :algorithms
99
-
100
- # The session-id for this session, as decided during the initial key exchange.
101
- attr_reader :session_id
102
-
103
- # Returns true if the given packet can be processed during a key-exchange.
104
- def self.allowed_packet?(packet)
105
- ( 1.. 4).include?(packet.type) ||
106
- ( 6..19).include?(packet.type) ||
107
- (21..49).include?(packet.type)
108
- end
109
-
110
- # Instantiates a new Algorithms object, and prepares the hash of preferred
111
- # algorithms based on the options parameter and the ALGORITHMS constant.
112
- def initialize(session, options={})
113
- @session = session
114
- @logger = session.logger
115
- @options = options
116
- @algorithms = {}
117
- @pending = @initialized = false
118
- @client_packet = @server_packet = nil
119
- prepare_preferred_algorithms!
120
- end
121
-
122
- # Start the algorithm negotation
123
- def start
124
- raise ArgumentError, "Cannot call start if it's negotiation started or done" if @pending || @initialized
125
- send_kexinit
126
- end
127
-
128
- # Request a rekey operation. This will return immediately, and does not
129
- # actually perform the rekey operation. It does cause the session to change
130
- # state, however--until the key exchange finishes, no new packets will be
131
- # processed.
132
- def rekey!
133
- @client_packet = @server_packet = nil
134
- @initialized = false
135
- send_kexinit
136
- end
137
-
138
- # Called by the transport layer when a KEXINIT packet is received, indicating
139
- # that the server wants to exchange keys. This can be spontaneous, or it
140
- # can be in response to a client-initiated rekey request (see #rekey!). Either
141
- # way, this will block until the key exchange completes.
142
- def accept_kexinit(packet)
143
- info { "got KEXINIT from server" }
144
- @server_data = parse_server_algorithm_packet(packet)
145
- @server_packet = @server_data[:raw]
146
- if !pending?
147
- send_kexinit
148
- else
149
- proceed!
150
- end
151
- end
152
-
153
- # A convenience method for accessing the list of preferred types for a
154
- # specific algorithm (see #algorithms).
155
- def [](key)
156
- algorithms[key]
157
- end
158
-
159
- # Returns +true+ if a key-exchange is pending. This will be true from the
160
- # moment either the client or server requests the key exchange, until the
161
- # exchange completes. While an exchange is pending, only a limited number
162
- # of packets are allowed, so event processing essentially stops during this
163
- # period.
164
- def pending?
165
- @pending
166
- end
167
-
168
- # Returns true if no exchange is pending, and otherwise returns true or
169
- # false depending on whether the given packet is of a type that is allowed
170
- # during a key exchange.
171
- def allow?(packet)
172
- !pending? || Algorithms.allowed_packet?(packet)
173
- end
174
-
175
- # Returns true if the algorithms have been negotiated at all.
176
- def initialized?
177
- @initialized
178
- end
179
-
180
- private
181
-
182
- # Sends a KEXINIT packet to the server. If a server KEXINIT has already
183
- # been received, this will then invoke #proceed! to proceed with the key
184
- # exchange, otherwise it returns immediately (but sets the object to the
185
- # pending state).
186
- def send_kexinit
187
- info { "sending KEXINIT" }
188
- @pending = true
189
- packet = build_client_algorithm_packet
190
- @client_packet = packet.to_s
191
- session.send_message(packet)
192
- proceed! if @server_packet
193
- end
194
-
195
- # After both client and server have sent their KEXINIT packets, this
196
- # will do the algorithm negotiation and key exchange. Once both finish,
197
- # the object leaves the pending state and the method returns.
198
- def proceed!
199
- info { "negotiating algorithms" }
200
- negotiate_algorithms
201
- exchange_keys
202
- @pending = false
203
- end
204
-
205
- # Prepares the list of preferred algorithms, based on the options hash
206
- # that was given when the object was constructed, and the ALGORITHMS
207
- # constant. Also, when determining the host_key type to use, the known
208
- # hosts files are examined to see if the host has ever sent a host_key
209
- # before, and if so, that key type is used as the preferred type for
210
- # communicating with this server.
211
- def prepare_preferred_algorithms!
212
- options[:compression] = %w(zlib@openssh.com zlib) if options[:compression] == true
213
-
214
- ALGORITHMS.each do |algorithm, supported|
215
- algorithms[algorithm] = compose_algorithm_list(supported, options[algorithm], options[:append_all_supported_algorithms])
11
+ module Net
12
+ module SSH
13
+ module Transport
14
+
15
+ # Implements the higher-level logic behind an SSH key-exchange. It handles
16
+ # both the initial exchange, as well as subsequent re-exchanges (as needed).
17
+ # It also encapsulates the negotiation of the algorithms, and provides a
18
+ # single point of access to the negotiated algorithms.
19
+ #
20
+ # You will never instantiate or reference this directly. It is used
21
+ # internally by the transport layer.
22
+ class Algorithms
23
+ include Loggable
24
+ include Constants
25
+
26
+ # Define the default algorithms, in order of preference, supported by
27
+ # Net::SSH.
28
+ ALGORITHMS = {
29
+ host_key: %w[ssh-rsa ssh-dss
30
+ ssh-rsa-cert-v01@openssh.com
31
+ ssh-rsa-cert-v00@openssh.com],
32
+ kex: %w[diffie-hellman-group-exchange-sha1
33
+ diffie-hellman-group1-sha1
34
+ diffie-hellman-group14-sha1
35
+ diffie-hellman-group-exchange-sha256],
36
+ encryption: %w[aes128-cbc 3des-cbc blowfish-cbc cast128-cbc
37
+ aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se
38
+ idea-cbc arcfour128 arcfour256 arcfour
39
+ aes128-ctr aes192-ctr aes256-ctr
40
+ cast128-ctr blowfish-ctr 3des-ctr none],
41
+
42
+ hmac: %w[hmac-sha1 hmac-md5 hmac-sha1-96 hmac-md5-96
43
+ hmac-ripemd160 hmac-ripemd160@openssh.com
44
+ hmac-sha2-256 hmac-sha2-512 hmac-sha2-256-96
45
+ hmac-sha2-512-96 none],
46
+
47
+ compression: %w[none zlib@openssh.com zlib],
48
+ language: %w[]
49
+ }
50
+ if defined?(OpenSSL::PKey::EC)
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]
216
58
  end
217
-
218
- # for convention, make sure our list has the same keys as the server
219
- # list
220
-
221
- algorithms[:encryption_client ] = algorithms[:encryption_server ] = algorithms[:encryption]
222
- algorithms[:hmac_client ] = algorithms[:hmac_server ] = algorithms[:hmac]
223
- algorithms[:compression_client] = algorithms[:compression_server] = algorithms[:compression]
224
- algorithms[:language_client ] = algorithms[:language_server ] = algorithms[:language]
225
-
226
- if !options.key?(:host_key)
227
- # make sure the host keys are specified in preference order, where any
228
- # existing known key for the host has preference.
229
-
230
- existing_keys = session.host_keys
231
- host_keys = existing_keys.map { |key| key.ssh_type }.uniq
232
- algorithms[:host_key].each do |name|
233
- host_keys << name unless host_keys.include?(name)
59
+
60
+ # The underlying transport layer session that supports this object
61
+ attr_reader :session
62
+
63
+ # The hash of options used to initialize this object
64
+ attr_reader :options
65
+
66
+ # The kex algorithm to use settled on between the client and server.
67
+ attr_reader :kex
68
+
69
+ # The type of host key that will be used for this session.
70
+ attr_reader :host_key
71
+
72
+ # The type of the cipher to use to encrypt packets sent from the client to
73
+ # the server.
74
+ attr_reader :encryption_client
75
+
76
+ # The type of the cipher to use to decrypt packets arriving from the server.
77
+ attr_reader :encryption_server
78
+
79
+ # The type of HMAC to use to sign packets sent by the client.
80
+ attr_reader :hmac_client
81
+
82
+ # The type of HMAC to use to validate packets arriving from the server.
83
+ attr_reader :hmac_server
84
+
85
+ # The type of compression to use to compress packets being sent by the client.
86
+ attr_reader :compression_client
87
+
88
+ # The type of compression to use to decompress packets arriving from the server.
89
+ attr_reader :compression_server
90
+
91
+ # The language that will be used in messages sent by the client.
92
+ attr_reader :language_client
93
+
94
+ # The language that will be used in messages sent from the server.
95
+ attr_reader :language_server
96
+
97
+ # The hash of algorithms preferred by the client, which will be told to
98
+ # the server during algorithm negotiation.
99
+ attr_reader :algorithms
100
+
101
+ # The session-id for this session, as decided during the initial key exchange.
102
+ attr_reader :session_id
103
+
104
+ # Returns true if the given packet can be processed during a key-exchange.
105
+ def self.allowed_packet?(packet)
106
+ (1..4).include?(packet.type) ||
107
+ (6..19).include?(packet.type) ||
108
+ (21..49).include?(packet.type)
109
+ end
110
+
111
+ # Instantiates a new Algorithms object, and prepares the hash of preferred
112
+ # algorithms based on the options parameter and the ALGORITHMS constant.
113
+ def initialize(session, options={})
114
+ @session = session
115
+ @logger = session.logger
116
+ @options = options
117
+ @algorithms = {}
118
+ @pending = @initialized = false
119
+ @client_packet = @server_packet = nil
120
+ prepare_preferred_algorithms!
121
+ end
122
+
123
+ # Start the algorithm negotation
124
+ def start
125
+ raise ArgumentError, "Cannot call start if it's negotiation started or done" if @pending || @initialized
126
+ send_kexinit
127
+ end
128
+
129
+ # Request a rekey operation. This will return immediately, and does not
130
+ # actually perform the rekey operation. It does cause the session to change
131
+ # state, however--until the key exchange finishes, no new packets will be
132
+ # processed.
133
+ def rekey!
134
+ @client_packet = @server_packet = nil
135
+ @initialized = false
136
+ send_kexinit
137
+ end
138
+
139
+ # Called by the transport layer when a KEXINIT packet is received, indicating
140
+ # that the server wants to exchange keys. This can be spontaneous, or it
141
+ # can be in response to a client-initiated rekey request (see #rekey!). Either
142
+ # way, this will block until the key exchange completes.
143
+ def accept_kexinit(packet)
144
+ info { "got KEXINIT from server" }
145
+ @server_data = parse_server_algorithm_packet(packet)
146
+ @server_packet = @server_data[:raw]
147
+ if !pending?
148
+ send_kexinit
149
+ else
150
+ proceed!
234
151
  end
235
- algorithms[:host_key] = host_keys
236
152
  end
237
- end
238
-
239
- # Composes the list of algorithms by taking supported algorithms and matching with supplied options.
240
- def compose_algorithm_list(supported, option, append_all_supported_algorithms = false)
241
- return supported.dup unless option
242
-
243
- list = []
244
- option = Array(option).compact.uniq
245
-
246
- if option.first && option.first.start_with?('+')
247
- list = supported.dup
248
- list << option.first[1..-1]
249
- list.concat(option[1..-1])
250
- list.uniq!
251
- else
252
- list = option
253
-
254
- if append_all_supported_algorithms
255
- supported.each { |name| list << name unless list.include?(name) }
153
+
154
+ # A convenience method for accessing the list of preferred types for a
155
+ # specific algorithm (see #algorithms).
156
+ def [](key)
157
+ algorithms[key]
158
+ end
159
+
160
+ # Returns +true+ if a key-exchange is pending. This will be true from the
161
+ # moment either the client or server requests the key exchange, until the
162
+ # exchange completes. While an exchange is pending, only a limited number
163
+ # of packets are allowed, so event processing essentially stops during this
164
+ # period.
165
+ def pending?
166
+ @pending
167
+ end
168
+
169
+ # Returns true if no exchange is pending, and otherwise returns true or
170
+ # false depending on whether the given packet is of a type that is allowed
171
+ # during a key exchange.
172
+ def allow?(packet)
173
+ !pending? || Algorithms.allowed_packet?(packet)
174
+ end
175
+
176
+ # Returns true if the algorithms have been negotiated at all.
177
+ def initialized?
178
+ @initialized
179
+ end
180
+
181
+ private
182
+
183
+ # Sends a KEXINIT packet to the server. If a server KEXINIT has already
184
+ # been received, this will then invoke #proceed! to proceed with the key
185
+ # exchange, otherwise it returns immediately (but sets the object to the
186
+ # pending state).
187
+ def send_kexinit
188
+ info { "sending KEXINIT" }
189
+ @pending = true
190
+ packet = build_client_algorithm_packet
191
+ @client_packet = packet.to_s
192
+ session.send_message(packet)
193
+ proceed! if @server_packet
194
+ end
195
+
196
+ # After both client and server have sent their KEXINIT packets, this
197
+ # will do the algorithm negotiation and key exchange. Once both finish,
198
+ # the object leaves the pending state and the method returns.
199
+ def proceed!
200
+ info { "negotiating algorithms" }
201
+ negotiate_algorithms
202
+ exchange_keys
203
+ @pending = false
204
+ end
205
+
206
+ # Prepares the list of preferred algorithms, based on the options hash
207
+ # that was given when the object was constructed, and the ALGORITHMS
208
+ # constant. Also, when determining the host_key type to use, the known
209
+ # hosts files are examined to see if the host has ever sent a host_key
210
+ # before, and if so, that key type is used as the preferred type for
211
+ # communicating with this server.
212
+ def prepare_preferred_algorithms!
213
+ options[:compression] = %w[zlib@openssh.com zlib] if options[:compression] == true
214
+
215
+ ALGORITHMS.each do |algorithm, supported|
216
+ algorithms[algorithm] = compose_algorithm_list(supported, options[algorithm], options[:append_all_supported_algorithms])
217
+ end
218
+
219
+ # for convention, make sure our list has the same keys as the server
220
+ # list
221
+
222
+ algorithms[:encryption_client ] = algorithms[:encryption_server ] = algorithms[:encryption]
223
+ algorithms[:hmac_client ] = algorithms[:hmac_server ] = algorithms[:hmac]
224
+ algorithms[:compression_client] = algorithms[:compression_server] = algorithms[:compression]
225
+ algorithms[:language_client ] = algorithms[:language_server ] = algorithms[:language]
226
+
227
+ if !options.key?(:host_key)
228
+ # make sure the host keys are specified in preference order, where any
229
+ # existing known key for the host has preference.
230
+
231
+ existing_keys = session.host_keys
232
+ host_keys = existing_keys.map { |key| key.ssh_type }.uniq
233
+ algorithms[:host_key].each do |name|
234
+ host_keys << name unless host_keys.include?(name)
235
+ end
236
+ algorithms[:host_key] = host_keys
256
237
  end
257
238
  end
258
-
259
- unsupported = []
260
- list.select! do |name|
261
- is_supported = supported.include?(name)
262
- unsupported << name unless is_supported
263
- is_supported
239
+
240
+ # Composes the list of algorithms by taking supported algorithms and matching with supplied options.
241
+ def compose_algorithm_list(supported, option, append_all_supported_algorithms = false)
242
+ return supported.dup unless option
243
+
244
+ list = []
245
+ option = Array(option).compact.uniq
246
+
247
+ if option.first && option.first.start_with?('+')
248
+ list = supported.dup
249
+ list << option.first[1..-1]
250
+ list.concat(option[1..-1])
251
+ list.uniq!
252
+ else
253
+ list = option
254
+
255
+ if append_all_supported_algorithms
256
+ supported.each { |name| list << name unless list.include?(name) }
257
+ end
258
+ end
259
+
260
+ unsupported = []
261
+ list.select! do |name|
262
+ is_supported = supported.include?(name)
263
+ unsupported << name unless is_supported
264
+ is_supported
265
+ end
266
+
267
+ lwarn { %(unsupported algorithm: `#{unsupported}') } unless unsupported.empty?
268
+
269
+ list
264
270
  end
265
-
266
- lwarn { %(unsupported algorithm: `#{unsupported}') } unless unsupported.empty?
267
-
268
- list
269
- end
270
-
271
- # Parses a KEXINIT packet from the server.
272
- def parse_server_algorithm_packet(packet)
273
- data = { raw: packet.content }
274
-
275
- packet.read(16) # skip the cookie value
276
-
277
- data[:kex] = packet.read_string.split(/,/)
278
- data[:host_key] = packet.read_string.split(/,/)
279
- data[:encryption_client] = packet.read_string.split(/,/)
280
- data[:encryption_server] = packet.read_string.split(/,/)
281
- data[:hmac_client] = packet.read_string.split(/,/)
282
- data[:hmac_server] = packet.read_string.split(/,/)
283
- data[:compression_client] = packet.read_string.split(/,/)
284
- data[:compression_server] = packet.read_string.split(/,/)
285
- data[:language_client] = packet.read_string.split(/,/)
286
- data[:language_server] = packet.read_string.split(/,/)
287
-
288
- # TODO: if first_kex_packet_follows, we need to try to skip the
289
- # actual kexinit stuff and try to guess what the server is doing...
290
- # need to read more about this scenario.
291
- # first_kex_packet_follows = packet.read_bool
292
-
293
- return data
294
- end
295
-
296
- # Given the #algorithms map of preferred algorithm types, this constructs
297
- # a KEXINIT packet to send to the server. It does not actually send it,
298
- # it simply builds the packet and returns it.
299
- def build_client_algorithm_packet
300
- kex = algorithms[:kex ].join(",")
301
- host_key = algorithms[:host_key ].join(",")
302
- encryption = algorithms[:encryption ].join(",")
303
- hmac = algorithms[:hmac ].join(",")
304
- compression = algorithms[:compression].join(",")
305
- language = algorithms[:language ].join(",")
306
-
307
- Net::SSH::Buffer.from(:byte, KEXINIT,
308
- :long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)],
309
- :mstring, [kex, host_key, encryption, encryption, hmac, hmac],
310
- :mstring, [compression, compression, language, language],
311
- :bool, false, :long, 0)
312
- end
313
-
314
- # Given the parsed server KEX packet, and the client's preferred algorithm
315
- # lists in #algorithms, determine which preferred algorithms each has
316
- # in common and set those as the selected algorithms. If, for any algorithm,
317
- # no type can be settled on, an exception is raised.
318
- def negotiate_algorithms
319
- @kex = negotiate(:kex)
320
- @host_key = negotiate(:host_key)
321
- @encryption_client = negotiate(:encryption_client)
322
- @encryption_server = negotiate(:encryption_server)
323
- @hmac_client = negotiate(:hmac_client)
324
- @hmac_server = negotiate(:hmac_server)
325
- @compression_client = negotiate(:compression_client)
326
- @compression_server = negotiate(:compression_server)
327
- @language_client = negotiate(:language_client) rescue ""
328
- @language_server = negotiate(:language_server) rescue ""
329
-
330
- debug do
331
- "negotiated:\n" +
332
- [:kex, :host_key, :encryption_server, :encryption_client, :hmac_client, :hmac_server, :compression_client, :compression_server, :language_client, :language_server].map do |key|
333
- "* #{key}: #{instance_variable_get("@#{key}")}"
334
- end.join("\n")
271
+
272
+ # Parses a KEXINIT packet from the server.
273
+ def parse_server_algorithm_packet(packet)
274
+ data = { raw: packet.content }
275
+
276
+ packet.read(16) # skip the cookie value
277
+
278
+ data[:kex] = packet.read_string.split(/,/)
279
+ data[:host_key] = packet.read_string.split(/,/)
280
+ data[:encryption_client] = packet.read_string.split(/,/)
281
+ data[:encryption_server] = packet.read_string.split(/,/)
282
+ data[:hmac_client] = packet.read_string.split(/,/)
283
+ data[:hmac_server] = packet.read_string.split(/,/)
284
+ data[:compression_client] = packet.read_string.split(/,/)
285
+ data[:compression_server] = packet.read_string.split(/,/)
286
+ data[:language_client] = packet.read_string.split(/,/)
287
+ data[:language_server] = packet.read_string.split(/,/)
288
+
289
+ # TODO: if first_kex_packet_follows, we need to try to skip the
290
+ # actual kexinit stuff and try to guess what the server is doing...
291
+ # need to read more about this scenario.
292
+ # first_kex_packet_follows = packet.read_bool
293
+
294
+ return data
335
295
  end
336
- end
337
-
338
- # Negotiates a single algorithm based on the preferences reported by the
339
- # server and those set by the client. This is called by
340
- # #negotiate_algorithms.
341
- def negotiate(algorithm)
342
- match = self[algorithm].find { |item| @server_data[algorithm].include?(item) }
343
-
344
- if match.nil?
345
- raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm"
296
+
297
+ # Given the #algorithms map of preferred algorithm types, this constructs
298
+ # a KEXINIT packet to send to the server. It does not actually send it,
299
+ # it simply builds the packet and returns it.
300
+ def build_client_algorithm_packet
301
+ kex = algorithms[:kex].join(",")
302
+ host_key = algorithms[:host_key].join(",")
303
+ encryption = algorithms[:encryption].join(",")
304
+ hmac = algorithms[:hmac].join(",")
305
+ compression = algorithms[:compression].join(",")
306
+ language = algorithms[:language].join(",")
307
+
308
+ Net::SSH::Buffer.from(:byte, KEXINIT,
309
+ :long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)],
310
+ :mstring, [kex, host_key, encryption, encryption, hmac, hmac],
311
+ :mstring, [compression, compression, language, language],
312
+ :bool, false, :long, 0)
346
313
  end
347
-
348
- return match
349
- end
350
-
351
- # Considers the sizes of the keys and block-sizes for the selected ciphers,
352
- # and the lengths of the hmacs, and returns the largest as the byte requirement
353
- # for the key-exchange algorithm.
354
- def kex_byte_requirement
355
- sizes = [8] # require at least 8 bytes
356
-
357
- sizes.concat(CipherFactory.get_lengths(encryption_client))
358
- sizes.concat(CipherFactory.get_lengths(encryption_server))
359
-
360
- sizes << HMAC.key_length(hmac_client)
361
- sizes << HMAC.key_length(hmac_server)
362
-
363
- sizes.max
364
- end
365
-
366
- # Instantiates one of the Transport::Kex classes (based on the negotiated
367
- # kex algorithm), and uses it to exchange keys. Then, the ciphers and
368
- # HMACs are initialized and fed to the transport layer, to be used in
369
- # further communication with the server.
370
- def exchange_keys
371
- debug { "exchanging keys" }
372
-
373
- algorithm = Kex::MAP[kex].new(self, session,
374
- client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION,
375
- server_version_string: session.server_version.version,
376
- server_algorithm_packet: @server_packet,
377
- client_algorithm_packet: @client_packet,
378
- need_bytes: kex_byte_requirement,
379
- minimum_dh_bits: options[:minimum_dh_bits],
380
- logger: logger)
381
- result = algorithm.exchange_keys
382
-
383
- secret = result[:shared_secret].to_ssh
384
- hash = result[:session_id]
385
- digester = result[:hashing_algorithm]
386
-
387
- @session_id ||= hash
388
-
389
- key = Proc.new { |salt| digester.digest(secret + hash + salt + @session_id) }
390
-
391
- iv_client = key["A"]
392
- iv_server = key["B"]
393
- key_client = key["C"]
394
- key_server = key["D"]
395
- mac_key_client = key["E"]
396
- mac_key_server = key["F"]
397
-
398
- parameters = { shared: secret, hash: hash, digester: digester }
399
-
400
- cipher_client = CipherFactory.get(encryption_client, parameters.merge(iv: iv_client, key: key_client, encrypt: true))
401
- cipher_server = CipherFactory.get(encryption_server, parameters.merge(iv: iv_server, key: key_server, decrypt: true))
402
-
403
- mac_client = HMAC.get(hmac_client, mac_key_client, parameters)
404
- mac_server = HMAC.get(hmac_server, mac_key_server, parameters)
405
-
406
- session.configure_client cipher: cipher_client, hmac: mac_client,
407
- compression: normalize_compression_name(compression_client),
408
- compression_level: options[:compression_level],
409
- rekey_limit: options[:rekey_limit],
410
- max_packets: options[:rekey_packet_limit],
411
- max_blocks: options[:rekey_blocks_limit]
412
-
413
- session.configure_server cipher: cipher_server, hmac: mac_server,
414
- compression: normalize_compression_name(compression_server),
415
- rekey_limit: options[:rekey_limit],
416
- max_packets: options[:rekey_packet_limit],
417
- max_blocks: options[:rekey_blocks_limit]
418
-
419
- @initialized = true
420
- end
421
-
422
- # Given the SSH name for some compression algorithm, return a normalized
423
- # name as a symbol.
424
- def normalize_compression_name(name)
425
- case name
426
- when "none" then false
427
- when "zlib" then :standard
428
- when "zlib@openssh.com" then :delayed
429
- else raise ArgumentError, "unknown compression type `#{name}'"
314
+
315
+ # Given the parsed server KEX packet, and the client's preferred algorithm
316
+ # lists in #algorithms, determine which preferred algorithms each has
317
+ # in common and set those as the selected algorithms. If, for any algorithm,
318
+ # no type can be settled on, an exception is raised.
319
+ def negotiate_algorithms
320
+ @kex = negotiate(:kex)
321
+ @host_key = negotiate(:host_key)
322
+ @encryption_client = negotiate(:encryption_client)
323
+ @encryption_server = negotiate(:encryption_server)
324
+ @hmac_client = negotiate(:hmac_client)
325
+ @hmac_server = negotiate(:hmac_server)
326
+ @compression_client = negotiate(:compression_client)
327
+ @compression_server = negotiate(:compression_server)
328
+ @language_client = negotiate(:language_client) rescue ""
329
+ @language_server = negotiate(:language_server) rescue ""
330
+
331
+ debug do
332
+ "negotiated:\n" +
333
+ %i[kex host_key encryption_server encryption_client hmac_client hmac_server compression_client compression_server language_client language_server].map do |key|
334
+ "* #{key}: #{instance_variable_get("@#{key}")}"
335
+ end.join("\n")
336
+ end
337
+ end
338
+
339
+ # Negotiates a single algorithm based on the preferences reported by the
340
+ # server and those set by the client. This is called by
341
+ # #negotiate_algorithms.
342
+ def negotiate(algorithm)
343
+ match = self[algorithm].find { |item| @server_data[algorithm].include?(item) }
344
+
345
+ raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm" if match.nil?
346
+
347
+ return match
348
+ end
349
+
350
+ # Considers the sizes of the keys and block-sizes for the selected ciphers,
351
+ # and the lengths of the hmacs, and returns the largest as the byte requirement
352
+ # for the key-exchange algorithm.
353
+ def kex_byte_requirement
354
+ sizes = [8] # require at least 8 bytes
355
+
356
+ sizes.concat(CipherFactory.get_lengths(encryption_client))
357
+ sizes.concat(CipherFactory.get_lengths(encryption_server))
358
+
359
+ sizes << HMAC.key_length(hmac_client)
360
+ sizes << HMAC.key_length(hmac_server)
361
+
362
+ sizes.max
363
+ end
364
+
365
+ # Instantiates one of the Transport::Kex classes (based on the negotiated
366
+ # kex algorithm), and uses it to exchange keys. Then, the ciphers and
367
+ # HMACs are initialized and fed to the transport layer, to be used in
368
+ # further communication with the server.
369
+ def exchange_keys
370
+ debug { "exchanging keys" }
371
+
372
+ algorithm = Kex::MAP[kex].new(self, session,
373
+ client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION,
374
+ server_version_string: session.server_version.version,
375
+ server_algorithm_packet: @server_packet,
376
+ client_algorithm_packet: @client_packet,
377
+ need_bytes: kex_byte_requirement,
378
+ minimum_dh_bits: options[:minimum_dh_bits],
379
+ logger: logger)
380
+ result = algorithm.exchange_keys
381
+
382
+ secret = result[:shared_secret].to_ssh
383
+ hash = result[:session_id]
384
+ digester = result[:hashing_algorithm]
385
+
386
+ @session_id ||= hash
387
+
388
+ key = Proc.new { |salt| digester.digest(secret + hash + salt + @session_id) }
389
+
390
+ iv_client = key["A"]
391
+ iv_server = key["B"]
392
+ key_client = key["C"]
393
+ key_server = key["D"]
394
+ mac_key_client = key["E"]
395
+ mac_key_server = key["F"]
396
+
397
+ parameters = { shared: secret, hash: hash, digester: digester }
398
+
399
+ cipher_client = CipherFactory.get(encryption_client, parameters.merge(iv: iv_client, key: key_client, encrypt: true))
400
+ cipher_server = CipherFactory.get(encryption_server, parameters.merge(iv: iv_server, key: key_server, decrypt: true))
401
+
402
+ mac_client = HMAC.get(hmac_client, mac_key_client, parameters)
403
+ mac_server = HMAC.get(hmac_server, mac_key_server, parameters)
404
+
405
+ session.configure_client cipher: cipher_client, hmac: mac_client,
406
+ compression: normalize_compression_name(compression_client),
407
+ compression_level: options[:compression_level],
408
+ rekey_limit: options[:rekey_limit],
409
+ max_packets: options[:rekey_packet_limit],
410
+ max_blocks: options[:rekey_blocks_limit]
411
+
412
+ session.configure_server cipher: cipher_server, hmac: mac_server,
413
+ compression: normalize_compression_name(compression_server),
414
+ rekey_limit: options[:rekey_limit],
415
+ max_packets: options[:rekey_packet_limit],
416
+ max_blocks: options[:rekey_blocks_limit]
417
+
418
+ @initialized = true
419
+ end
420
+
421
+ # Given the SSH name for some compression algorithm, return a normalized
422
+ # name as a symbol.
423
+ def normalize_compression_name(name)
424
+ case name
425
+ when "none" then false
426
+ when "zlib" then :standard
427
+ when "zlib@openssh.com" then :delayed
428
+ else raise ArgumentError, "unknown compression type `#{name}'"
429
+ end
430
430
  end
431
431
  end
432
+ end
432
433
  end
433
- end; end; end
434
+ end