net-ssh-backports 6.3.0.backports

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 (111) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +93 -0
  3. data/.gitignore +13 -0
  4. data/.rubocop.yml +21 -0
  5. data/.rubocop_todo.yml +1074 -0
  6. data/.travis.yml +51 -0
  7. data/CHANGES.txt +698 -0
  8. data/Gemfile +13 -0
  9. data/Gemfile.noed25519 +12 -0
  10. data/ISSUE_TEMPLATE.md +30 -0
  11. data/LICENSE.txt +19 -0
  12. data/Manifest +132 -0
  13. data/README.md +287 -0
  14. data/Rakefile +105 -0
  15. data/THANKS.txt +110 -0
  16. data/appveyor.yml +58 -0
  17. data/lib/net/ssh/authentication/agent.rb +284 -0
  18. data/lib/net/ssh/authentication/certificate.rb +183 -0
  19. data/lib/net/ssh/authentication/constants.rb +20 -0
  20. data/lib/net/ssh/authentication/ed25519.rb +185 -0
  21. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  22. data/lib/net/ssh/authentication/key_manager.rb +297 -0
  23. data/lib/net/ssh/authentication/methods/abstract.rb +69 -0
  24. data/lib/net/ssh/authentication/methods/hostbased.rb +72 -0
  25. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +77 -0
  26. data/lib/net/ssh/authentication/methods/none.rb +34 -0
  27. data/lib/net/ssh/authentication/methods/password.rb +80 -0
  28. data/lib/net/ssh/authentication/methods/publickey.rb +95 -0
  29. data/lib/net/ssh/authentication/pageant.rb +497 -0
  30. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  31. data/lib/net/ssh/authentication/session.rb +163 -0
  32. data/lib/net/ssh/buffer.rb +434 -0
  33. data/lib/net/ssh/buffered_io.rb +202 -0
  34. data/lib/net/ssh/config.rb +406 -0
  35. data/lib/net/ssh/connection/channel.rb +695 -0
  36. data/lib/net/ssh/connection/constants.rb +33 -0
  37. data/lib/net/ssh/connection/event_loop.rb +123 -0
  38. data/lib/net/ssh/connection/keepalive.rb +59 -0
  39. data/lib/net/ssh/connection/session.rb +712 -0
  40. data/lib/net/ssh/connection/term.rb +180 -0
  41. data/lib/net/ssh/errors.rb +106 -0
  42. data/lib/net/ssh/key_factory.rb +218 -0
  43. data/lib/net/ssh/known_hosts.rb +264 -0
  44. data/lib/net/ssh/loggable.rb +62 -0
  45. data/lib/net/ssh/packet.rb +106 -0
  46. data/lib/net/ssh/prompt.rb +62 -0
  47. data/lib/net/ssh/proxy/command.rb +123 -0
  48. data/lib/net/ssh/proxy/errors.rb +16 -0
  49. data/lib/net/ssh/proxy/http.rb +98 -0
  50. data/lib/net/ssh/proxy/https.rb +50 -0
  51. data/lib/net/ssh/proxy/jump.rb +54 -0
  52. data/lib/net/ssh/proxy/socks4.rb +67 -0
  53. data/lib/net/ssh/proxy/socks5.rb +140 -0
  54. data/lib/net/ssh/service/forward.rb +426 -0
  55. data/lib/net/ssh/test/channel.rb +147 -0
  56. data/lib/net/ssh/test/extensions.rb +173 -0
  57. data/lib/net/ssh/test/kex.rb +46 -0
  58. data/lib/net/ssh/test/local_packet.rb +53 -0
  59. data/lib/net/ssh/test/packet.rb +101 -0
  60. data/lib/net/ssh/test/remote_packet.rb +40 -0
  61. data/lib/net/ssh/test/script.rb +180 -0
  62. data/lib/net/ssh/test/socket.rb +65 -0
  63. data/lib/net/ssh/test.rb +94 -0
  64. data/lib/net/ssh/transport/algorithms.rb +502 -0
  65. data/lib/net/ssh/transport/cipher_factory.rb +103 -0
  66. data/lib/net/ssh/transport/constants.rb +40 -0
  67. data/lib/net/ssh/transport/ctr.rb +115 -0
  68. data/lib/net/ssh/transport/hmac/abstract.rb +97 -0
  69. data/lib/net/ssh/transport/hmac/md5.rb +10 -0
  70. data/lib/net/ssh/transport/hmac/md5_96.rb +9 -0
  71. data/lib/net/ssh/transport/hmac/none.rb +13 -0
  72. data/lib/net/ssh/transport/hmac/ripemd160.rb +11 -0
  73. data/lib/net/ssh/transport/hmac/sha1.rb +11 -0
  74. data/lib/net/ssh/transport/hmac/sha1_96.rb +9 -0
  75. data/lib/net/ssh/transport/hmac/sha2_256.rb +11 -0
  76. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +9 -0
  77. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  78. data/lib/net/ssh/transport/hmac/sha2_512.rb +11 -0
  79. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +9 -0
  80. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  81. data/lib/net/ssh/transport/hmac.rb +47 -0
  82. data/lib/net/ssh/transport/identity_cipher.rb +57 -0
  83. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  84. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  85. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  86. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  87. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +37 -0
  88. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  89. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +122 -0
  90. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +72 -0
  91. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +11 -0
  92. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +39 -0
  93. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +21 -0
  94. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +21 -0
  95. data/lib/net/ssh/transport/kex.rb +31 -0
  96. data/lib/net/ssh/transport/key_expander.rb +30 -0
  97. data/lib/net/ssh/transport/openssl.rb +253 -0
  98. data/lib/net/ssh/transport/packet_stream.rb +280 -0
  99. data/lib/net/ssh/transport/server_version.rb +77 -0
  100. data/lib/net/ssh/transport/session.rb +354 -0
  101. data/lib/net/ssh/transport/state.rb +208 -0
  102. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  103. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  104. data/lib/net/ssh/verifiers/always.rb +58 -0
  105. data/lib/net/ssh/verifiers/never.rb +19 -0
  106. data/lib/net/ssh/version.rb +68 -0
  107. data/lib/net/ssh.rb +330 -0
  108. data/net-ssh-public_cert.pem +20 -0
  109. data/net-ssh.gemspec +44 -0
  110. data/support/ssh_tunnel_bug.rb +65 -0
  111. metadata +271 -0
@@ -0,0 +1,253 @@
1
+ require 'openssl'
2
+ require 'net/ssh/authentication/pub_key_fingerprint'
3
+
4
+ module OpenSSL
5
+ # This class is originally defined in the OpenSSL module. As needed, methods
6
+ # have been added to it by the Net::SSH module for convenience in dealing with
7
+ # SSH functionality.
8
+ class BN
9
+ # Converts a BN object to a string. The format used is that which is
10
+ # required by the SSH2 protocol.
11
+ def to_ssh
12
+ if zero?
13
+ return [0].pack("N")
14
+ else
15
+ buf = to_s(2)
16
+ if buf.getbyte(0)[7] == 1
17
+ return [buf.length + 1, 0, buf].pack("NCA*")
18
+ else
19
+ return [buf.length, buf].pack("NA*")
20
+ end
21
+ end
22
+ end
23
+ end
24
+
25
+ module PKey
26
+ class PKey
27
+ include Net::SSH::Authentication::PubKeyFingerprint
28
+ end
29
+
30
+ # This class is originally defined in the OpenSSL module. As needed, methods
31
+ # have been added to it by the Net::SSH module for convenience in dealing
32
+ # with SSH functionality.
33
+ class DH
34
+ # Determines whether the pub_key for this key is valid. (This algorithm
35
+ # lifted more-or-less directly from OpenSSH, dh.c, dh_pub_is_valid.)
36
+ def valid?
37
+ return false if pub_key.nil? || pub_key < 0
38
+
39
+ bits_set = 0
40
+ pub_key.num_bits.times { |i| bits_set += 1 if pub_key.bit_set?(i) }
41
+ return (bits_set > 1 && pub_key < p)
42
+ end
43
+ end
44
+
45
+ # This class is originally defined in the OpenSSL module. As needed, methods
46
+ # have been added to it by the Net::SSH module for convenience in dealing
47
+ # with SSH functionality.
48
+ class RSA
49
+ # Returns "ssh-rsa", which is the description of this key type used by the
50
+ # SSH2 protocol.
51
+ def ssh_type
52
+ "ssh-rsa"
53
+ end
54
+
55
+ alias ssh_signature_type ssh_type
56
+
57
+ # Converts the key to a blob, according to the SSH2 protocol.
58
+ def to_blob
59
+ @blob ||= Net::SSH::Buffer.from(:string, ssh_type, :bignum, e, :bignum, n).to_s
60
+ end
61
+
62
+ # Verifies the given signature matches the given 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)
74
+ end
75
+
76
+ # Returns the signature for the given data.
77
+ def ssh_do_sign(data)
78
+ sign(OpenSSL::Digest::SHA1.new, data)
79
+ end
80
+ end
81
+
82
+ # This class is originally defined in the OpenSSL module. As needed, methods
83
+ # have been added to it by the Net::SSH module for convenience in dealing
84
+ # with SSH functionality.
85
+ class DSA
86
+ # Returns "ssh-dss", which is the description of this key type used by the
87
+ # SSH2 protocol.
88
+ def ssh_type
89
+ "ssh-dss"
90
+ end
91
+
92
+ alias ssh_signature_type ssh_type
93
+
94
+ # Converts the key to a blob, according to the SSH2 protocol.
95
+ def to_blob
96
+ @blob ||= Net::SSH::Buffer.from(:string, ssh_type,
97
+ :bignum, p, :bignum, q, :bignum, g, :bignum, pub_key).to_s
98
+ end
99
+
100
+ # Verifies the given signature matches the given data.
101
+ def ssh_do_verify(sig, data, options = {})
102
+ sig_r = sig[0,20].unpack("H*")[0].to_i(16)
103
+ sig_s = sig[20,20].unpack("H*")[0].to_i(16)
104
+ a1sig = OpenSSL::ASN1::Sequence([
105
+ OpenSSL::ASN1::Integer(sig_r),
106
+ OpenSSL::ASN1::Integer(sig_s)
107
+ ])
108
+ return verify(OpenSSL::Digest::SHA1.new, a1sig.to_der, data)
109
+ end
110
+
111
+ # Signs the given data.
112
+ def ssh_do_sign(data)
113
+ sig = sign(OpenSSL::Digest::SHA1.new, data)
114
+ a1sig = OpenSSL::ASN1.decode(sig)
115
+
116
+ sig_r = a1sig.value[0].value.to_s(2)
117
+ sig_s = a1sig.value[1].value.to_s(2)
118
+
119
+ raise OpenSSL::PKey::DSAError, "bad sig size" if sig_r.length > 20 || sig_s.length > 20
120
+
121
+ sig_r = "\0" * (20 - sig_r.length) + sig_r if sig_r.length < 20
122
+ sig_s = "\0" * (20 - sig_s.length) + sig_s if sig_s.length < 20
123
+
124
+ return sig_r + sig_s
125
+ end
126
+ end
127
+
128
+ # This class is originally defined in the OpenSSL module. As needed, methods
129
+ # have been added to it by the Net::SSH module for convenience in dealing
130
+ # with SSH functionality.
131
+ class EC
132
+ CurveNameAlias = {
133
+ 'nistp256' => 'prime256v1',
134
+ 'nistp384' => 'secp384r1',
135
+ 'nistp521' => 'secp521r1'
136
+ }.freeze
137
+
138
+ CurveNameAliasInv = {
139
+ 'prime256v1' => 'nistp256',
140
+ 'secp384r1' => 'nistp384',
141
+ 'secp521r1' => 'nistp521'
142
+ }.freeze
143
+
144
+ def self.read_keyblob(curve_name_in_type, buffer)
145
+ curve_name_in_key = buffer.read_string
146
+
147
+ unless curve_name_in_type == curve_name_in_key
148
+ raise Net::SSH::Exception, "curve name mismatched (`#{curve_name_in_key}' with `#{curve_name_in_type}')"
149
+ end
150
+
151
+ public_key_oct = buffer.read_string
152
+ begin
153
+ key = OpenSSL::PKey::EC.new(OpenSSL::PKey::EC::CurveNameAlias[curve_name_in_key])
154
+ group = key.group
155
+ point = OpenSSL::PKey::EC::Point.new(group, OpenSSL::BN.new(public_key_oct, 2))
156
+ key.public_key = point
157
+
158
+ return key
159
+ rescue OpenSSL::PKey::ECError
160
+ raise NotImplementedError, "unsupported key type `#{type}'"
161
+ end
162
+ end
163
+
164
+ # Returns the description of this key type used by the
165
+ # SSH2 protocol, like "ecdsa-sha2-nistp256"
166
+ def ssh_type
167
+ "ecdsa-sha2-#{CurveNameAliasInv[group.curve_name]}"
168
+ end
169
+
170
+ alias ssh_signature_type ssh_type
171
+
172
+ def digester
173
+ if group.curve_name =~ /^[a-z]+(\d+)\w*\z/
174
+ curve_size = Regexp.last_match(1).to_i
175
+ if curve_size <= 256
176
+ OpenSSL::Digest::SHA256.new
177
+ elsif curve_size <= 384
178
+ OpenSSL::Digest::SHA384.new
179
+ else
180
+ OpenSSL::Digest::SHA512.new
181
+ end
182
+ else
183
+ OpenSSL::Digest::SHA256.new
184
+ end
185
+ end
186
+ private :digester
187
+
188
+ # Converts the key to a blob, according to the SSH2 protocol.
189
+ def to_blob
190
+ @blob ||= Net::SSH::Buffer.from(:string, ssh_type,
191
+ :string, CurveNameAliasInv[group.curve_name],
192
+ :mstring, public_key.to_bn.to_s(2)).to_s
193
+ @blob
194
+ end
195
+
196
+ # Verifies the given signature matches the given data.
197
+ def ssh_do_verify(sig, data, options = {})
198
+ digest = digester.digest(data)
199
+ a1sig = nil
200
+
201
+ begin
202
+ sig_r_len = sig[0, 4].unpack('H*')[0].to_i(16)
203
+ sig_l_len = sig[4 + sig_r_len, 4].unpack('H*')[0].to_i(16)
204
+
205
+ sig_r = sig[4, sig_r_len].unpack('H*')[0]
206
+ sig_s = sig[4 + sig_r_len + 4, sig_l_len].unpack('H*')[0]
207
+
208
+ a1sig = OpenSSL::ASN1::Sequence([
209
+ OpenSSL::ASN1::Integer(sig_r.to_i(16)),
210
+ OpenSSL::ASN1::Integer(sig_s.to_i(16))
211
+ ])
212
+ rescue StandardError
213
+ end
214
+
215
+ if a1sig.nil?
216
+ return false
217
+ else
218
+ dsa_verify_asn1(digest, a1sig.to_der)
219
+ end
220
+ end
221
+
222
+ # Returns the signature for the given data.
223
+ def ssh_do_sign(data)
224
+ digest = digester.digest(data)
225
+ sig = dsa_sign_asn1(digest)
226
+ a1sig = OpenSSL::ASN1.decode(sig)
227
+
228
+ sig_r = a1sig.value[0].value
229
+ sig_s = a1sig.value[1].value
230
+
231
+ Net::SSH::Buffer.from(:bignum, sig_r, :bignum, sig_s).to_s
232
+ end
233
+
234
+ class Point
235
+ # Returns the description of this key type used by the
236
+ # SSH2 protocol, like "ecdsa-sha2-nistp256"
237
+ def ssh_type
238
+ "ecdsa-sha2-#{CurveNameAliasInv[group.curve_name]}"
239
+ end
240
+
241
+ alias ssh_signature_type ssh_type
242
+
243
+ # Converts the key to a blob, according to the SSH2 protocol.
244
+ def to_blob
245
+ @blob ||= Net::SSH::Buffer.from(:string, ssh_type,
246
+ :string, CurveNameAliasInv[group.curve_name],
247
+ :mstring, to_bn.to_s(2)).to_s
248
+ @blob
249
+ end
250
+ end
251
+ end
252
+ end
253
+ end
@@ -0,0 +1,280 @@
1
+ require 'net/ssh/buffered_io'
2
+ require 'net/ssh/errors'
3
+ require 'net/ssh/packet'
4
+ require 'net/ssh/transport/cipher_factory'
5
+ require 'net/ssh/transport/hmac'
6
+ require 'net/ssh/transport/state'
7
+
8
+ module Net
9
+ module SSH
10
+ module Transport
11
+ # A module that builds additional functionality onto the Net::SSH::BufferedIo
12
+ # module. It adds SSH encryption, compression, and packet validation, as
13
+ # per the SSH2 protocol. It also adds an abstraction for polling packets,
14
+ # to allow for both blocking and non-blocking reads.
15
+ module PacketStream
16
+ PROXY_COMMAND_HOST_IP = '<no hostip for proxy command>'.freeze
17
+
18
+ include BufferedIo
19
+
20
+ def self.extended(object)
21
+ object.__send__(:initialize_ssh)
22
+ end
23
+
24
+ # The map of "hints" that can be used to modify the behavior of the packet
25
+ # stream. For instance, when authentication succeeds, an "authenticated"
26
+ # hint is set, which is used to determine whether or not to compress the
27
+ # data when using the "delayed" compression algorithm.
28
+ attr_reader :hints
29
+
30
+ # The server state object, which encapsulates the algorithms used to interpret
31
+ # packets coming from the server.
32
+ attr_reader :server
33
+
34
+ # The client state object, which encapsulates the algorithms used to build
35
+ # packets to send to the server.
36
+ attr_reader :client
37
+
38
+ # The name of the client (local) end of the socket, as reported by the
39
+ # socket.
40
+ def client_name
41
+ @client_name ||= begin
42
+ sockaddr = getsockname
43
+ begin
44
+ Socket.getnameinfo(sockaddr, Socket::NI_NAMEREQD).first
45
+ rescue StandardError
46
+ begin
47
+ Socket.getnameinfo(sockaddr).first
48
+ rescue StandardError
49
+ begin
50
+ Socket.gethostbyname(Socket.gethostname).first
51
+ rescue StandardError
52
+ lwarn { "the client ipaddr/name could not be determined" }
53
+ "unknown"
54
+ end
55
+ end
56
+ end
57
+ end
58
+ end
59
+
60
+ # The IP address of the peer (remote) end of the socket, as reported by
61
+ # the socket.
62
+ def peer_ip
63
+ @peer_ip ||=
64
+ if respond_to?(:getpeername)
65
+ addr = getpeername
66
+ Socket.getnameinfo(addr, Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV).first
67
+ else
68
+ PROXY_COMMAND_HOST_IP
69
+ end
70
+ end
71
+
72
+ # Returns true if the IO is available for reading, and false otherwise.
73
+ def available_for_read?
74
+ result = IO.select([self], nil, nil, 0)
75
+ result && result.first.any?
76
+ end
77
+
78
+ # Returns the next full packet. If the mode parameter is :nonblock (the
79
+ # default), then this will return immediately, whether a packet is
80
+ # available or not, and will return nil if there is no packet ready to be
81
+ # returned. If the mode parameter is :block, then this method will block
82
+ # until a packet is available or timeout seconds have passed.
83
+ def next_packet(mode=:nonblock, timeout=nil)
84
+ case mode
85
+ when :nonblock then
86
+ packet = poll_next_packet
87
+ return packet if packet
88
+
89
+ if available_for_read?
90
+ if fill <= 0
91
+ result = poll_next_packet
92
+ if result.nil?
93
+ raise Net::SSH::Disconnect, "connection closed by remote host"
94
+ else
95
+ return result
96
+ end
97
+ end
98
+ end
99
+ poll_next_packet
100
+
101
+ when :block then
102
+ loop do
103
+ packet = poll_next_packet
104
+ return packet if packet
105
+
106
+ result = IO.select([self], nil, nil, timeout)
107
+ raise Net::SSH::ConnectionTimeout, "timeout waiting for next packet" unless result
108
+ raise Net::SSH::Disconnect, "connection closed by remote host" if fill <= 0
109
+ end
110
+
111
+ else
112
+ raise ArgumentError, "expected :block or :nonblock, got #{mode.inspect}"
113
+ end
114
+ end
115
+
116
+ # Enqueues a packet to be sent, and blocks until the entire packet is
117
+ # sent.
118
+ def send_packet(payload)
119
+ enqueue_packet(payload)
120
+ wait_for_pending_sends
121
+ end
122
+
123
+ # Enqueues a packet to be sent, but does not immediately send the packet.
124
+ # The given payload is pre-processed according to the algorithms specified
125
+ # in the client state (compression, cipher, and hmac).
126
+ def enqueue_packet(payload)
127
+ # try to compress the packet
128
+ payload = client.compress(payload)
129
+
130
+ # the length of the packet, minus the padding
131
+ actual_length = (client.hmac.etm ? 0 : 4) + payload.bytesize + 1
132
+
133
+ # compute the padding length
134
+ padding_length = client.block_size - (actual_length % client.block_size)
135
+ padding_length += client.block_size if padding_length < 4
136
+
137
+ # compute the packet length (sans the length field itself)
138
+ packet_length = payload.bytesize + padding_length + 1
139
+
140
+ if packet_length < 16
141
+ padding_length += client.block_size
142
+ packet_length = payload.bytesize + padding_length + 1
143
+ end
144
+
145
+ padding = Array.new(padding_length) { rand(256) }.pack("C*")
146
+
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
170
+
171
+ message = encrypted_data + mac
172
+ end
173
+
174
+ debug { "queueing packet nr #{client.sequence_number} type #{payload.getbyte(0)} len #{packet_length}" }
175
+ enqueue(message)
176
+
177
+ client.increment(packet_length)
178
+
179
+ self
180
+ end
181
+
182
+ # Performs any pending cleanup necessary on the IO and its associated
183
+ # state objects. (See State#cleanup).
184
+ def cleanup
185
+ client.cleanup
186
+ server.cleanup
187
+ end
188
+
189
+ # If the IO object requires a rekey operation (as indicated by either its
190
+ # client or server state objects, see State#needs_rekey?), this will
191
+ # yield. Otherwise, this does nothing.
192
+ def if_needs_rekey?
193
+ if client.needs_rekey? || server.needs_rekey?
194
+ yield
195
+ client.reset! if client.needs_rekey?
196
+ server.reset! if server.needs_rekey?
197
+ end
198
+ end
199
+
200
+ protected
201
+
202
+ # Called when this module is used to extend an object. It initializes
203
+ # the states and generally prepares the object for use as a packet stream.
204
+ def initialize_ssh
205
+ @hints = {}
206
+ @server = State.new(self, :server)
207
+ @client = State.new(self, :client)
208
+ @packet = nil
209
+ initialize_buffered_io
210
+ end
211
+
212
+ # Tries to read the next packet. If there is insufficient data to read
213
+ # an entire packet, this returns immediately, otherwise the packet is
214
+ # read, post-processed according to the cipher, hmac, and compression
215
+ # algorithms specified in the server state object, and returned as a
216
+ # new Packet object.
217
+ # rubocop:disable Metrics/AbcSize
218
+ def poll_next_packet
219
+ aad_length = server.hmac.etm ? 4 : 0
220
+
221
+ if @packet.nil?
222
+ minimum = server.block_size < 4 ? 4 : server.block_size
223
+ return nil if available < minimum + aad_length
224
+
225
+ data = read_available(minimum + aad_length)
226
+
227
+ # decipher it
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
236
+ end
237
+
238
+ need = @packet_length + 4 - aad_length - server.block_size
239
+ raise Net::SSH::Exception, "padding error, need #{need} block #{server.block_size}" if need % server.block_size != 0
240
+
241
+ return nil if available < need + server.hmac.mac_length
242
+
243
+ if need > 0
244
+ # read the remainder of the packet and decrypt it.
245
+ data = read_available(need)
246
+ @mac_data += data if server.hmac.etm
247
+ @packet.append(server.update_cipher(data))
248
+ end
249
+
250
+ # get the hmac from the tail of the packet (if one exists), and
251
+ # then validate it.
252
+ real_hmac = read_available(server.hmac.mac_length) || ""
253
+
254
+ @packet.append(server.final_cipher)
255
+ padding_length = @packet.read_byte
256
+
257
+ payload = @packet.read(@packet_length - padding_length - 1)
258
+
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
265
+
266
+ # try to decompress the payload, in case compression is active
267
+ payload = server.decompress(payload)
268
+
269
+ debug { "received packet nr #{server.sequence_number} type #{payload.getbyte(0)} len #{@packet_length}" }
270
+
271
+ server.increment(@packet_length)
272
+ @packet = nil
273
+
274
+ return Packet.new(payload)
275
+ end
276
+ end
277
+ # rubocop:enable Metrics/AbcSize
278
+ end
279
+ end
280
+ end
@@ -0,0 +1,77 @@
1
+ require 'net/ssh/errors'
2
+ require 'net/ssh/loggable'
3
+ require 'net/ssh/version'
4
+
5
+ module Net
6
+ module SSH
7
+ module Transport
8
+ # Negotiates the SSH protocol version and trades information about server
9
+ # and client. This is never used directly--it is always called by the
10
+ # transport layer as part of the initialization process of the transport
11
+ # layer.
12
+ #
13
+ # Note that this class also encapsulates the negotiated version, and acts as
14
+ # the authoritative reference for any queries regarding the version in effect.
15
+ class ServerVersion
16
+ include Loggable
17
+
18
+ # The SSH version string as reported by Net::SSH
19
+ PROTO_VERSION = "SSH-2.0-Ruby/Net::SSH_#{Net::SSH::Version::CURRENT} #{RUBY_PLATFORM}"
20
+
21
+ # Any header text sent by the server prior to sending the version.
22
+ attr_reader :header
23
+
24
+ # The version string reported by the server.
25
+ attr_reader :version
26
+
27
+ # Instantiates a new ServerVersion and immediately (and synchronously)
28
+ # negotiates the SSH protocol in effect, using the given socket.
29
+ def initialize(socket, logger, timeout = nil)
30
+ @header = String.new
31
+ @version = nil
32
+ @logger = logger
33
+ negotiate!(socket, timeout)
34
+ end
35
+
36
+ private
37
+
38
+ # Negotiates the SSH protocol to use, via the given socket. If the server
39
+ # reports an incompatible SSH version (e.g., SSH1), this will raise an
40
+ # exception.
41
+ def negotiate!(socket, timeout)
42
+ info { "negotiating protocol version" }
43
+
44
+ debug { "local is `#{PROTO_VERSION}'" }
45
+ socket.write "#{PROTO_VERSION}\r\n"
46
+ socket.flush
47
+
48
+ raise Net::SSH::ConnectionTimeout, "timeout during server version negotiating" if timeout && !IO.select([socket], nil, nil, timeout)
49
+
50
+ loop do
51
+ @version = String.new
52
+ loop do
53
+ begin
54
+ b = socket.readpartial(1)
55
+ raise Net::SSH::Disconnect, "connection closed by remote host" if b.nil?
56
+ rescue EOFError
57
+ raise Net::SSH::Disconnect, "connection closed by remote host"
58
+ end
59
+ @version << b
60
+ break if b == "\n"
61
+ end
62
+ break if @version.match(/^SSH-/)
63
+
64
+ @header << @version
65
+ end
66
+
67
+ @version.chomp!
68
+ debug { "remote is `#{@version}'" }
69
+
70
+ raise Net::SSH::Exception, "incompatible SSH version `#{@version}'" unless @version.match(/^SSH-(1\.99|2\.0)-/)
71
+
72
+ raise Net::SSH::ConnectionTimeout, "timeout during client version negotiating" if timeout && !IO.select(nil, [socket], nil, timeout)
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end