net-ssh 2.7.0 → 7.3.0

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 (199) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data/.dockerignore +6 -0
  4. data/.github/FUNDING.yml +1 -0
  5. data/.github/config/rubocop_linter_action.yml +4 -0
  6. data/.github/workflows/ci-with-docker.yml +44 -0
  7. data/.github/workflows/ci.yml +94 -0
  8. data/.github/workflows/rubocop.yml +16 -0
  9. data/.gitignore +15 -0
  10. data/.rubocop.yml +22 -0
  11. data/.rubocop_todo.yml +1081 -0
  12. data/CHANGES.txt +387 -0
  13. data/DEVELOPMENT.md +23 -0
  14. data/Dockerfile +29 -0
  15. data/Dockerfile.openssl3 +17 -0
  16. data/Gemfile +13 -0
  17. data/Gemfile.noed25519 +12 -0
  18. data/Gemfile.norbnacl +12 -0
  19. data/ISSUE_TEMPLATE.md +30 -0
  20. data/Manifest +4 -5
  21. data/README.md +303 -0
  22. data/Rakefile +174 -40
  23. data/SECURITY.md +4 -0
  24. data/THANKS.txt +25 -0
  25. data/appveyor.yml +58 -0
  26. data/docker-compose.yml +25 -0
  27. data/lib/net/ssh/authentication/agent.rb +279 -18
  28. data/lib/net/ssh/authentication/certificate.rb +183 -0
  29. data/lib/net/ssh/authentication/constants.rb +17 -15
  30. data/lib/net/ssh/authentication/ed25519.rb +184 -0
  31. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  32. data/lib/net/ssh/authentication/key_manager.rb +125 -54
  33. data/lib/net/ssh/authentication/methods/abstract.rb +67 -48
  34. data/lib/net/ssh/authentication/methods/hostbased.rb +34 -37
  35. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +19 -12
  36. data/lib/net/ssh/authentication/methods/none.rb +16 -19
  37. data/lib/net/ssh/authentication/methods/password.rb +56 -19
  38. data/lib/net/ssh/authentication/methods/publickey.rb +96 -55
  39. data/lib/net/ssh/authentication/pageant.rb +483 -246
  40. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  41. data/lib/net/ssh/authentication/session.rb +138 -120
  42. data/lib/net/ssh/buffer.rb +399 -300
  43. data/lib/net/ssh/buffered_io.rb +154 -150
  44. data/lib/net/ssh/config.rb +361 -166
  45. data/lib/net/ssh/connection/channel.rb +640 -596
  46. data/lib/net/ssh/connection/constants.rb +29 -29
  47. data/lib/net/ssh/connection/event_loop.rb +123 -0
  48. data/lib/net/ssh/connection/keepalive.rb +59 -0
  49. data/lib/net/ssh/connection/session.rb +628 -548
  50. data/lib/net/ssh/connection/term.rb +125 -123
  51. data/lib/net/ssh/errors.rb +101 -95
  52. data/lib/net/ssh/key_factory.rb +198 -100
  53. data/lib/net/ssh/known_hosts.rb +221 -98
  54. data/lib/net/ssh/loggable.rb +50 -49
  55. data/lib/net/ssh/packet.rb +83 -79
  56. data/lib/net/ssh/prompt.rb +50 -81
  57. data/lib/net/ssh/proxy/command.rb +108 -60
  58. data/lib/net/ssh/proxy/errors.rb +12 -10
  59. data/lib/net/ssh/proxy/http.rb +82 -78
  60. data/lib/net/ssh/proxy/https.rb +50 -0
  61. data/lib/net/ssh/proxy/jump.rb +54 -0
  62. data/lib/net/ssh/proxy/socks4.rb +5 -8
  63. data/lib/net/ssh/proxy/socks5.rb +18 -20
  64. data/lib/net/ssh/service/forward.rb +383 -255
  65. data/lib/net/ssh/test/channel.rb +145 -136
  66. data/lib/net/ssh/test/extensions.rb +131 -110
  67. data/lib/net/ssh/test/kex.rb +34 -32
  68. data/lib/net/ssh/test/local_packet.rb +46 -44
  69. data/lib/net/ssh/test/packet.rb +89 -70
  70. data/lib/net/ssh/test/remote_packet.rb +32 -30
  71. data/lib/net/ssh/test/script.rb +156 -142
  72. data/lib/net/ssh/test/socket.rb +49 -48
  73. data/lib/net/ssh/test.rb +82 -77
  74. data/lib/net/ssh/transport/aes128_gcm.rb +40 -0
  75. data/lib/net/ssh/transport/aes256_gcm.rb +40 -0
  76. data/lib/net/ssh/transport/algorithms.rb +472 -348
  77. data/lib/net/ssh/transport/chacha20_poly1305_cipher.rb +117 -0
  78. data/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb +17 -0
  79. data/lib/net/ssh/transport/cipher_factory.rb +124 -100
  80. data/lib/net/ssh/transport/constants.rb +32 -24
  81. data/lib/net/ssh/transport/ctr.rb +42 -22
  82. data/lib/net/ssh/transport/gcm_cipher.rb +207 -0
  83. data/lib/net/ssh/transport/hmac/abstract.rb +97 -63
  84. data/lib/net/ssh/transport/hmac/md5.rb +0 -2
  85. data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
  86. data/lib/net/ssh/transport/hmac/none.rb +0 -2
  87. data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
  88. data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
  89. data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
  90. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  91. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  92. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  93. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  94. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  95. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  96. data/lib/net/ssh/transport/hmac.rb +14 -12
  97. data/lib/net/ssh/transport/identity_cipher.rb +54 -44
  98. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  99. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  100. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  101. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  102. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
  103. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  104. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +119 -213
  105. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -61
  106. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  107. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
  108. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
  109. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
  110. data/lib/net/ssh/transport/kex.rb +15 -12
  111. data/lib/net/ssh/transport/key_expander.rb +24 -20
  112. data/lib/net/ssh/transport/openssl.rb +161 -124
  113. data/lib/net/ssh/transport/openssl_cipher_extensions.rb +8 -0
  114. data/lib/net/ssh/transport/packet_stream.rb +246 -183
  115. data/lib/net/ssh/transport/server_version.rb +57 -51
  116. data/lib/net/ssh/transport/session.rb +307 -235
  117. data/lib/net/ssh/transport/state.rb +178 -176
  118. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  119. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  120. data/lib/net/ssh/verifiers/always.rb +58 -0
  121. data/lib/net/ssh/verifiers/never.rb +19 -0
  122. data/lib/net/ssh/version.rb +57 -51
  123. data/lib/net/ssh.rb +140 -40
  124. data/net-ssh-public_cert.pem +21 -0
  125. data/net-ssh.gemspec +39 -184
  126. data/support/ssh_tunnel_bug.rb +5 -5
  127. data.tar.gz.sig +0 -0
  128. metadata +205 -99
  129. metadata.gz.sig +0 -0
  130. data/README.rdoc +0 -219
  131. data/Rudyfile +0 -96
  132. data/gem-public_cert.pem +0 -20
  133. data/lib/net/ssh/authentication/agent/java_pageant.rb +0 -85
  134. data/lib/net/ssh/authentication/agent/socket.rb +0 -170
  135. data/lib/net/ssh/ruby_compat.rb +0 -51
  136. data/lib/net/ssh/verifiers/lenient.rb +0 -30
  137. data/lib/net/ssh/verifiers/null.rb +0 -12
  138. data/lib/net/ssh/verifiers/secure.rb +0 -54
  139. data/lib/net/ssh/verifiers/strict.rb +0 -24
  140. data/setup.rb +0 -1585
  141. data/support/arcfour_check.rb +0 -20
  142. data/test/README.txt +0 -47
  143. data/test/authentication/methods/common.rb +0 -28
  144. data/test/authentication/methods/test_abstract.rb +0 -51
  145. data/test/authentication/methods/test_hostbased.rb +0 -114
  146. data/test/authentication/methods/test_keyboard_interactive.rb +0 -100
  147. data/test/authentication/methods/test_none.rb +0 -41
  148. data/test/authentication/methods/test_password.rb +0 -52
  149. data/test/authentication/methods/test_publickey.rb +0 -148
  150. data/test/authentication/test_agent.rb +0 -205
  151. data/test/authentication/test_key_manager.rb +0 -218
  152. data/test/authentication/test_session.rb +0 -108
  153. data/test/common.rb +0 -108
  154. data/test/configs/eqsign +0 -3
  155. data/test/configs/exact_match +0 -8
  156. data/test/configs/host_plus +0 -10
  157. data/test/configs/multihost +0 -4
  158. data/test/configs/nohost +0 -19
  159. data/test/configs/numeric_host +0 -4
  160. data/test/configs/send_env +0 -2
  161. data/test/configs/substitutes +0 -8
  162. data/test/configs/wild_cards +0 -14
  163. data/test/connection/test_channel.rb +0 -467
  164. data/test/connection/test_session.rb +0 -526
  165. data/test/known_hosts/github +0 -1
  166. data/test/manual/test_forward.rb +0 -223
  167. data/test/start/test_options.rb +0 -36
  168. data/test/start/test_transport.rb +0 -28
  169. data/test/test_all.rb +0 -11
  170. data/test/test_buffer.rb +0 -433
  171. data/test/test_buffered_io.rb +0 -63
  172. data/test/test_config.rb +0 -151
  173. data/test/test_key_factory.rb +0 -173
  174. data/test/test_known_hosts.rb +0 -13
  175. data/test/transport/hmac/test_md5.rb +0 -41
  176. data/test/transport/hmac/test_md5_96.rb +0 -27
  177. data/test/transport/hmac/test_none.rb +0 -34
  178. data/test/transport/hmac/test_ripemd160.rb +0 -36
  179. data/test/transport/hmac/test_sha1.rb +0 -36
  180. data/test/transport/hmac/test_sha1_96.rb +0 -27
  181. data/test/transport/hmac/test_sha2_256.rb +0 -37
  182. data/test/transport/hmac/test_sha2_256_96.rb +0 -27
  183. data/test/transport/hmac/test_sha2_512.rb +0 -37
  184. data/test/transport/hmac/test_sha2_512_96.rb +0 -27
  185. data/test/transport/kex/test_diffie_hellman_group14_sha1.rb +0 -13
  186. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +0 -146
  187. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +0 -92
  188. data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +0 -34
  189. data/test/transport/kex/test_ecdh_sha2_nistp256.rb +0 -161
  190. data/test/transport/kex/test_ecdh_sha2_nistp384.rb +0 -38
  191. data/test/transport/kex/test_ecdh_sha2_nistp521.rb +0 -38
  192. data/test/transport/test_algorithms.rb +0 -330
  193. data/test/transport/test_cipher_factory.rb +0 -443
  194. data/test/transport/test_hmac.rb +0 -34
  195. data/test/transport/test_identity_cipher.rb +0 -40
  196. data/test/transport/test_packet_stream.rb +0 -1755
  197. data/test/transport/test_server_version.rb +0 -78
  198. data/test/transport/test_session.rb +0 -319
  199. data/test/transport/test_state.rb +0 -181
@@ -1,238 +1,301 @@
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'
8
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 # rubocop:disable Metrics/ModuleLength
16
+ PROXY_COMMAND_HOST_IP = '<no hostip for proxy command>'.freeze
9
17
 
10
- module Net; module SSH; module Transport
18
+ include BufferedIo
11
19
 
12
- # A module that builds additional functionality onto the Net::SSH::BufferedIo
13
- # module. It adds SSH encryption, compression, and packet validation, as
14
- # per the SSH2 protocol. It also adds an abstraction for polling packets,
15
- # to allow for both blocking and non-blocking reads.
16
- module PacketStream
17
- include BufferedIo
18
-
19
- def self.extended(object)
20
- object.__send__(:initialize_ssh)
21
- end
20
+ def self.extended(object)
21
+ object.__send__(:initialize_ssh)
22
+ end
22
23
 
23
- # The map of "hints" that can be used to modify the behavior of the packet
24
- # stream. For instance, when authentication succeeds, an "authenticated"
25
- # hint is set, which is used to determine whether or not to compress the
26
- # data when using the "delayed" compression algorithm.
27
- attr_reader :hints
28
-
29
- # The server state object, which encapsulates the algorithms used to interpret
30
- # packets coming from the server.
31
- attr_reader :server
32
-
33
- # The client state object, which encapsulates the algorithms used to build
34
- # packets to send to the server.
35
- attr_reader :client
36
-
37
- # The name of the client (local) end of the socket, as reported by the
38
- # socket.
39
- def client_name
40
- @client_name ||= begin
41
- sockaddr = getsockname
42
- begin
43
- Socket.getnameinfo(sockaddr, Socket::NI_NAMEREQD).first
44
- rescue
45
- begin
46
- Socket.getnameinfo(sockaddr).first
47
- rescue
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
48
43
  begin
49
- Socket.gethostbyname(Socket.gethostname).first
50
- rescue
51
- lwarn { "the client ipaddr/name could not be determined" }
52
- "unknown"
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
53
56
  end
54
57
  end
55
58
  end
56
- end
57
- end
58
59
 
59
- # The IP address of the peer (remote) end of the socket, as reported by
60
- # the socket.
61
- def peer_ip
62
- @peer_ip ||=
63
- if respond_to?(:getpeername)
64
- addr = getpeername
65
- Socket.getnameinfo(addr, Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV).first
66
- else
67
- "<no hostip for proxy command>"
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
68
70
  end
69
- end
70
-
71
- # Returns true if the IO is available for reading, and false otherwise.
72
- def available_for_read?
73
- result = Net::SSH::Compat.io_select([self], nil, nil, 0)
74
- result && result.first.any?
75
- end
76
-
77
- # Returns the next full packet. If the mode parameter is :nonblock (the
78
- # default), then this will return immediately, whether a packet is
79
- # available or not, and will return nil if there is no packet ready to be
80
- # returned. If the mode parameter is :block, then this method will block
81
- # until a packet is available.
82
- def next_packet(mode=:nonblock)
83
- case mode
84
- when :nonblock then
85
- if available_for_read?
86
- if fill <= 0
87
- raise Net::SSH::Disconnect, "connection closed by remote host"
88
- 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?
89
76
  end
90
- poll_next_packet
91
77
 
92
- when :block then
93
- loop do
94
- packet = poll_next_packet
95
- return packet if packet
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
96
100
 
97
- loop do
98
- result = Net::SSH::Compat.io_select([self]) or next
99
- break if result.first.any?
100
- end
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
101
110
 
102
- if fill <= 0
103
- raise Net::SSH::Disconnect, "connection closed by remote host"
111
+ else
112
+ raise ArgumentError, "expected :block or :nonblock, got #{mode.inspect}"
104
113
  end
105
114
  end
106
115
 
107
- else
108
- raise ArgumentError, "expected :block or :nonblock, got #{mode.inspect}"
109
- end
110
- end
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
111
122
 
112
- # Enqueues a packet to be sent, and blocks until the entire packet is
113
- # sent.
114
- def send_packet(payload)
115
- enqueue_packet(payload)
116
- wait_for_pending_sends
117
- end
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) # rubocop:disable Metrics/AbcSize
127
+ # try to compress the packet
128
+ payload = client.compress(payload)
118
129
 
119
- # Enqueues a packet to be sent, but does not immediately send the packet.
120
- # The given payload is pre-processed according to the algorithms specified
121
- # in the client state (compression, cipher, and hmac).
122
- def enqueue_packet(payload)
123
- # try to compress the packet
124
- payload = client.compress(payload)
130
+ # the length of the packet, minus the padding
131
+ actual_length = (client.hmac.etm || client.hmac.aead ? 0 : 4) + payload.bytesize + 1
125
132
 
126
- # the length of the packet, minus the padding
127
- actual_length = 4 + payload.bytesize + 1
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
128
136
 
129
- # compute the padding length
130
- padding_length = client.block_size - (actual_length % client.block_size)
131
- padding_length += client.block_size if padding_length < 4
137
+ # compute the packet length (sans the length field itself)
138
+ packet_length = payload.bytesize + padding_length + 1
132
139
 
133
- # compute the packet length (sans the length field itself)
134
- packet_length = payload.bytesize + padding_length + 1
140
+ if packet_length < 16
141
+ padding_length += client.block_size
142
+ packet_length = payload.bytesize + padding_length + 1
143
+ end
135
144
 
136
- if packet_length < 16
137
- padding_length += client.block_size
138
- packet_length = payload.bytesize + padding_length + 1
139
- end
145
+ padding = Array.new(padding_length) { rand(256) }.pack("C*")
140
146
 
141
- padding = Array.new(padding_length) { rand(256) }.pack("C*")
147
+ if client.cipher.implicit_mac?
148
+ unencrypted_data = [padding_length, payload, padding].pack("CA*A*")
149
+ message = client.cipher.update_cipher_mac(unencrypted_data, client.sequence_number)
150
+ elsif client.hmac.etm
151
+ debug { "using encrypt-then-mac" }
142
152
 
143
- unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*")
144
- mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*"))
153
+ # Encrypt padding_length, payload, and padding. Take MAC
154
+ # from the unencrypted packet_length and the encrypted
155
+ # data.
156
+ length_data = [packet_length].pack("N")
145
157
 
146
- encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
147
- message = encrypted_data + mac
158
+ unencrypted_data = [padding_length, payload, padding].pack("CA*A*")
148
159
 
149
- debug { "queueing packet nr #{client.sequence_number} type #{payload.getbyte(0)} len #{packet_length}" }
150
- enqueue(message)
160
+ encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
151
161
 
152
- client.increment(packet_length)
162
+ mac_data = length_data + encrypted_data
153
163
 
154
- self
155
- end
164
+ mac = client.hmac.digest([client.sequence_number, mac_data].pack("NA*"))
156
165
 
157
- # Performs any pending cleanup necessary on the IO and its associated
158
- # state objects. (See State#cleanup).
159
- def cleanup
160
- client.cleanup
161
- server.cleanup
162
- end
166
+ message = mac_data + mac
167
+ else
168
+ unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*")
163
169
 
164
- # If the IO object requires a rekey operation (as indicated by either its
165
- # client or server state objects, see State#needs_rekey?), this will
166
- # yield. Otherwise, this does nothing.
167
- def if_needs_rekey?
168
- if client.needs_rekey? || server.needs_rekey?
169
- yield
170
- client.reset! if client.needs_rekey?
171
- server.reset! if server.needs_rekey?
172
- end
173
- end
170
+ mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*"))
174
171
 
175
- protected
172
+ encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
176
173
 
177
- # Called when this module is used to extend an object. It initializes
178
- # the states and generally prepares the object for use as a packet stream.
179
- def initialize_ssh
180
- @hints = {}
181
- @server = State.new(self, :server)
182
- @client = State.new(self, :client)
183
- @packet = nil
184
- initialize_buffered_io
185
- end
174
+ message = encrypted_data + mac
175
+ end
186
176
 
187
- # Tries to read the next packet. If there is insufficient data to read
188
- # an entire packet, this returns immediately, otherwise the packet is
189
- # read, post-processed according to the cipher, hmac, and compression
190
- # algorithms specified in the server state object, and returned as a
191
- # new Packet object.
192
- def poll_next_packet
193
- if @packet.nil?
194
- minimum = server.block_size < 4 ? 4 : server.block_size
195
- return nil if available < minimum
196
- data = read_available(minimum)
197
-
198
- # decipher it
199
- @packet = Net::SSH::Buffer.new(server.update_cipher(data))
200
- @packet_length = @packet.read_long
177
+ debug { "queueing packet nr #{client.sequence_number} type #{payload.getbyte(0)} len #{packet_length}" }
178
+ enqueue(message)
179
+
180
+ client.increment(packet_length)
181
+
182
+ self
201
183
  end
202
184
 
203
- need = @packet_length + 4 - server.block_size
204
- raise Net::SSH::Exception, "padding error, need #{need} block #{server.block_size}" if need % server.block_size != 0
185
+ # Performs any pending cleanup necessary on the IO and its associated
186
+ # state objects. (See State#cleanup).
187
+ def cleanup
188
+ client.cleanup
189
+ server.cleanup
190
+ end
191
+
192
+ # If the IO object requires a rekey operation (as indicated by either its
193
+ # client or server state objects, see State#needs_rekey?), this will
194
+ # yield. Otherwise, this does nothing.
195
+ def if_needs_rekey?
196
+ if client.needs_rekey? || server.needs_rekey?
197
+ yield
198
+ client.reset! if client.needs_rekey?
199
+ server.reset! if server.needs_rekey?
200
+ end
201
+ end
205
202
 
206
- return nil if available < need + server.hmac.mac_length
203
+ protected
207
204
 
208
- if need > 0
209
- # read the remainder of the packet and decrypt it.
210
- data = read_available(need)
211
- @packet.append(server.update_cipher(data))
205
+ # Called when this module is used to extend an object. It initializes
206
+ # the states and generally prepares the object for use as a packet stream.
207
+ def initialize_ssh
208
+ @hints = {}
209
+ @server = State.new(self, :server)
210
+ @client = State.new(self, :client)
211
+ @packet = nil
212
+ initialize_buffered_io
212
213
  end
213
214
 
214
- # get the hmac from the tail of the packet (if one exists), and
215
- # then validate it.
216
- real_hmac = read_available(server.hmac.mac_length) || ""
215
+ # Tries to read the next packet. If there is insufficient data to read
216
+ # an entire packet, this returns immediately, otherwise the packet is
217
+ # read, post-processed according to the cipher, hmac, and compression
218
+ # algorithms specified in the server state object, and returned as a
219
+ # new Packet object.
220
+ # rubocop:disable Metrics/AbcSize
221
+ def poll_next_packet
222
+ aad_length = server.hmac.etm || server.hmac.aead ? 4 : 0
223
+
224
+ if @packet.nil?
225
+ minimum = server.block_size < 4 ? 4 : server.block_size
226
+ return nil if available < minimum + aad_length
227
+
228
+ data = read_available(minimum + aad_length)
229
+
230
+ # decipher it
231
+ if server.cipher.implicit_mac?
232
+ @packet_length = server.cipher.read_length(data[0...4], server.sequence_number)
233
+ @packet = Net::SSH::Buffer.new
234
+ @mac_data = data
235
+ elsif server.hmac.etm
236
+ @packet_length = data.unpack("N").first
237
+ @mac_data = data
238
+ @packet = Net::SSH::Buffer.new(server.update_cipher(data[aad_length..-1]))
239
+ else
240
+ @packet = Net::SSH::Buffer.new(server.update_cipher(data))
241
+ @packet_length = @packet.read_long
242
+ end
243
+ end
217
244
 
218
- @packet.append(server.final_cipher)
219
- padding_length = @packet.read_byte
245
+ need = @packet_length + 4 - aad_length - server.block_size
246
+ raise Net::SSH::Exception, "padding error, need #{need} block #{server.block_size}" if need % server.block_size != 0
220
247
 
221
- payload = @packet.read(@packet_length - padding_length - 1)
248
+ if server.cipher.implicit_mac?
249
+ return nil if available < need + server.cipher.mac_length
250
+ else
251
+ return nil if available < need + server.hmac.mac_length # rubocop:disable Style/IfInsideElse
252
+ end
222
253
 
223
- my_computed_hmac = server.hmac.digest([server.sequence_number, @packet.content].pack("NA*"))
224
- raise Net::SSH::Exception, "corrupted mac detected" if real_hmac != my_computed_hmac
254
+ if need > 0
255
+ # read the remainder of the packet and decrypt it.
256
+ data = read_available(need)
257
+ @mac_data += data if server.hmac.etm || server.cipher.implicit_mac?
258
+ unless server.cipher.implicit_mac?
259
+ @packet.append(
260
+ server.update_cipher(data)
261
+ )
262
+ end
263
+ end
225
264
 
226
- # try to decompress the payload, in case compression is active
227
- payload = server.decompress(payload)
265
+ if server.cipher.implicit_mac?
266
+ real_hmac = read_available(server.cipher.mac_length) || ""
267
+ @packet = Net::SSH::Buffer.new(server.cipher.read_and_mac(@mac_data, real_hmac, server.sequence_number))
268
+ padding_length = @packet.read_byte
269
+ payload = @packet.read(@packet_length - padding_length - 1)
270
+ else
271
+ # get the hmac from the tail of the packet (if one exists), and
272
+ # then validate it.
273
+ real_hmac = read_available(server.hmac.mac_length) || ""
274
+
275
+ @packet.append(server.final_cipher)
276
+ padding_length = @packet.read_byte
277
+
278
+ payload = @packet.read(@packet_length - padding_length - 1)
279
+
280
+ my_computed_hmac = if server.hmac.etm
281
+ server.hmac.digest([server.sequence_number, @mac_data].pack("NA*"))
282
+ else
283
+ server.hmac.digest([server.sequence_number, @packet.content].pack("NA*"))
284
+ end
285
+ raise Net::SSH::Exception, "corrupted hmac detected #{server.hmac.class}" if real_hmac != my_computed_hmac
286
+ end
287
+ # try to decompress the payload, in case compression is active
288
+ payload = server.decompress(payload)
228
289
 
229
- debug { "received packet nr #{server.sequence_number} type #{payload.getbyte(0)} len #{@packet_length}" }
290
+ debug { "received packet nr #{server.sequence_number} type #{payload.getbyte(0)} len #{@packet_length}" }
230
291
 
231
- server.increment(@packet_length)
232
- @packet = nil
292
+ server.increment(@packet_length)
293
+ @packet = nil
233
294
 
234
- return Packet.new(payload)
295
+ return Packet.new(payload)
296
+ end
235
297
  end
298
+ # rubocop:enable Metrics/AbcSize
299
+ end
236
300
  end
237
-
238
- end; end; end
301
+ end
@@ -2,70 +2,76 @@ require 'net/ssh/errors'
2
2
  require 'net/ssh/loggable'
3
3
  require 'net/ssh/version'
4
4
 
5
- module Net; module SSH; module Transport
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
6
17
 
7
- # Negotiates the SSH protocol version and trades information about server
8
- # and client. This is never used directly--it is always called by the
9
- # transport layer as part of the initialization process of the transport
10
- # layer.
11
- #
12
- # Note that this class also encapsulates the negotiated version, and acts as
13
- # the authoritative reference for any queries regarding the version in effect.
14
- class ServerVersion
15
- include Loggable
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}"
16
20
 
17
- # The SSH version string as reported by Net::SSH
18
- PROTO_VERSION = "SSH-2.0-Ruby/Net::SSH_#{Net::SSH::Version::CURRENT} #{RUBY_PLATFORM}"
21
+ # Any header text sent by the server prior to sending the version.
22
+ attr_reader :header
19
23
 
20
- # Any header text sent by the server prior to sending the version.
21
- attr_reader :header
24
+ # The version string reported by the server.
25
+ attr_reader :version
22
26
 
23
- # The version string reported by the server.
24
- attr_reader :version
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
25
35
 
26
- # Instantiates a new ServerVersion and immediately (and synchronously)
27
- # negotiates the SSH protocol in effect, using the given socket.
28
- def initialize(socket, logger)
29
- @header = ""
30
- @version = nil
31
- @logger = logger
32
- negotiate!(socket)
33
- end
36
+ private
34
37
 
35
- private
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" }
36
43
 
37
- # Negotiates the SSH protocol to use, via the given socket. If the server
38
- # reports an incompatible SSH version (e.g., SSH1), this will raise an
39
- # exception.
40
- def negotiate!(socket)
41
- info { "negotiating protocol version" }
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)
42
49
 
43
- loop do
44
- @version = ""
45
50
  loop do
46
- begin
47
- b = socket.readpartial(1)
48
- raise Net::SSH::Disconnect, "connection closed by remote host" if b.nil?
49
- rescue EOFError
50
- raise Net::SSH::Disconnect, "connection closed by remote host"
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"
51
61
  end
52
- @version << b
53
- break if b == "\n"
62
+ break if @version.match(/^SSH-/)
63
+
64
+ @header << @version
54
65
  end
55
- break if @version.match(/^SSH-/)
56
- @header << @version
57
- end
58
66
 
59
- @version.chomp!
60
- debug { "remote is `#{@version}'" }
67
+ @version.chomp!
68
+ debug { "remote is `#{@version}'" }
61
69
 
62
- unless @version.match(/^SSH-(1\.99|2\.0)-/)
63
- raise Net::SSH::Exception, "incompatible SSH version `#{@version}'"
64
- end
70
+ raise Net::SSH::Exception, "incompatible SSH version `#{@version}'" unless @version.match(/^SSH-(1\.99|2\.0)-/)
65
71
 
66
- debug { "local is `#{PROTO_VERSION}'" }
67
- socket.write "#{PROTO_VERSION}\r\n"
68
- socket.flush
72
+ raise Net::SSH::ConnectionTimeout, "timeout during client version negotiating" if timeout && !IO.select(nil, [socket], nil, timeout)
73
+ end
69
74
  end
75
+ end
70
76
  end
71
- end; end; end
77
+ end