net-ssh 3.2.0 → 7.2.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (210) hide show
  1. checksums.yaml +5 -5
  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 +93 -0
  8. data/.github/workflows/rubocop.yml +16 -0
  9. data/.gitignore +13 -0
  10. data/.rubocop.yml +22 -0
  11. data/.rubocop_todo.yml +1081 -0
  12. data/CHANGES.txt +237 -7
  13. data/DEVELOPMENT.md +23 -0
  14. data/Dockerfile +27 -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 +298 -0
  22. data/Rakefile +125 -74
  23. data/SECURITY.md +4 -0
  24. data/appveyor.yml +58 -0
  25. data/docker-compose.yml +23 -0
  26. data/lib/net/ssh/authentication/agent.rb +279 -18
  27. data/lib/net/ssh/authentication/certificate.rb +183 -0
  28. data/lib/net/ssh/authentication/constants.rb +17 -15
  29. data/lib/net/ssh/authentication/ed25519.rb +186 -0
  30. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  31. data/lib/net/ssh/authentication/key_manager.rb +86 -39
  32. data/lib/net/ssh/authentication/methods/abstract.rb +67 -48
  33. data/lib/net/ssh/authentication/methods/hostbased.rb +34 -37
  34. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +13 -13
  35. data/lib/net/ssh/authentication/methods/none.rb +16 -19
  36. data/lib/net/ssh/authentication/methods/password.rb +27 -17
  37. data/lib/net/ssh/authentication/methods/publickey.rb +96 -55
  38. data/lib/net/ssh/authentication/pageant.rb +471 -367
  39. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  40. data/lib/net/ssh/authentication/session.rb +131 -121
  41. data/lib/net/ssh/buffer.rb +399 -300
  42. data/lib/net/ssh/buffered_io.rb +154 -150
  43. data/lib/net/ssh/config.rb +308 -185
  44. data/lib/net/ssh/connection/channel.rb +635 -613
  45. data/lib/net/ssh/connection/constants.rb +29 -29
  46. data/lib/net/ssh/connection/event_loop.rb +123 -0
  47. data/lib/net/ssh/connection/keepalive.rb +55 -51
  48. data/lib/net/ssh/connection/session.rb +620 -551
  49. data/lib/net/ssh/connection/term.rb +125 -123
  50. data/lib/net/ssh/errors.rb +101 -99
  51. data/lib/net/ssh/key_factory.rb +197 -105
  52. data/lib/net/ssh/known_hosts.rb +214 -127
  53. data/lib/net/ssh/loggable.rb +50 -49
  54. data/lib/net/ssh/packet.rb +83 -79
  55. data/lib/net/ssh/prompt.rb +50 -81
  56. data/lib/net/ssh/proxy/command.rb +105 -90
  57. data/lib/net/ssh/proxy/errors.rb +12 -10
  58. data/lib/net/ssh/proxy/http.rb +82 -79
  59. data/lib/net/ssh/proxy/https.rb +50 -0
  60. data/lib/net/ssh/proxy/jump.rb +54 -0
  61. data/lib/net/ssh/proxy/socks4.rb +2 -6
  62. data/lib/net/ssh/proxy/socks5.rb +14 -17
  63. data/lib/net/ssh/service/forward.rb +370 -317
  64. data/lib/net/ssh/test/channel.rb +145 -136
  65. data/lib/net/ssh/test/extensions.rb +131 -110
  66. data/lib/net/ssh/test/kex.rb +34 -32
  67. data/lib/net/ssh/test/local_packet.rb +46 -44
  68. data/lib/net/ssh/test/packet.rb +89 -70
  69. data/lib/net/ssh/test/remote_packet.rb +32 -30
  70. data/lib/net/ssh/test/script.rb +156 -142
  71. data/lib/net/ssh/test/socket.rb +49 -48
  72. data/lib/net/ssh/test.rb +82 -77
  73. data/lib/net/ssh/transport/algorithms.rb +462 -359
  74. data/lib/net/ssh/transport/chacha20_poly1305_cipher.rb +117 -0
  75. data/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb +17 -0
  76. data/lib/net/ssh/transport/cipher_factory.rb +122 -99
  77. data/lib/net/ssh/transport/constants.rb +32 -24
  78. data/lib/net/ssh/transport/ctr.rb +42 -22
  79. data/lib/net/ssh/transport/hmac/abstract.rb +81 -63
  80. data/lib/net/ssh/transport/hmac/md5.rb +0 -2
  81. data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
  82. data/lib/net/ssh/transport/hmac/none.rb +0 -2
  83. data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
  84. data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
  85. data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
  86. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  87. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  88. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  89. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  90. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  91. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  92. data/lib/net/ssh/transport/hmac.rb +14 -12
  93. data/lib/net/ssh/transport/identity_cipher.rb +54 -44
  94. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  95. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  96. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  97. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  98. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
  99. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  100. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +119 -213
  101. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -61
  102. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  103. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
  104. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
  105. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
  106. data/lib/net/ssh/transport/kex.rb +15 -12
  107. data/lib/net/ssh/transport/key_expander.rb +24 -20
  108. data/lib/net/ssh/transport/openssl.rb +161 -124
  109. data/lib/net/ssh/transport/openssl_cipher_extensions.rb +8 -0
  110. data/lib/net/ssh/transport/packet_stream.rb +246 -185
  111. data/lib/net/ssh/transport/server_version.rb +55 -56
  112. data/lib/net/ssh/transport/session.rb +306 -255
  113. data/lib/net/ssh/transport/state.rb +178 -176
  114. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  115. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  116. data/lib/net/ssh/verifiers/always.rb +58 -0
  117. data/lib/net/ssh/verifiers/never.rb +19 -0
  118. data/lib/net/ssh/version.rb +55 -53
  119. data/lib/net/ssh.rb +111 -47
  120. data/net-ssh-public_cert.pem +18 -18
  121. data/net-ssh.gemspec +38 -205
  122. data/support/ssh_tunnel_bug.rb +5 -5
  123. data.tar.gz.sig +0 -0
  124. metadata +173 -118
  125. metadata.gz.sig +0 -0
  126. data/.travis.yml +0 -18
  127. data/README.rdoc +0 -182
  128. data/lib/net/ssh/authentication/agent/java_pageant.rb +0 -85
  129. data/lib/net/ssh/authentication/agent/socket.rb +0 -178
  130. data/lib/net/ssh/ruby_compat.rb +0 -46
  131. data/lib/net/ssh/verifiers/lenient.rb +0 -30
  132. data/lib/net/ssh/verifiers/null.rb +0 -12
  133. data/lib/net/ssh/verifiers/secure.rb +0 -52
  134. data/lib/net/ssh/verifiers/strict.rb +0 -24
  135. data/setup.rb +0 -1585
  136. data/support/arcfour_check.rb +0 -20
  137. data/test/README.txt +0 -18
  138. data/test/authentication/methods/common.rb +0 -28
  139. data/test/authentication/methods/test_abstract.rb +0 -51
  140. data/test/authentication/methods/test_hostbased.rb +0 -114
  141. data/test/authentication/methods/test_keyboard_interactive.rb +0 -121
  142. data/test/authentication/methods/test_none.rb +0 -41
  143. data/test/authentication/methods/test_password.rb +0 -95
  144. data/test/authentication/methods/test_publickey.rb +0 -148
  145. data/test/authentication/test_agent.rb +0 -232
  146. data/test/authentication/test_key_manager.rb +0 -240
  147. data/test/authentication/test_session.rb +0 -107
  148. data/test/common.rb +0 -125
  149. data/test/configs/auth_off +0 -5
  150. data/test/configs/auth_on +0 -4
  151. data/test/configs/empty +0 -0
  152. data/test/configs/eqsign +0 -3
  153. data/test/configs/exact_match +0 -8
  154. data/test/configs/host_plus +0 -10
  155. data/test/configs/multihost +0 -4
  156. data/test/configs/negative_match +0 -6
  157. data/test/configs/nohost +0 -19
  158. data/test/configs/numeric_host +0 -4
  159. data/test/configs/proxy_remote_user +0 -2
  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 -487
  164. data/test/connection/test_session.rb +0 -564
  165. data/test/integration/README.txt +0 -17
  166. data/test/integration/Vagrantfile +0 -12
  167. data/test/integration/common.rb +0 -63
  168. data/test/integration/playbook.yml +0 -56
  169. data/test/integration/test_forward.rb +0 -637
  170. data/test/integration/test_id_rsa_keys.rb +0 -96
  171. data/test/integration/test_proxy.rb +0 -93
  172. data/test/known_hosts/github +0 -1
  173. data/test/known_hosts/github_hash +0 -1
  174. data/test/manual/test_pageant.rb +0 -37
  175. data/test/start/test_connection.rb +0 -53
  176. data/test/start/test_options.rb +0 -57
  177. data/test/start/test_transport.rb +0 -28
  178. data/test/start/test_user_nil.rb +0 -27
  179. data/test/test_all.rb +0 -12
  180. data/test/test_buffer.rb +0 -433
  181. data/test/test_buffered_io.rb +0 -63
  182. data/test/test_config.rb +0 -268
  183. data/test/test_key_factory.rb +0 -191
  184. data/test/test_known_hosts.rb +0 -66
  185. data/test/transport/hmac/test_md5.rb +0 -41
  186. data/test/transport/hmac/test_md5_96.rb +0 -27
  187. data/test/transport/hmac/test_none.rb +0 -34
  188. data/test/transport/hmac/test_ripemd160.rb +0 -36
  189. data/test/transport/hmac/test_sha1.rb +0 -36
  190. data/test/transport/hmac/test_sha1_96.rb +0 -27
  191. data/test/transport/hmac/test_sha2_256.rb +0 -37
  192. data/test/transport/hmac/test_sha2_256_96.rb +0 -27
  193. data/test/transport/hmac/test_sha2_512.rb +0 -37
  194. data/test/transport/hmac/test_sha2_512_96.rb +0 -27
  195. data/test/transport/kex/test_diffie_hellman_group14_sha1.rb +0 -13
  196. data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +0 -150
  197. data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +0 -96
  198. data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +0 -19
  199. data/test/transport/kex/test_ecdh_sha2_nistp256.rb +0 -161
  200. data/test/transport/kex/test_ecdh_sha2_nistp384.rb +0 -38
  201. data/test/transport/kex/test_ecdh_sha2_nistp521.rb +0 -38
  202. data/test/transport/test_algorithms.rb +0 -328
  203. data/test/transport/test_cipher_factory.rb +0 -443
  204. data/test/transport/test_hmac.rb +0 -34
  205. data/test/transport/test_identity_cipher.rb +0 -40
  206. data/test/transport/test_packet_stream.rb +0 -1762
  207. data/test/transport/test_server_version.rb +0 -74
  208. data/test/transport/test_session.rb +0 -331
  209. data/test/transport/test_state.rb +0 -181
  210. data/test/verifiers/test_secure.rb +0 -40
@@ -1,240 +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
- PROXY_COMMAND_HOST_IP = '<no hostip for proxy command>'.freeze
18
-
19
- include BufferedIo
20
-
21
- def self.extended(object)
22
- object.__send__(:initialize_ssh)
23
- end
20
+ def self.extended(object)
21
+ object.__send__(:initialize_ssh)
22
+ end
24
23
 
25
- # The map of "hints" that can be used to modify the behavior of the packet
26
- # stream. For instance, when authentication succeeds, an "authenticated"
27
- # hint is set, which is used to determine whether or not to compress the
28
- # data when using the "delayed" compression algorithm.
29
- attr_reader :hints
30
-
31
- # The server state object, which encapsulates the algorithms used to interpret
32
- # packets coming from the server.
33
- attr_reader :server
34
-
35
- # The client state object, which encapsulates the algorithms used to build
36
- # packets to send to the server.
37
- attr_reader :client
38
-
39
- # The name of the client (local) end of the socket, as reported by the
40
- # socket.
41
- def client_name
42
- @client_name ||= begin
43
- sockaddr = getsockname
44
- begin
45
- Socket.getnameinfo(sockaddr, Socket::NI_NAMEREQD).first
46
- rescue
47
- begin
48
- Socket.getnameinfo(sockaddr).first
49
- 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
50
43
  begin
51
- Socket.gethostbyname(Socket.gethostname).first
52
- rescue
53
- lwarn { "the client ipaddr/name could not be determined" }
54
- "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
55
56
  end
56
57
  end
57
58
  end
58
- end
59
- end
60
59
 
61
- # The IP address of the peer (remote) end of the socket, as reported by
62
- # the socket.
63
- def peer_ip
64
- @peer_ip ||=
65
- if respond_to?(:getpeername)
66
- addr = getpeername
67
- Socket.getnameinfo(addr, Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV).first
68
- else
69
- PROXY_COMMAND_HOST_IP
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
70
  end
71
- end
72
-
73
- # Returns true if the IO is available for reading, and false otherwise.
74
- def available_for_read?
75
- result = Net::SSH::Compat.io_select([self], nil, nil, 0)
76
- result && result.first.any?
77
- end
78
-
79
- # Returns the next full packet. If the mode parameter is :nonblock (the
80
- # default), then this will return immediately, whether a packet is
81
- # available or not, and will return nil if there is no packet ready to be
82
- # returned. If the mode parameter is :block, then this method will block
83
- # until a packet is available.
84
- def next_packet(mode=:nonblock)
85
- case mode
86
- when :nonblock then
87
- if available_for_read?
88
- if fill <= 0
89
- raise Net::SSH::Disconnect, "connection closed by remote host"
90
- 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?
91
76
  end
92
- poll_next_packet
93
77
 
94
- when :block then
95
- loop do
96
- packet = poll_next_packet
97
- 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
98
100
 
99
- loop do
100
- result = Net::SSH::Compat.io_select([self]) or next
101
- break if result.first.any?
102
- end
101
+ when :block then
102
+ loop do
103
+ packet = poll_next_packet
104
+ return packet if packet
103
105
 
104
- if fill <= 0
105
- raise Net::SSH::Disconnect, "connection closed by remote host"
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}"
106
113
  end
107
114
  end
108
115
 
109
- else
110
- raise ArgumentError, "expected :block or :nonblock, got #{mode.inspect}"
111
- end
112
- 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
113
122
 
114
- # Enqueues a packet to be sent, and blocks until the entire packet is
115
- # sent.
116
- def send_packet(payload)
117
- enqueue_packet(payload)
118
- wait_for_pending_sends
119
- 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)
120
129
 
121
- # Enqueues a packet to be sent, but does not immediately send the packet.
122
- # The given payload is pre-processed according to the algorithms specified
123
- # in the client state (compression, cipher, and hmac).
124
- def enqueue_packet(payload)
125
- # try to compress the packet
126
- payload = client.compress(payload)
130
+ # the length of the packet, minus the padding
131
+ actual_length = (client.hmac.etm ? 0 : 4) + payload.bytesize + 1
127
132
 
128
- # the length of the packet, minus the padding
129
- 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
130
136
 
131
- # compute the padding length
132
- padding_length = client.block_size - (actual_length % client.block_size)
133
- 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
134
139
 
135
- # compute the packet length (sans the length field itself)
136
- 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
137
144
 
138
- if packet_length < 16
139
- padding_length += client.block_size
140
- packet_length = payload.bytesize + padding_length + 1
141
- end
145
+ padding = Array.new(padding_length) { rand(256) }.pack("C*")
142
146
 
143
- 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" }
144
152
 
145
- unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*")
146
- 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_lenght and the encrypted
155
+ # data.
156
+ length_data = [packet_length].pack("N")
147
157
 
148
- encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
149
- message = encrypted_data + mac
158
+ unencrypted_data = [padding_length, payload, padding].pack("CA*A*")
150
159
 
151
- debug { "queueing packet nr #{client.sequence_number} type #{payload.getbyte(0)} len #{packet_length}" }
152
- enqueue(message)
160
+ encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
153
161
 
154
- client.increment(packet_length)
162
+ mac_data = length_data + encrypted_data
155
163
 
156
- self
157
- end
164
+ mac = client.hmac.digest([client.sequence_number, mac_data].pack("NA*"))
158
165
 
159
- # Performs any pending cleanup necessary on the IO and its associated
160
- # state objects. (See State#cleanup).
161
- def cleanup
162
- client.cleanup
163
- server.cleanup
164
- end
166
+ message = mac_data + mac
167
+ else
168
+ unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*")
165
169
 
166
- # If the IO object requires a rekey operation (as indicated by either its
167
- # client or server state objects, see State#needs_rekey?), this will
168
- # yield. Otherwise, this does nothing.
169
- def if_needs_rekey?
170
- if client.needs_rekey? || server.needs_rekey?
171
- yield
172
- client.reset! if client.needs_rekey?
173
- server.reset! if server.needs_rekey?
174
- end
175
- end
170
+ mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*"))
176
171
 
177
- protected
172
+ encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
178
173
 
179
- # Called when this module is used to extend an object. It initializes
180
- # the states and generally prepares the object for use as a packet stream.
181
- def initialize_ssh
182
- @hints = {}
183
- @server = State.new(self, :server)
184
- @client = State.new(self, :client)
185
- @packet = nil
186
- initialize_buffered_io
187
- end
174
+ message = encrypted_data + mac
175
+ end
188
176
 
189
- # Tries to read the next packet. If there is insufficient data to read
190
- # an entire packet, this returns immediately, otherwise the packet is
191
- # read, post-processed according to the cipher, hmac, and compression
192
- # algorithms specified in the server state object, and returned as a
193
- # new Packet object.
194
- def poll_next_packet
195
- if @packet.nil?
196
- minimum = server.block_size < 4 ? 4 : server.block_size
197
- return nil if available < minimum
198
- data = read_available(minimum)
199
-
200
- # decipher it
201
- @packet = Net::SSH::Buffer.new(server.update_cipher(data))
202
- @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
203
183
  end
204
184
 
205
- need = @packet_length + 4 - server.block_size
206
- 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
207
191
 
208
- return nil if available < need + server.hmac.mac_length
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
209
202
 
210
- if need > 0
211
- # read the remainder of the packet and decrypt it.
212
- data = read_available(need)
213
- @packet.append(server.update_cipher(data))
203
+ protected
204
+
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
214
213
  end
215
214
 
216
- # get the hmac from the tail of the packet (if one exists), and
217
- # then validate it.
218
- 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 ? 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
219
244
 
220
- @packet.append(server.final_cipher)
221
- 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
222
247
 
223
- 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
224
253
 
225
- my_computed_hmac = server.hmac.digest([server.sequence_number, @packet.content].pack("NA*"))
226
- 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
227
264
 
228
- # try to decompress the payload, in case compression is active
229
- 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)
230
289
 
231
- 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}" }
232
291
 
233
- server.increment(@packet_length)
234
- @packet = nil
292
+ server.increment(@packet_length)
293
+ @packet = nil
235
294
 
236
- return Packet.new(payload)
295
+ return Packet.new(payload)
296
+ end
237
297
  end
298
+ # rubocop:enable Metrics/AbcSize
299
+ end
238
300
  end
239
-
240
- end; end; end
301
+ end
@@ -2,77 +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, timeout = nil)
29
- @header = ""
30
- @version = nil
31
- @logger = logger
32
- negotiate!(socket, timeout)
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, timeout)
41
- info { "negotiating protocol version" }
44
+ debug { "local is `#{PROTO_VERSION}'" }
45
+ socket.write "#{PROTO_VERSION}\r\n"
46
+ socket.flush
42
47
 
43
- debug { "local is `#{PROTO_VERSION}'" }
44
- socket.write "#{PROTO_VERSION}\r\n"
45
- socket.flush
48
+ raise Net::SSH::ConnectionTimeout, "timeout during server version negotiating" if timeout && !IO.select([socket], nil, nil, timeout)
46
49
 
47
- if timeout && !IO.select([socket], nil, nil, timeout)
48
- raise Net::SSH::ConnectionTimeout, "timeout during server version negotiating"
49
- end
50
- loop do
51
- @version = ""
52
50
  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"
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"
58
61
  end
59
- @version << b
60
- break if b == "\n"
62
+ break if @version.match(/^SSH-/)
63
+
64
+ @header << @version
61
65
  end
62
- break if @version.match(/^SSH-/)
63
- @header << @version
64
- end
65
66
 
66
- @version.chomp!
67
- debug { "remote is `#{@version}'" }
67
+ @version.chomp!
68
+ debug { "remote is `#{@version}'" }
68
69
 
69
- unless @version.match(/^SSH-(1\.99|2\.0)-/)
70
- raise Net::SSH::Exception, "incompatible SSH version `#{@version}'"
71
- end
70
+ raise Net::SSH::Exception, "incompatible SSH version `#{@version}'" unless @version.match(/^SSH-(1\.99|2\.0)-/)
72
71
 
73
- if timeout && !IO.select(nil, [socket], nil, timeout)
74
- raise Net::SSH::ConnectionTimeout, "timeout during client version negotiating"
72
+ raise Net::SSH::ConnectionTimeout, "timeout during client version negotiating" if timeout && !IO.select(nil, [socket], nil, timeout)
75
73
  end
76
74
  end
75
+ end
77
76
  end
78
- end; end; end
77
+ end