net-ssh 5.0.2 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +8 -2
  6. data/.rubocop_todo.yml +392 -379
  7. data/.travis.yml +22 -22
  8. data/CHANGES.txt +63 -0
  9. data/Manifest +0 -1
  10. data/README.md +287 -0
  11. data/Rakefile +1 -2
  12. data/appveyor.yml +4 -2
  13. data/lib/net/ssh.rb +13 -4
  14. data/lib/net/ssh/authentication/agent.rb +9 -3
  15. data/lib/net/ssh/authentication/certificate.rb +10 -1
  16. data/lib/net/ssh/authentication/ed25519.rb +75 -46
  17. data/lib/net/ssh/authentication/ed25519_loader.rb +1 -1
  18. data/lib/net/ssh/authentication/key_manager.rb +35 -6
  19. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +3 -1
  20. data/lib/net/ssh/authentication/methods/publickey.rb +2 -0
  21. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +0 -1
  22. data/lib/net/ssh/authentication/session.rb +9 -6
  23. data/lib/net/ssh/buffer.rb +41 -10
  24. data/lib/net/ssh/buffered_io.rb +0 -1
  25. data/lib/net/ssh/config.rb +68 -35
  26. data/lib/net/ssh/connection/channel.rb +17 -5
  27. data/lib/net/ssh/connection/event_loop.rb +0 -1
  28. data/lib/net/ssh/connection/session.rb +7 -4
  29. data/lib/net/ssh/key_factory.rb +104 -17
  30. data/lib/net/ssh/known_hosts.rb +41 -26
  31. data/lib/net/ssh/loggable.rb +2 -2
  32. data/lib/net/ssh/proxy/command.rb +0 -1
  33. data/lib/net/ssh/proxy/socks5.rb +0 -1
  34. data/lib/net/ssh/service/forward.rb +2 -1
  35. data/lib/net/ssh/test.rb +8 -7
  36. data/lib/net/ssh/test/extensions.rb +2 -0
  37. data/lib/net/ssh/transport/algorithms.rb +161 -105
  38. data/lib/net/ssh/transport/cipher_factory.rb +11 -27
  39. data/lib/net/ssh/transport/constants.rb +10 -6
  40. data/lib/net/ssh/transport/ctr.rb +1 -7
  41. data/lib/net/ssh/transport/hmac.rb +15 -13
  42. data/lib/net/ssh/transport/hmac/abstract.rb +16 -0
  43. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  44. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  45. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  46. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  47. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  48. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  49. data/lib/net/ssh/transport/kex.rb +14 -11
  50. data/lib/net/ssh/transport/kex/abstract.rb +123 -0
  51. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  52. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +38 -0
  53. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  54. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +1 -15
  55. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +9 -118
  56. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +0 -6
  57. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  58. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +18 -79
  59. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +5 -4
  60. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +5 -4
  61. data/lib/net/ssh/transport/openssl.rb +106 -93
  62. data/lib/net/ssh/transport/packet_stream.rb +52 -20
  63. data/lib/net/ssh/transport/session.rb +26 -4
  64. data/lib/net/ssh/transport/state.rb +1 -1
  65. data/lib/net/ssh/verifiers/accept_new.rb +7 -0
  66. data/lib/net/ssh/verifiers/always.rb +4 -0
  67. data/lib/net/ssh/verifiers/never.rb +4 -0
  68. data/lib/net/ssh/version.rb +3 -3
  69. data/net-ssh-public_cert.pem +18 -19
  70. data/net-ssh.gemspec +9 -7
  71. metadata +56 -40
  72. metadata.gz.sig +0 -0
  73. data/Gemfile.noed25519.lock +0 -41
  74. data/README.rdoc +0 -169
  75. data/lib/net/ssh/ruby_compat.rb +0 -13
  76. data/support/arcfour_check.rb +0 -20
@@ -1,8 +1,9 @@
1
- module Net
2
- module SSH
3
- module Transport
4
- module Kex
1
+ require 'net/ssh/transport/kex/ecdh_sha2_nistp256'
5
2
 
3
+ module Net
4
+ module SSH
5
+ module Transport
6
+ module Kex
6
7
  # A key-exchange service implementing the "ecdh-sha2-nistp256"
7
8
  # key-exchange algorithm. (defined in RFC 5656)
8
9
  class EcdhSHA2NistP384 < EcdhSHA2NistP256
@@ -1,8 +1,9 @@
1
- module Net
2
- module SSH
3
- module Transport
4
- module Kex
1
+ require 'net/ssh/transport/kex/ecdh_sha2_nistp256'
5
2
 
3
+ module Net
4
+ module SSH
5
+ module Transport
6
+ module Kex
6
7
  # A key-exchange service implementing the "ecdh-sha2-nistp521"
7
8
  # key-exchange algorithm. (defined in RFC 5656)
8
9
  class EcdhSHA2NistP521 < EcdhSHA2NistP256
@@ -98,9 +98,9 @@ module OpenSSL
98
98
  sig_r = sig[0,20].unpack("H*")[0].to_i(16)
99
99
  sig_s = sig[20,20].unpack("H*")[0].to_i(16)
100
100
  a1sig = OpenSSL::ASN1::Sequence([
101
- OpenSSL::ASN1::Integer(sig_r),
102
- OpenSSL::ASN1::Integer(sig_s)
103
- ])
101
+ OpenSSL::ASN1::Integer(sig_r),
102
+ OpenSSL::ASN1::Integer(sig_s)
103
+ ])
104
104
  return verify(OpenSSL::Digest::SHA1.new, a1sig.to_der, data)
105
105
  end
106
106
 
@@ -121,115 +121,128 @@ module OpenSSL
121
121
  end
122
122
  end
123
123
 
124
- if defined?(OpenSSL::PKey::EC)
125
- # This class is originally defined in the OpenSSL module. As needed, methods
126
- # have been added to it by the Net::SSH module for convenience in dealing
127
- # with SSH functionality.
128
- class EC
129
- CurveNameAlias = {
130
- "nistp256" => "prime256v1",
131
- "nistp384" => "secp384r1",
132
- "nistp521" => "secp521r1"
133
- }
134
-
135
- CurveNameAliasInv = {
136
- "prime256v1" => "nistp256",
137
- "secp384r1" => "nistp384",
138
- "secp521r1" => "nistp521"
139
- }
140
-
141
- def self.read_keyblob(curve_name_in_type, buffer)
142
- curve_name_in_key = buffer.read_string
143
- raise Net::SSH::Exception, "curve name mismatched (`#{curve_name_in_key}' with `#{curve_name_in_type}')" unless curve_name_in_type == curve_name_in_key
144
- public_key_oct = buffer.read_string
145
- begin
146
- key = OpenSSL::PKey::EC.new(OpenSSL::PKey::EC::CurveNameAlias[curve_name_in_key])
147
- group = key.group
148
- point = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(public_key_oct, 2))
149
- key.public_key = point
150
-
151
- return key
152
- rescue OpenSSL::PKey::ECError
153
- raise NotImplementedError, "unsupported key type `#{type}'"
154
- end
124
+ # This class is originally defined in the OpenSSL module. As needed, methods
125
+ # have been added to it by the Net::SSH module for convenience in dealing
126
+ # with SSH functionality.
127
+ class EC
128
+ CurveNameAlias = {
129
+ 'nistp256' => 'prime256v1',
130
+ 'nistp384' => 'secp384r1',
131
+ 'nistp521' => 'secp521r1'
132
+ }.freeze
133
+
134
+ CurveNameAliasInv = {
135
+ 'prime256v1' => 'nistp256',
136
+ 'secp384r1' => 'nistp384',
137
+ 'secp521r1' => 'nistp521'
138
+ }.freeze
139
+
140
+ def self.read_keyblob(curve_name_in_type, buffer)
141
+ curve_name_in_key = buffer.read_string
142
+
143
+ unless curve_name_in_type == curve_name_in_key
144
+ raise Net::SSH::Exception, "curve name mismatched (`#{curve_name_in_key}' with `#{curve_name_in_type}')"
155
145
  end
156
146
 
157
- # Returns the description of this key type used by the
158
- # SSH2 protocol, like "ecdsa-sha2-nistp256"
159
- def ssh_type
160
- "ecdsa-sha2-#{CurveNameAliasInv[self.group.curve_name]}"
161
- end
147
+ public_key_oct = buffer.read_string
148
+ begin
149
+ key = OpenSSL::PKey::EC.new(OpenSSL::PKey::EC::CurveNameAlias[curve_name_in_key])
150
+ group = key.group
151
+ point = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(public_key_oct, 2))
152
+ key.public_key = point
162
153
 
163
- def ssh_signature_type
164
- ssh_type
154
+ return key
155
+ rescue OpenSSL::PKey::ECError
156
+ raise NotImplementedError, "unsupported key type `#{type}'"
165
157
  end
158
+ end
166
159
 
167
- def digester
168
- if self.group.curve_name =~ /^[a-z]+(\d+)\w*\z/
169
- curve_size = $1.to_i
170
- if curve_size <= 256
171
- OpenSSL::Digest::SHA256.new
172
- elsif curve_size <= 384
173
- OpenSSL::Digest::SHA384.new
174
- else
175
- OpenSSL::Digest::SHA512.new
176
- end
177
- else
160
+ # Returns the description of this key type used by the
161
+ # SSH2 protocol, like "ecdsa-sha2-nistp256"
162
+ def ssh_type
163
+ "ecdsa-sha2-#{CurveNameAliasInv[group.curve_name]}"
164
+ end
165
+
166
+ def ssh_signature_type
167
+ ssh_type
168
+ end
169
+
170
+ def digester
171
+ if group.curve_name =~ /^[a-z]+(\d+)\w*\z/
172
+ curve_size = Regexp.last_match(1).to_i
173
+ if curve_size <= 256
178
174
  OpenSSL::Digest::SHA256.new
175
+ elsif curve_size <= 384
176
+ OpenSSL::Digest::SHA384.new
177
+ else
178
+ OpenSSL::Digest::SHA512.new
179
179
  end
180
+ else
181
+ OpenSSL::Digest::SHA256.new
180
182
  end
181
- private :digester
183
+ end
184
+ private :digester
182
185
 
183
- # Converts the key to a blob, according to the SSH2 protocol.
184
- def to_blob
185
- @blob ||= Net::SSH::Buffer.from(:string, ssh_type,
186
- :string, CurveNameAliasInv[self.group.curve_name],
187
- :mstring, self.public_key.to_bn.to_s(2)).to_s
188
- @blob
189
- end
186
+ # Converts the key to a blob, according to the SSH2 protocol.
187
+ def to_blob
188
+ @blob ||= Net::SSH::Buffer.from(:string, ssh_type,
189
+ :string, CurveNameAliasInv[group.curve_name],
190
+ :mstring, public_key.to_bn.to_s(2)).to_s
191
+ @blob
192
+ end
190
193
 
191
- # Verifies the given signature matches the given data.
192
- def ssh_do_verify(sig, data)
193
- digest = digester.digest(data)
194
- a1sig = nil
194
+ # Verifies the given signature matches the given data.
195
+ def ssh_do_verify(sig, data)
196
+ digest = digester.digest(data)
197
+ a1sig = nil
195
198
 
196
- begin
197
- sig_r_len = sig[0,4].unpack("H*")[0].to_i(16)
198
- sig_l_len = sig[4 + sig_r_len,4].unpack("H*")[0].to_i(16)
199
+ begin
200
+ sig_r_len = sig[0, 4].unpack('H*')[0].to_i(16)
201
+ sig_l_len = sig[4 + sig_r_len, 4].unpack('H*')[0].to_i(16)
199
202
 
200
- sig_r = sig[4,sig_r_len].unpack("H*")[0]
201
- sig_s = sig[4 + sig_r_len + 4,sig_l_len].unpack("H*")[0]
203
+ sig_r = sig[4, sig_r_len].unpack('H*')[0]
204
+ sig_s = sig[4 + sig_r_len + 4, sig_l_len].unpack('H*')[0]
202
205
 
203
- a1sig = OpenSSL::ASN1::Sequence([
204
- OpenSSL::ASN1::Integer(sig_r.to_i(16)),
205
- OpenSSL::ASN1::Integer(sig_s.to_i(16))
206
- ])
207
- rescue StandardError
208
- end
206
+ a1sig = OpenSSL::ASN1::Sequence([
207
+ OpenSSL::ASN1::Integer(sig_r.to_i(16)),
208
+ OpenSSL::ASN1::Integer(sig_s.to_i(16))
209
+ ])
210
+ rescue StandardError
211
+ end
209
212
 
210
- if a1sig == nil
211
- return false
212
- else
213
- dsa_verify_asn1(digest, a1sig.to_der)
214
- end
213
+ if a1sig.nil?
214
+ return false
215
+ else
216
+ dsa_verify_asn1(digest, a1sig.to_der)
215
217
  end
218
+ end
219
+
220
+ # Returns the signature for the given data.
221
+ def ssh_do_sign(data)
222
+ digest = digester.digest(data)
223
+ sig = dsa_sign_asn1(digest)
224
+ a1sig = OpenSSL::ASN1.decode(sig)
216
225
 
217
- # Returns the signature for the given data.
218
- def ssh_do_sign(data)
219
- digest = digester.digest(data)
220
- sig = dsa_sign_asn1(digest)
221
- a1sig = OpenSSL::ASN1.decode(sig)
226
+ sig_r = a1sig.value[0].value
227
+ sig_s = a1sig.value[1].value
222
228
 
223
- sig_r = a1sig.value[0].value
224
- sig_s = a1sig.value[1].value
229
+ Net::SSH::Buffer.from(:bignum, sig_r, :bignum, sig_s).to_s
230
+ end
225
231
 
226
- return Net::SSH::Buffer.from(:bignum, sig_r, :bignum, sig_s).to_s
232
+ class Point
233
+ # Returns the description of this key type used by the
234
+ # SSH2 protocol, like "ecdsa-sha2-nistp256"
235
+ def ssh_type
236
+ "ecdsa-sha2-#{CurveNameAliasInv[group.curve_name]}"
237
+ end
238
+
239
+ # Converts the key to a blob, according to the SSH2 protocol.
240
+ def to_blob
241
+ @blob ||= Net::SSH::Buffer.from(:string, ssh_type,
242
+ :string, CurveNameAliasInv[group.curve_name],
243
+ :mstring, to_bn.to_s(2)).to_s
244
+ @blob
227
245
  end
228
- end
229
- else
230
- class OpenSSL::PKey::ECError < RuntimeError
231
- # for compatibility with interpreters
232
- # without EC support (i.e. JRuby)
233
246
  end
234
247
  end
235
248
  end
@@ -1,7 +1,6 @@
1
1
  require 'net/ssh/buffered_io'
2
2
  require 'net/ssh/errors'
3
3
  require 'net/ssh/packet'
4
- require 'net/ssh/ruby_compat'
5
4
  require 'net/ssh/transport/cipher_factory'
6
5
  require 'net/ssh/transport/hmac'
7
6
  require 'net/ssh/transport/state'
@@ -81,8 +80,8 @@ module Net
81
80
  # default), then this will return immediately, whether a packet is
82
81
  # available or not, and will return nil if there is no packet ready to be
83
82
  # returned. If the mode parameter is :block, then this method will block
84
- # until a packet is available.
85
- def next_packet(mode=:nonblock)
83
+ # until a packet is available or timeout seconds have passed.
84
+ def next_packet(mode=:nonblock, timeout=nil)
86
85
  case mode
87
86
  when :nonblock then
88
87
  packet = poll_next_packet
@@ -105,11 +104,8 @@ module Net
105
104
  packet = poll_next_packet
106
105
  return packet if packet
107
106
 
108
- loop do
109
- result = IO.select([self]) or next
110
- break if result.first.any?
111
- end
112
-
107
+ result = IO.select([self], nil, nil, timeout)
108
+ raise Net::SSH::ConnectionTimeout, "timeout waiting for next packet" unless result
113
109
  raise Net::SSH::Disconnect, "connection closed by remote host" if fill <= 0
114
110
  end
115
111
 
@@ -133,7 +129,7 @@ module Net
133
129
  payload = client.compress(payload)
134
130
 
135
131
  # the length of the packet, minus the padding
136
- actual_length = 4 + payload.bytesize + 1
132
+ actual_length = (client.hmac.etm ? 0 : 4) + payload.bytesize + 1
137
133
 
138
134
  # compute the padding length
139
135
  padding_length = client.block_size - (actual_length % client.block_size)
@@ -149,11 +145,32 @@ module Net
149
145
 
150
146
  padding = Array.new(padding_length) { rand(256) }.pack("C*")
151
147
 
152
- unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*")
153
- mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*"))
148
+ if client.hmac.etm
149
+ debug { "using encrypt-then-mac" }
150
+
151
+ # Encrypt padding_length, payload, and padding. Take MAC
152
+ # from the unencrypted packet_lenght and the encrypted
153
+ # data.
154
+ length_data = [packet_length].pack("N")
155
+
156
+ unencrypted_data = [padding_length, payload, padding].pack("CA*A*")
154
157
 
155
- encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
156
- message = encrypted_data + mac
158
+ encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
159
+
160
+ mac_data = length_data + encrypted_data
161
+
162
+ mac = client.hmac.digest([client.sequence_number, mac_data].pack("NA*"))
163
+
164
+ message = mac_data + mac
165
+ else
166
+ unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*")
167
+
168
+ mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*"))
169
+
170
+ encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
171
+
172
+ message = encrypted_data + mac
173
+ end
157
174
 
158
175
  debug { "queueing packet nr #{client.sequence_number} type #{payload.getbyte(0)} len #{packet_length}" }
159
176
  enqueue(message)
@@ -198,18 +215,27 @@ module Net
198
215
  # read, post-processed according to the cipher, hmac, and compression
199
216
  # algorithms specified in the server state object, and returned as a
200
217
  # new Packet object.
218
+ # rubocop:disable Metrics/AbcSize
201
219
  def poll_next_packet
220
+ aad_length = server.hmac.etm ? 4 : 0
221
+
202
222
  if @packet.nil?
203
223
  minimum = server.block_size < 4 ? 4 : server.block_size
204
- return nil if available < minimum
205
- data = read_available(minimum)
224
+ return nil if available < minimum + aad_length
225
+ data = read_available(minimum + aad_length)
206
226
 
207
227
  # decipher it
208
- @packet = Net::SSH::Buffer.new(server.update_cipher(data))
209
- @packet_length = @packet.read_long
228
+ if server.hmac.etm
229
+ @packet_length = data.unpack("N").first
230
+ @mac_data = data
231
+ @packet = Net::SSH::Buffer.new(server.update_cipher(data[aad_length..-1]))
232
+ else
233
+ @packet = Net::SSH::Buffer.new(server.update_cipher(data))
234
+ @packet_length = @packet.read_long
235
+ end
210
236
  end
211
237
 
212
- need = @packet_length + 4 - server.block_size
238
+ need = @packet_length + 4 - aad_length - server.block_size
213
239
  raise Net::SSH::Exception, "padding error, need #{need} block #{server.block_size}" if need % server.block_size != 0
214
240
 
215
241
  return nil if available < need + server.hmac.mac_length
@@ -217,6 +243,7 @@ module Net
217
243
  if need > 0
218
244
  # read the remainder of the packet and decrypt it.
219
245
  data = read_available(need)
246
+ @mac_data += data if server.hmac.etm
220
247
  @packet.append(server.update_cipher(data))
221
248
  end
222
249
 
@@ -229,8 +256,12 @@ module Net
229
256
 
230
257
  payload = @packet.read(@packet_length - padding_length - 1)
231
258
 
232
- my_computed_hmac = server.hmac.digest([server.sequence_number, @packet.content].pack("NA*"))
233
- raise Net::SSH::Exception, "corrupted hmac detected" if real_hmac != my_computed_hmac
259
+ my_computed_hmac = if server.hmac.etm
260
+ server.hmac.digest([server.sequence_number, @mac_data].pack("NA*"))
261
+ else
262
+ server.hmac.digest([server.sequence_number, @packet.content].pack("NA*"))
263
+ end
264
+ raise Net::SSH::Exception, "corrupted hmac detected #{server.hmac.class}" if real_hmac != my_computed_hmac
234
265
 
235
266
  # try to decompress the payload, in case compression is active
236
267
  payload = server.decompress(payload)
@@ -243,6 +274,7 @@ module Net
243
274
  return Packet.new(payload)
244
275
  end
245
276
  end
277
+ # rubocop:enable Metrics/AbcSize
246
278
 
247
279
  end
248
280
  end
@@ -190,7 +190,7 @@ module Net
190
190
  loop do
191
191
  return @queue.shift if consume_queue && @queue.any? && algorithms.allow?(@queue.first)
192
192
 
193
- packet = socket.next_packet(mode)
193
+ packet = socket.next_packet(mode, options[:timeout])
194
194
  return nil if packet.nil?
195
195
 
196
196
  case packet.type
@@ -274,6 +274,23 @@ module Net
274
274
 
275
275
  private
276
276
 
277
+ # Compatibility verifier which allows users to keep using
278
+ # custom verifier code without adding new :verify_signature
279
+ # method.
280
+ class CompatibleVerifier
281
+ def initialize(verifier)
282
+ @verifier = verifier
283
+ end
284
+
285
+ def verify(arguments)
286
+ @verifier.verify(arguments)
287
+ end
288
+
289
+ def verify_signature(&block)
290
+ yield
291
+ end
292
+ end
293
+
277
294
  # Instantiates a new host-key verification class, based on the value of
278
295
  # the parameter.
279
296
  #
@@ -285,8 +302,8 @@ module Net
285
302
  # - :accept_new (insecure)
286
303
  # - :always (secure)
287
304
  #
288
- # If the argument happens to respond to :verify, it is returned
289
- # directly. Otherwise, an exception is raised.
305
+ # If the argument happens to respond to :verify and :verify_signature,
306
+ # it is returned directly. Otherwise, an exception is raised.
290
307
  #
291
308
  # Values false, true, and :very were deprecated in
292
309
  # [#595](https://github.com/net-ssh/net-ssh/pull/595)
@@ -314,7 +331,12 @@ module Net
314
331
  Net::SSH::Verifiers::Always.new
315
332
  else
316
333
  if verifier.respond_to?(:verify)
317
- verifier
334
+ if verifier.respond_to?(:verify_signature)
335
+ verifier
336
+ else
337
+ Kernel.warn("Warning: verifier without :verify_signature is deprecated")
338
+ CompatibleVerifier.new(verifier)
339
+ end
318
340
  else
319
341
  raise(
320
342
  ArgumentError,