net-ssh 5.2.0 → 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.
Files changed (122) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.dockerignore +6 -0
  4. data/.github/config/rubocop_linter_action.yml +4 -0
  5. data/.github/workflows/ci-with-docker.yml +44 -0
  6. data/.github/workflows/ci.yml +87 -0
  7. data/.github/workflows/rubocop.yml +13 -0
  8. data/.gitignore +3 -0
  9. data/.rubocop.yml +16 -2
  10. data/.rubocop_todo.yml +623 -511
  11. data/CHANGES.txt +50 -2
  12. data/Dockerfile +27 -0
  13. data/Dockerfile.openssl3 +17 -0
  14. data/Gemfile +2 -0
  15. data/Gemfile.noed25519 +2 -0
  16. data/Manifest +0 -1
  17. data/README.md +293 -0
  18. data/Rakefile +6 -2
  19. data/appveyor.yml +4 -2
  20. data/docker-compose.yml +23 -0
  21. data/lib/net/ssh/authentication/agent.rb +29 -13
  22. data/lib/net/ssh/authentication/certificate.rb +19 -7
  23. data/lib/net/ssh/authentication/constants.rb +0 -1
  24. data/lib/net/ssh/authentication/ed25519.rb +13 -8
  25. data/lib/net/ssh/authentication/ed25519_loader.rb +5 -8
  26. data/lib/net/ssh/authentication/key_manager.rb +73 -32
  27. data/lib/net/ssh/authentication/methods/abstract.rb +12 -3
  28. data/lib/net/ssh/authentication/methods/hostbased.rb +3 -5
  29. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +5 -3
  30. data/lib/net/ssh/authentication/methods/none.rb +6 -9
  31. data/lib/net/ssh/authentication/methods/password.rb +2 -3
  32. data/lib/net/ssh/authentication/methods/publickey.rb +56 -16
  33. data/lib/net/ssh/authentication/pageant.rb +97 -97
  34. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +2 -3
  35. data/lib/net/ssh/authentication/session.rb +27 -23
  36. data/lib/net/ssh/buffer.rb +51 -40
  37. data/lib/net/ssh/buffered_io.rb +24 -26
  38. data/lib/net/ssh/config.rb +82 -50
  39. data/lib/net/ssh/connection/channel.rb +101 -87
  40. data/lib/net/ssh/connection/constants.rb +0 -4
  41. data/lib/net/ssh/connection/event_loop.rb +30 -25
  42. data/lib/net/ssh/connection/keepalive.rb +12 -12
  43. data/lib/net/ssh/connection/session.rb +115 -111
  44. data/lib/net/ssh/connection/term.rb +56 -58
  45. data/lib/net/ssh/errors.rb +12 -12
  46. data/lib/net/ssh/key_factory.rb +10 -13
  47. data/lib/net/ssh/known_hosts.rb +106 -39
  48. data/lib/net/ssh/loggable.rb +10 -11
  49. data/lib/net/ssh/packet.rb +1 -1
  50. data/lib/net/ssh/prompt.rb +9 -11
  51. data/lib/net/ssh/proxy/command.rb +1 -2
  52. data/lib/net/ssh/proxy/errors.rb +2 -4
  53. data/lib/net/ssh/proxy/http.rb +18 -20
  54. data/lib/net/ssh/proxy/https.rb +8 -10
  55. data/lib/net/ssh/proxy/jump.rb +8 -10
  56. data/lib/net/ssh/proxy/socks4.rb +2 -4
  57. data/lib/net/ssh/proxy/socks5.rb +3 -6
  58. data/lib/net/ssh/service/forward.rb +9 -8
  59. data/lib/net/ssh/test/channel.rb +24 -26
  60. data/lib/net/ssh/test/extensions.rb +35 -35
  61. data/lib/net/ssh/test/kex.rb +6 -8
  62. data/lib/net/ssh/test/local_packet.rb +0 -2
  63. data/lib/net/ssh/test/packet.rb +3 -3
  64. data/lib/net/ssh/test/remote_packet.rb +6 -8
  65. data/lib/net/ssh/test/script.rb +25 -27
  66. data/lib/net/ssh/test/socket.rb +12 -15
  67. data/lib/net/ssh/test.rb +7 -7
  68. data/lib/net/ssh/transport/algorithms.rb +100 -58
  69. data/lib/net/ssh/transport/cipher_factory.rb +34 -50
  70. data/lib/net/ssh/transport/constants.rb +13 -9
  71. data/lib/net/ssh/transport/ctr.rb +8 -14
  72. data/lib/net/ssh/transport/hmac/abstract.rb +20 -5
  73. data/lib/net/ssh/transport/hmac/md5.rb +0 -2
  74. data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
  75. data/lib/net/ssh/transport/hmac/none.rb +0 -2
  76. data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
  77. data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
  78. data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
  79. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  80. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  81. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  82. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  83. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  84. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  85. data/lib/net/ssh/transport/hmac.rb +13 -11
  86. data/lib/net/ssh/transport/identity_cipher.rb +11 -13
  87. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  88. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  89. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  90. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  91. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +5 -19
  92. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  93. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +30 -139
  94. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +1 -8
  95. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  96. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +20 -81
  97. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +5 -4
  98. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +5 -4
  99. data/lib/net/ssh/transport/kex.rb +15 -10
  100. data/lib/net/ssh/transport/key_expander.rb +7 -8
  101. data/lib/net/ssh/transport/openssl.rb +149 -127
  102. data/lib/net/ssh/transport/packet_stream.rb +50 -16
  103. data/lib/net/ssh/transport/server_version.rb +17 -16
  104. data/lib/net/ssh/transport/session.rb +9 -7
  105. data/lib/net/ssh/transport/state.rb +44 -44
  106. data/lib/net/ssh/verifiers/accept_new.rb +0 -2
  107. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +1 -2
  108. data/lib/net/ssh/verifiers/always.rb +6 -4
  109. data/lib/net/ssh/verifiers/never.rb +0 -2
  110. data/lib/net/ssh/version.rb +3 -3
  111. data/lib/net/ssh.rb +12 -8
  112. data/net-ssh-public_cert.pem +8 -8
  113. data/net-ssh.gemspec +9 -7
  114. data/support/ssh_tunnel_bug.rb +3 -3
  115. data.tar.gz.sig +0 -0
  116. metadata +55 -30
  117. metadata.gz.sig +0 -0
  118. data/.travis.yml +0 -53
  119. data/Gemfile.noed25519.lock +0 -41
  120. data/README.rdoc +0 -194
  121. data/lib/net/ssh/ruby_compat.rb +0 -13
  122. data/support/arcfour_check.rb +0 -20
@@ -2,7 +2,6 @@ require 'openssl'
2
2
  require 'net/ssh/authentication/pub_key_fingerprint'
3
3
 
4
4
  module OpenSSL
5
-
6
5
  # This class is originally defined in the OpenSSL module. As needed, methods
7
6
  # have been added to it by the Net::SSH module for convenience in dealing with
8
7
  # SSH functionality.
@@ -24,7 +23,6 @@ module OpenSSL
24
23
  end
25
24
 
26
25
  module PKey
27
-
28
26
  class PKey
29
27
  include Net::SSH::Authentication::PubKeyFingerprint
30
28
  end
@@ -37,6 +35,7 @@ module OpenSSL
37
35
  # lifted more-or-less directly from OpenSSH, dh.c, dh_pub_is_valid.)
38
36
  def valid?
39
37
  return false if pub_key.nil? || pub_key < 0
38
+
40
39
  bits_set = 0
41
40
  pub_key.num_bits.times { |i| bits_set += 1 if pub_key.bit_set?(i) }
42
41
  return (bits_set > 1 && pub_key < p)
@@ -53,9 +52,7 @@ module OpenSSL
53
52
  "ssh-rsa"
54
53
  end
55
54
 
56
- def ssh_signature_type
57
- ssh_type
58
- end
55
+ alias ssh_signature_type ssh_type
59
56
 
60
57
  # Converts the key to a blob, according to the SSH2 protocol.
61
58
  def to_blob
@@ -63,13 +60,30 @@ module OpenSSL
63
60
  end
64
61
 
65
62
  # Verifies the given signature matches the given data.
66
- def ssh_do_verify(sig, data)
67
- verify(OpenSSL::Digest::SHA1.new, sig, data)
63
+ def ssh_do_verify(sig, data, options = {})
64
+ digester =
65
+ if options[:host_key] == "rsa-sha2-512"
66
+ OpenSSL::Digest::SHA512.new
67
+ elsif options[:host_key] == "rsa-sha2-256"
68
+ OpenSSL::Digest::SHA256.new
69
+ else
70
+ OpenSSL::Digest::SHA1.new
71
+ end
72
+
73
+ verify(digester, sig, data)
68
74
  end
69
75
 
70
76
  # Returns the signature for the given data.
71
- def ssh_do_sign(data)
72
- sign(OpenSSL::Digest::SHA1.new, data)
77
+ def ssh_do_sign(data, sig_alg = nil)
78
+ digester =
79
+ if sig_alg == "rsa-sha2-512"
80
+ OpenSSL::Digest::SHA512.new
81
+ elsif sig_alg == "rsa-sha2-256"
82
+ OpenSSL::Digest::SHA256.new
83
+ else
84
+ OpenSSL::Digest::SHA1.new
85
+ end
86
+ sign(digester, data)
73
87
  end
74
88
  end
75
89
 
@@ -83,36 +97,35 @@ module OpenSSL
83
97
  "ssh-dss"
84
98
  end
85
99
 
86
- def ssh_signature_type
87
- ssh_type
88
- end
100
+ alias ssh_signature_type ssh_type
89
101
 
90
102
  # Converts the key to a blob, according to the SSH2 protocol.
91
103
  def to_blob
92
104
  @blob ||= Net::SSH::Buffer.from(:string, ssh_type,
93
- :bignum, p, :bignum, q, :bignum, g, :bignum, pub_key).to_s
105
+ :bignum, p, :bignum, q, :bignum, g, :bignum, pub_key).to_s
94
106
  end
95
107
 
96
108
  # Verifies the given signature matches the given data.
97
- def ssh_do_verify(sig, data)
98
- sig_r = sig[0,20].unpack("H*")[0].to_i(16)
99
- sig_s = sig[20,20].unpack("H*")[0].to_i(16)
109
+ def ssh_do_verify(sig, data, options = {})
110
+ sig_r = sig[0, 20].unpack("H*")[0].to_i(16)
111
+ sig_s = sig[20, 20].unpack("H*")[0].to_i(16)
100
112
  a1sig = OpenSSL::ASN1::Sequence([
101
- OpenSSL::ASN1::Integer(sig_r),
102
- OpenSSL::ASN1::Integer(sig_s)
103
- ])
113
+ OpenSSL::ASN1::Integer(sig_r),
114
+ OpenSSL::ASN1::Integer(sig_s)
115
+ ])
104
116
  return verify(OpenSSL::Digest::SHA1.new, a1sig.to_der, data)
105
117
  end
106
118
 
107
119
  # Signs the given data.
108
- def ssh_do_sign(data)
120
+ def ssh_do_sign(data, sig_alg = nil)
109
121
  sig = sign(OpenSSL::Digest::SHA1.new, data)
110
122
  a1sig = OpenSSL::ASN1.decode(sig)
111
123
 
112
124
  sig_r = a1sig.value[0].value.to_s(2)
113
125
  sig_s = a1sig.value[1].value.to_s(2)
114
126
 
115
- raise OpenSSL::PKey::DSAError, "bad sig size" if sig_r.length > 20 || sig_s.length > 20
127
+ sig_size = params["q"].num_bits / 8
128
+ raise OpenSSL::PKey::DSAError, "bad sig size" if sig_r.length > sig_size || sig_s.length > sig_size
116
129
 
117
130
  sig_r = "\0" * (20 - sig_r.length) + sig_r if sig_r.length < 20
118
131
  sig_s = "\0" * (20 - sig_s.length) + sig_s if sig_s.length < 20
@@ -121,132 +134,141 @@ module OpenSSL
121
134
  end
122
135
  end
123
136
 
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
137
+ # This class is originally defined in the OpenSSL module. As needed, methods
138
+ # have been added to it by the Net::SSH module for convenience in dealing
139
+ # with SSH functionality.
140
+ class EC
141
+ CurveNameAlias = {
142
+ 'nistp256' => 'prime256v1',
143
+ 'nistp384' => 'secp384r1',
144
+ 'nistp521' => 'secp521r1'
145
+ }.freeze
146
+
147
+ CurveNameAliasInv = {
148
+ 'prime256v1' => 'nistp256',
149
+ 'secp384r1' => 'nistp384',
150
+ 'secp521r1' => 'nistp521'
151
+ }.freeze
152
+
153
+ def self.read_keyblob(curve_name_in_type, buffer)
154
+ curve_name_in_key = buffer.read_string
155
+
156
+ unless curve_name_in_type == curve_name_in_key
157
+ raise Net::SSH::Exception, "curve name mismatched (`#{curve_name_in_key}' with `#{curve_name_in_type}')"
155
158
  end
156
159
 
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]}"
160
+ public_key_oct = buffer.read_string
161
+ begin
162
+ curvename = OpenSSL::PKey::EC::CurveNameAlias[curve_name_in_key]
163
+ group = OpenSSL::PKey::EC::Group.new(curvename)
164
+ point = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(public_key_oct, 2))
165
+ asn1 = OpenSSL::ASN1::Sequence(
166
+ [
167
+ OpenSSL::ASN1::Sequence(
168
+ [
169
+ OpenSSL::ASN1::ObjectId("id-ecPublicKey"),
170
+ OpenSSL::ASN1::ObjectId(curvename)
171
+ ]
172
+ ),
173
+ OpenSSL::ASN1::BitString(point.to_octet_string(:uncompressed))
174
+ ]
175
+ )
176
+
177
+ key = OpenSSL::PKey::EC.new(asn1.to_der)
178
+
179
+ return key
180
+ rescue OpenSSL::PKey::ECError
181
+ raise NotImplementedError, "unsupported key type `#{type}'"
161
182
  end
183
+ end
162
184
 
163
- def ssh_signature_type
164
- ssh_type
165
- end
185
+ # Returns the description of this key type used by the
186
+ # SSH2 protocol, like "ecdsa-sha2-nistp256"
187
+ def ssh_type
188
+ "ecdsa-sha2-#{CurveNameAliasInv[group.curve_name]}"
189
+ end
166
190
 
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
191
+ alias ssh_signature_type ssh_type
192
+
193
+ def digester
194
+ if group.curve_name =~ /^[a-z]+(\d+)\w*\z/
195
+ curve_size = Regexp.last_match(1).to_i
196
+ if curve_size <= 256
178
197
  OpenSSL::Digest::SHA256.new
198
+ elsif curve_size <= 384
199
+ OpenSSL::Digest::SHA384.new
200
+ else
201
+ OpenSSL::Digest::SHA512.new
179
202
  end
203
+ else
204
+ OpenSSL::Digest::SHA256.new
180
205
  end
181
- private :digester
182
-
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
190
-
191
- # Verifies the given signature matches the given data.
192
- def ssh_do_verify(sig, data)
193
- digest = digester.digest(data)
194
- a1sig = nil
195
-
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)
206
+ end
207
+ private :digester
199
208
 
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]
209
+ # Converts the key to a blob, according to the SSH2 protocol.
210
+ def to_blob
211
+ @blob ||= Net::SSH::Buffer.from(:string, ssh_type,
212
+ :string, CurveNameAliasInv[group.curve_name],
213
+ :mstring, public_key.to_bn.to_s(2)).to_s
214
+ @blob
215
+ end
202
216
 
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
217
+ # Verifies the given signature matches the given data.
218
+ def ssh_do_verify(sig, data, options = {})
219
+ digest = digester.digest(data)
220
+ a1sig = nil
221
+
222
+ begin
223
+ sig_r_len = sig[0, 4].unpack('H*')[0].to_i(16)
224
+ sig_l_len = sig[4 + sig_r_len, 4].unpack('H*')[0].to_i(16)
225
+
226
+ sig_r = sig[4, sig_r_len].unpack('H*')[0]
227
+ sig_s = sig[4 + sig_r_len + 4, sig_l_len].unpack('H*')[0]
228
+
229
+ a1sig = OpenSSL::ASN1::Sequence([
230
+ OpenSSL::ASN1::Integer(sig_r.to_i(16)),
231
+ OpenSSL::ASN1::Integer(sig_s.to_i(16))
232
+ ])
233
+ rescue StandardError
234
+ end
209
235
 
210
- if a1sig == nil
211
- return false
212
- else
213
- dsa_verify_asn1(digest, a1sig.to_der)
214
- end
236
+ if a1sig.nil?
237
+ return false
238
+ else
239
+ dsa_verify_asn1(digest, a1sig.to_der)
215
240
  end
241
+ end
242
+
243
+ # Returns the signature for the given data.
244
+ def ssh_do_sign(data, sig_alg = nil)
245
+ digest = digester.digest(data)
246
+ sig = dsa_sign_asn1(digest)
247
+ a1sig = OpenSSL::ASN1.decode(sig)
216
248
 
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)
249
+ sig_r = a1sig.value[0].value
250
+ sig_s = a1sig.value[1].value
222
251
 
223
- sig_r = a1sig.value[0].value
224
- sig_s = a1sig.value[1].value
252
+ Net::SSH::Buffer.from(:bignum, sig_r, :bignum, sig_s).to_s
253
+ end
225
254
 
226
- return Net::SSH::Buffer.from(:bignum, sig_r, :bignum, sig_s).to_s
255
+ class Point
256
+ # Returns the description of this key type used by the
257
+ # SSH2 protocol, like "ecdsa-sha2-nistp256"
258
+ def ssh_type
259
+ "ecdsa-sha2-#{CurveNameAliasInv[group.curve_name]}"
227
260
  end
228
261
 
229
- class Point
230
- # Returns the description of this key type used by the
231
- # SSH2 protocol, like "ecdsa-sha2-nistp256"
232
- def ssh_type
233
- "ecdsa-sha2-#{CurveNameAliasInv[self.group.curve_name]}"
234
- end
262
+ alias ssh_signature_type ssh_type
235
263
 
236
- # Converts the key to a blob, according to the SSH2 protocol.
237
- def to_blob
238
- @blob ||= Net::SSH::Buffer.from(:string, ssh_type,
239
- :string, CurveNameAliasInv[self.group.curve_name],
240
- :mstring, self.to_bn.to_s(2)).to_s
241
- @blob
242
- end
264
+ # Converts the key to a blob, according to the SSH2 protocol.
265
+ def to_blob
266
+ @blob ||= Net::SSH::Buffer.from(:string, ssh_type,
267
+ :string, CurveNameAliasInv[group.curve_name],
268
+ :mstring, to_bn.to_s(2)).to_s
269
+ @blob
243
270
  end
244
271
  end
245
- else
246
- class OpenSSL::PKey::ECError < RuntimeError
247
- # for compatibility with interpreters
248
- # without EC support (i.e. JRuby)
249
- end
250
272
  end
251
273
  end
252
274
  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'
@@ -9,7 +8,6 @@ require 'net/ssh/transport/state'
9
8
  module Net
10
9
  module SSH
11
10
  module Transport
12
-
13
11
  # A module that builds additional functionality onto the Net::SSH::BufferedIo
14
12
  # module. It adds SSH encryption, compression, and packet validation, as
15
13
  # per the SSH2 protocol. It also adds an abstraction for polling packets,
@@ -82,7 +80,7 @@ module Net
82
80
  # available or not, and will return nil if there is no packet ready to be
83
81
  # returned. If the mode parameter is :block, then this method will block
84
82
  # until a packet is available or timeout seconds have passed.
85
- def next_packet(mode=:nonblock, timeout=nil)
83
+ def next_packet(mode = :nonblock, timeout = nil)
86
84
  case mode
87
85
  when :nonblock then
88
86
  packet = poll_next_packet
@@ -130,7 +128,7 @@ module Net
130
128
  payload = client.compress(payload)
131
129
 
132
130
  # the length of the packet, minus the padding
133
- actual_length = 4 + payload.bytesize + 1
131
+ actual_length = (client.hmac.etm ? 0 : 4) + payload.bytesize + 1
134
132
 
135
133
  # compute the padding length
136
134
  padding_length = client.block_size - (actual_length % client.block_size)
@@ -146,11 +144,32 @@ module Net
146
144
 
147
145
  padding = Array.new(padding_length) { rand(256) }.pack("C*")
148
146
 
149
- unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*")
150
- mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*"))
147
+ if client.hmac.etm
148
+ debug { "using encrypt-then-mac" }
149
+
150
+ # Encrypt padding_length, payload, and padding. Take MAC
151
+ # from the unencrypted packet_lenght and the encrypted
152
+ # data.
153
+ length_data = [packet_length].pack("N")
154
+
155
+ unencrypted_data = [padding_length, payload, padding].pack("CA*A*")
156
+
157
+ encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
158
+
159
+ mac_data = length_data + encrypted_data
160
+
161
+ mac = client.hmac.digest([client.sequence_number, mac_data].pack("NA*"))
162
+
163
+ message = mac_data + mac
164
+ else
165
+ unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*")
166
+
167
+ mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*"))
168
+
169
+ encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
151
170
 
152
- encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
153
- message = encrypted_data + mac
171
+ message = encrypted_data + mac
172
+ end
154
173
 
155
174
  debug { "queueing packet nr #{client.sequence_number} type #{payload.getbyte(0)} len #{packet_length}" }
156
175
  enqueue(message)
@@ -195,18 +214,28 @@ module Net
195
214
  # read, post-processed according to the cipher, hmac, and compression
196
215
  # algorithms specified in the server state object, and returned as a
197
216
  # new Packet object.
217
+ # rubocop:disable Metrics/AbcSize
198
218
  def poll_next_packet
219
+ aad_length = server.hmac.etm ? 4 : 0
220
+
199
221
  if @packet.nil?
200
222
  minimum = server.block_size < 4 ? 4 : server.block_size
201
- return nil if available < minimum
202
- data = read_available(minimum)
223
+ return nil if available < minimum + aad_length
224
+
225
+ data = read_available(minimum + aad_length)
203
226
 
204
227
  # decipher it
205
- @packet = Net::SSH::Buffer.new(server.update_cipher(data))
206
- @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
207
236
  end
208
237
 
209
- need = @packet_length + 4 - server.block_size
238
+ need = @packet_length + 4 - aad_length - server.block_size
210
239
  raise Net::SSH::Exception, "padding error, need #{need} block #{server.block_size}" if need % server.block_size != 0
211
240
 
212
241
  return nil if available < need + server.hmac.mac_length
@@ -214,6 +243,7 @@ module Net
214
243
  if need > 0
215
244
  # read the remainder of the packet and decrypt it.
216
245
  data = read_available(need)
246
+ @mac_data += data if server.hmac.etm
217
247
  @packet.append(server.update_cipher(data))
218
248
  end
219
249
 
@@ -226,8 +256,12 @@ module Net
226
256
 
227
257
  payload = @packet.read(@packet_length - padding_length - 1)
228
258
 
229
- my_computed_hmac = server.hmac.digest([server.sequence_number, @packet.content].pack("NA*"))
230
- 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
231
265
 
232
266
  # try to decompress the payload, in case compression is active
233
267
  payload = server.decompress(payload)
@@ -240,7 +274,7 @@ module Net
240
274
  return Packet.new(payload)
241
275
  end
242
276
  end
243
-
277
+ # rubocop:enable Metrics/AbcSize
244
278
  end
245
279
  end
246
280
  end
@@ -2,10 +2,9 @@ require 'net/ssh/errors'
2
2
  require 'net/ssh/loggable'
3
3
  require 'net/ssh/version'
4
4
 
5
- module Net
6
- module SSH
5
+ module Net
6
+ module SSH
7
7
  module Transport
8
-
9
8
  # Negotiates the SSH protocol version and trades information about server
10
9
  # and client. This is never used directly--it is always called by the
11
10
  # transport layer as part of the initialization process of the transport
@@ -15,40 +14,41 @@ module Net
15
14
  # the authoritative reference for any queries regarding the version in effect.
16
15
  class ServerVersion
17
16
  include Loggable
18
-
17
+
19
18
  # The SSH version string as reported by Net::SSH
20
19
  PROTO_VERSION = "SSH-2.0-Ruby/Net::SSH_#{Net::SSH::Version::CURRENT} #{RUBY_PLATFORM}"
21
-
20
+
22
21
  # Any header text sent by the server prior to sending the version.
23
22
  attr_reader :header
24
-
23
+
25
24
  # The version string reported by the server.
26
25
  attr_reader :version
27
-
26
+
28
27
  # Instantiates a new ServerVersion and immediately (and synchronously)
29
28
  # negotiates the SSH protocol in effect, using the given socket.
30
29
  def initialize(socket, logger, timeout = nil)
31
- @header = ""
30
+ @header = String.new
32
31
  @version = nil
33
32
  @logger = logger
34
33
  negotiate!(socket, timeout)
35
34
  end
36
-
35
+
37
36
  private
38
-
37
+
39
38
  # Negotiates the SSH protocol to use, via the given socket. If the server
40
39
  # reports an incompatible SSH version (e.g., SSH1), this will raise an
41
40
  # exception.
42
41
  def negotiate!(socket, timeout)
43
42
  info { "negotiating protocol version" }
44
-
43
+
45
44
  debug { "local is `#{PROTO_VERSION}'" }
46
45
  socket.write "#{PROTO_VERSION}\r\n"
47
46
  socket.flush
48
-
47
+
49
48
  raise Net::SSH::ConnectionTimeout, "timeout during server version negotiating" if timeout && !IO.select([socket], nil, nil, timeout)
49
+
50
50
  loop do
51
- @version = ""
51
+ @version = String.new
52
52
  loop do
53
53
  begin
54
54
  b = socket.readpartial(1)
@@ -60,14 +60,15 @@ module Net
60
60
  break if b == "\n"
61
61
  end
62
62
  break if @version.match(/^SSH-/)
63
+
63
64
  @header << @version
64
65
  end
65
-
66
+
66
67
  @version.chomp!
67
68
  debug { "remote is `#{@version}'" }
68
-
69
+
69
70
  raise Net::SSH::Exception, "incompatible SSH version `#{@version}'" unless @version.match(/^SSH-(1\.99|2\.0)-/)
70
-
71
+
71
72
  raise Net::SSH::ConnectionTimeout, "timeout during client version negotiating" if timeout && !IO.select(nil, [socket], nil, timeout)
72
73
  end
73
74
  end
@@ -15,7 +15,6 @@ require 'net/ssh/verifiers/never'
15
15
  module Net
16
16
  module SSH
17
17
  module Transport
18
-
19
18
  # The transport layer represents the lowest level of the SSH protocol, and
20
19
  # implements basic message exchanging and protocol initialization. It will
21
20
  # never be instantiated directly (unless you really know what you're about),
@@ -56,7 +55,7 @@ module Net
56
55
  # Instantiates a new transport layer abstraction. This will block until
57
56
  # the initial key exchange completes, leaving you with a ready-to-use
58
57
  # transport session.
59
- def initialize(host, options={})
58
+ def initialize(host, options = {})
60
59
  self.logger = options[:logger]
61
60
 
62
61
  @host = host
@@ -160,6 +159,7 @@ module Net
160
159
  # one is performed, causing this method to block until it completes.
161
160
  def rekey_as_needed
162
161
  return if algorithms.pending?
162
+
163
163
  socket.if_needs_rekey? { rekey! }
164
164
  end
165
165
 
@@ -186,7 +186,7 @@ module Net
186
186
  # received, it will be enqueued and otherwise ignored. When a key-exchange
187
187
  # is not in process, and consume_queue is true, packets will be first
188
188
  # read from the queue before the socket is queried.
189
- def poll_message(mode=:nonblock, consume_queue=true)
189
+ def poll_message(mode = :nonblock, consume_queue = true)
190
190
  loop do
191
191
  return @queue.shift if consume_queue && @queue.any? && algorithms.allow?(@queue.first)
192
192
 
@@ -211,6 +211,7 @@ module Net
211
211
 
212
212
  else
213
213
  return packet if algorithms.allow?(packet)
214
+
214
215
  push(packet)
215
216
  end
216
217
  end
@@ -222,6 +223,7 @@ module Net
222
223
  def wait
223
224
  loop do
224
225
  break if block_given? && yield
226
+
225
227
  message = poll_message(:nonblock, false)
226
228
  push(message) if message
227
229
  break if !block_given?
@@ -250,27 +252,27 @@ module Net
250
252
  # Configure's the packet stream's client state with the given set of
251
253
  # options. This is typically used to define the cipher, compression, and
252
254
  # hmac algorithms to use when sending packets to the server.
253
- def configure_client(options={})
255
+ def configure_client(options = {})
254
256
  socket.client.set(options)
255
257
  end
256
258
 
257
259
  # Configure's the packet stream's server state with the given set of
258
260
  # options. This is typically used to define the cipher, compression, and
259
261
  # hmac algorithms to use when reading packets from the server.
260
- def configure_server(options={})
262
+ def configure_server(options = {})
261
263
  socket.server.set(options)
262
264
  end
263
265
 
264
266
  # Sets a new hint for the packet stream, which the packet stream may use
265
267
  # to change its behavior. (See PacketStream#hints).
266
- def hint(which, value=true)
268
+ def hint(which, value = true)
267
269
  socket.hints[which] = value
268
270
  end
269
271
 
270
272
  public
271
273
 
272
274
  # this method is primarily for use in tests
273
- attr_reader :queue #:nodoc:
275
+ attr_reader :queue # :nodoc:
274
276
 
275
277
  private
276
278