net-ssh 4.2.0 → 7.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.dockerignore +6 -0
  4. data/.github/config/rubocop_linter_action.yml +4 -0
  5. data/.github/workflows/ci-with-docker.yml +44 -0
  6. data/.github/workflows/ci.yml +87 -0
  7. data/.github/workflows/rubocop.yml +13 -0
  8. data/.gitignore +7 -0
  9. data/.rubocop.yml +19 -2
  10. data/.rubocop_todo.yml +619 -667
  11. data/CHANGES.txt +110 -1
  12. data/Dockerfile +27 -0
  13. data/Dockerfile.openssl3 +17 -0
  14. data/Gemfile +3 -7
  15. data/{Gemfile.norbnacl → Gemfile.noed25519} +3 -1
  16. data/Manifest +4 -5
  17. data/README.md +293 -0
  18. data/Rakefile +45 -29
  19. data/appveyor.yml +8 -6
  20. data/docker-compose.yml +23 -0
  21. data/lib/net/ssh/authentication/agent.rb +248 -223
  22. data/lib/net/ssh/authentication/certificate.rb +178 -164
  23. data/lib/net/ssh/authentication/constants.rb +17 -15
  24. data/lib/net/ssh/authentication/ed25519.rb +141 -116
  25. data/lib/net/ssh/authentication/ed25519_loader.rb +28 -28
  26. data/lib/net/ssh/authentication/key_manager.rb +79 -36
  27. data/lib/net/ssh/authentication/methods/abstract.rb +62 -47
  28. data/lib/net/ssh/authentication/methods/hostbased.rb +34 -37
  29. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +3 -3
  30. data/lib/net/ssh/authentication/methods/none.rb +16 -19
  31. data/lib/net/ssh/authentication/methods/password.rb +15 -16
  32. data/lib/net/ssh/authentication/methods/publickey.rb +96 -55
  33. data/lib/net/ssh/authentication/pageant.rb +468 -465
  34. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  35. data/lib/net/ssh/authentication/session.rb +131 -122
  36. data/lib/net/ssh/buffer.rb +385 -332
  37. data/lib/net/ssh/buffered_io.rb +150 -151
  38. data/lib/net/ssh/config.rb +316 -239
  39. data/lib/net/ssh/connection/channel.rb +635 -613
  40. data/lib/net/ssh/connection/constants.rb +29 -29
  41. data/lib/net/ssh/connection/event_loop.rb +104 -95
  42. data/lib/net/ssh/connection/keepalive.rb +55 -51
  43. data/lib/net/ssh/connection/session.rb +614 -611
  44. data/lib/net/ssh/connection/term.rb +125 -123
  45. data/lib/net/ssh/errors.rb +101 -99
  46. data/lib/net/ssh/key_factory.rb +194 -108
  47. data/lib/net/ssh/known_hosts.rb +212 -134
  48. data/lib/net/ssh/loggable.rb +50 -49
  49. data/lib/net/ssh/packet.rb +83 -79
  50. data/lib/net/ssh/prompt.rb +51 -51
  51. data/lib/net/ssh/proxy/command.rb +105 -91
  52. data/lib/net/ssh/proxy/errors.rb +12 -10
  53. data/lib/net/ssh/proxy/http.rb +81 -81
  54. data/lib/net/ssh/proxy/https.rb +37 -36
  55. data/lib/net/ssh/proxy/jump.rb +49 -48
  56. data/lib/net/ssh/proxy/socks4.rb +2 -6
  57. data/lib/net/ssh/proxy/socks5.rb +14 -17
  58. data/lib/net/ssh/service/forward.rb +365 -362
  59. data/lib/net/ssh/test/channel.rb +145 -143
  60. data/lib/net/ssh/test/extensions.rb +131 -127
  61. data/lib/net/ssh/test/kex.rb +34 -32
  62. data/lib/net/ssh/test/local_packet.rb +46 -44
  63. data/lib/net/ssh/test/packet.rb +87 -84
  64. data/lib/net/ssh/test/remote_packet.rb +32 -30
  65. data/lib/net/ssh/test/script.rb +155 -155
  66. data/lib/net/ssh/test/socket.rb +49 -48
  67. data/lib/net/ssh/test.rb +82 -80
  68. data/lib/net/ssh/transport/algorithms.rb +433 -364
  69. data/lib/net/ssh/transport/cipher_factory.rb +95 -91
  70. data/lib/net/ssh/transport/constants.rb +32 -24
  71. data/lib/net/ssh/transport/ctr.rb +37 -15
  72. data/lib/net/ssh/transport/hmac/abstract.rb +81 -63
  73. data/lib/net/ssh/transport/hmac/md5.rb +0 -2
  74. data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
  75. data/lib/net/ssh/transport/hmac/none.rb +0 -2
  76. data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
  77. data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
  78. data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
  79. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  80. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  81. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  82. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  83. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  84. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  85. data/lib/net/ssh/transport/hmac.rb +14 -12
  86. data/lib/net/ssh/transport/identity_cipher.rb +54 -52
  87. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  88. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  89. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  90. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  91. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
  92. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  93. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +112 -217
  94. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -63
  95. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  96. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
  97. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
  98. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
  99. data/lib/net/ssh/transport/kex.rb +15 -12
  100. data/lib/net/ssh/transport/key_expander.rb +24 -21
  101. data/lib/net/ssh/transport/openssl.rb +158 -133
  102. data/lib/net/ssh/transport/packet_stream.rb +223 -191
  103. data/lib/net/ssh/transport/server_version.rb +55 -56
  104. data/lib/net/ssh/transport/session.rb +306 -259
  105. data/lib/net/ssh/transport/state.rb +178 -176
  106. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  107. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  108. data/lib/net/ssh/verifiers/always.rb +58 -0
  109. data/lib/net/ssh/verifiers/never.rb +19 -0
  110. data/lib/net/ssh/version.rb +55 -53
  111. data/lib/net/ssh.rb +47 -34
  112. data/net-ssh-public_cert.pem +18 -19
  113. data/net-ssh.gemspec +12 -11
  114. data/support/ssh_tunnel_bug.rb +5 -5
  115. data.tar.gz.sig +0 -0
  116. metadata +78 -73
  117. metadata.gz.sig +0 -0
  118. data/.travis.yml +0 -51
  119. data/Gemfile.norbnacl.lock +0 -41
  120. data/README.rdoc +0 -169
  121. data/lib/net/ssh/ruby_compat.rb +0 -24
  122. data/lib/net/ssh/verifiers/lenient.rb +0 -30
  123. data/lib/net/ssh/verifiers/null.rb +0 -12
  124. data/lib/net/ssh/verifiers/secure.rb +0 -52
  125. data/lib/net/ssh/verifiers/strict.rb +0 -24
  126. data/support/arcfour_check.rb +0 -20
@@ -5,429 +5,498 @@ require 'net/ssh/transport/cipher_factory'
5
5
  require 'net/ssh/transport/constants'
6
6
  require 'net/ssh/transport/hmac'
7
7
  require 'net/ssh/transport/kex'
8
+ require 'net/ssh/transport/kex/curve25519_sha256_loader'
8
9
  require 'net/ssh/transport/server_version'
9
10
  require 'net/ssh/authentication/ed25519_loader'
10
11
 
11
- module Net; module SSH; module Transport
12
-
13
- # Implements the higher-level logic behind an SSH key-exchange. It handles
14
- # both the initial exchange, as well as subsequent re-exchanges (as needed).
15
- # It also encapsulates the negotiation of the algorithms, and provides a
16
- # single point of access to the negotiated algorithms.
17
- #
18
- # You will never instantiate or reference this directly. It is used
19
- # internally by the transport layer.
20
- class Algorithms
21
- include Constants, Loggable
22
-
23
- # Define the default algorithms, in order of preference, supported by
24
- # Net::SSH.
25
- ALGORITHMS = {
26
- host_key: %w(ssh-rsa ssh-dss
27
- ssh-rsa-cert-v01@openssh.com
28
- ssh-rsa-cert-v00@openssh.com),
29
- kex: %w(diffie-hellman-group-exchange-sha1
30
- diffie-hellman-group1-sha1
31
- diffie-hellman-group14-sha1
32
- diffie-hellman-group-exchange-sha256),
33
- encryption: %w(aes128-cbc 3des-cbc blowfish-cbc cast128-cbc
34
- aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se
35
- idea-cbc arcfour128 arcfour256 arcfour
36
- aes128-ctr aes192-ctr aes256-ctr
37
- cast128-ctr blowfish-ctr 3des-ctr none),
38
-
39
- hmac: %w(hmac-sha1 hmac-md5 hmac-sha1-96 hmac-md5-96
40
- hmac-ripemd160 hmac-ripemd160@openssh.com
41
- hmac-sha2-256 hmac-sha2-512 hmac-sha2-256-96
42
- hmac-sha2-512-96 none),
43
-
44
- compression: %w(none zlib@openssh.com zlib),
45
- language: %w()
46
- }
47
- if defined?(OpenSSL::PKey::EC)
48
- ALGORITHMS[:host_key] += %w(ecdsa-sha2-nistp256
49
- ecdsa-sha2-nistp384
50
- ecdsa-sha2-nistp521)
51
- if Net::SSH::Authentication::ED25519Loader::LOADED
52
- ALGORITHMS[:host_key] += %w(ssh-ed25519)
53
- end
54
- ALGORITHMS[:kex] += %w(ecdh-sha2-nistp256
55
- ecdh-sha2-nistp384
56
- ecdh-sha2-nistp521)
57
- end
12
+ module Net
13
+ module SSH
14
+ module Transport
15
+ # Implements the higher-level logic behind an SSH key-exchange. It handles
16
+ # both the initial exchange, as well as subsequent re-exchanges (as needed).
17
+ # It also encapsulates the negotiation of the algorithms, and provides a
18
+ # single point of access to the negotiated algorithms.
19
+ #
20
+ # You will never instantiate or reference this directly. It is used
21
+ # internally by the transport layer.
22
+ class Algorithms
23
+ include Loggable
24
+ include Constants
25
+
26
+ # Define the default algorithms, in order of preference, supported by Net::SSH.
27
+ DEFAULT_ALGORITHMS = {
28
+ host_key: %w[ecdsa-sha2-nistp521-cert-v01@openssh.com
29
+ ecdsa-sha2-nistp384-cert-v01@openssh.com
30
+ ecdsa-sha2-nistp256-cert-v01@openssh.com
31
+ ecdsa-sha2-nistp521
32
+ ecdsa-sha2-nistp384
33
+ ecdsa-sha2-nistp256
34
+ ssh-rsa-cert-v01@openssh.com
35
+ ssh-rsa-cert-v00@openssh.com
36
+ ssh-rsa
37
+ rsa-sha2-256
38
+ rsa-sha2-512],
39
+
40
+ kex: %w[ecdh-sha2-nistp521
41
+ ecdh-sha2-nistp384
42
+ ecdh-sha2-nistp256
43
+ diffie-hellman-group-exchange-sha256
44
+ diffie-hellman-group14-sha256
45
+ diffie-hellman-group14-sha1],
46
+
47
+ encryption: %w[aes256-ctr aes192-ctr aes128-ctr],
48
+
49
+ hmac: %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com
50
+ hmac-sha2-512 hmac-sha2-256
51
+ hmac-sha1]
52
+ }.freeze
53
+
54
+ if Net::SSH::Authentication::ED25519Loader::LOADED
55
+ DEFAULT_ALGORITHMS[:host_key].unshift(
56
+ 'ssh-ed25519-cert-v01@openssh.com',
57
+ 'ssh-ed25519'
58
+ )
59
+ end
58
60
 
59
- # The underlying transport layer session that supports this object
60
- attr_reader :session
61
+ if Net::SSH::Transport::Kex::Curve25519Sha256Loader::LOADED
62
+ DEFAULT_ALGORITHMS[:kex].unshift(
63
+ 'curve25519-sha256',
64
+ 'curve25519-sha256@libssh.org'
65
+ )
66
+ end
61
67
 
62
- # The hash of options used to initialize this object
63
- attr_reader :options
68
+ # Define all algorithms, with the deprecated, supported by Net::SSH.
69
+ ALGORITHMS = {
70
+ host_key: DEFAULT_ALGORITHMS[:host_key] + %w[ssh-dss],
64
71
 
65
- # The kex algorithm to use settled on between the client and server.
66
- attr_reader :kex
72
+ kex: DEFAULT_ALGORITHMS[:kex] +
73
+ %w[diffie-hellman-group-exchange-sha1
74
+ diffie-hellman-group1-sha1],
67
75
 
68
- # The type of host key that will be used for this session.
69
- attr_reader :host_key
76
+ encryption: DEFAULT_ALGORITHMS[:encryption] +
77
+ %w[aes256-cbc aes192-cbc aes128-cbc
78
+ rijndael-cbc@lysator.liu.se
79
+ blowfish-ctr blowfish-cbc
80
+ cast128-ctr cast128-cbc
81
+ 3des-ctr 3des-cbc
82
+ idea-cbc
83
+ none],
70
84
 
71
- # The type of the cipher to use to encrypt packets sent from the client to
72
- # the server.
73
- attr_reader :encryption_client
85
+ hmac: DEFAULT_ALGORITHMS[:hmac] +
86
+ %w[hmac-sha2-512-96 hmac-sha2-256-96
87
+ hmac-sha1-96
88
+ hmac-ripemd160 hmac-ripemd160@openssh.com
89
+ hmac-md5 hmac-md5-96
90
+ none],
74
91
 
75
- # The type of the cipher to use to decrypt packets arriving from the server.
76
- attr_reader :encryption_server
92
+ compression: %w[none zlib@openssh.com zlib],
93
+ language: %w[]
94
+ }.freeze
77
95
 
78
- # The type of HMAC to use to sign packets sent by the client.
79
- attr_reader :hmac_client
96
+ # The underlying transport layer session that supports this object
97
+ attr_reader :session
80
98
 
81
- # The type of HMAC to use to validate packets arriving from the server.
82
- attr_reader :hmac_server
99
+ # The hash of options used to initialize this object
100
+ attr_reader :options
83
101
 
84
- # The type of compression to use to compress packets being sent by the client.
85
- attr_reader :compression_client
102
+ # The kex algorithm to use settled on between the client and server.
103
+ attr_reader :kex
86
104
 
87
- # The type of compression to use to decompress packets arriving from the server.
88
- attr_reader :compression_server
105
+ # The type of host key that will be used for this session.
106
+ attr_reader :host_key
89
107
 
90
- # The language that will be used in messages sent by the client.
91
- attr_reader :language_client
108
+ # The type of the cipher to use to encrypt packets sent from the client to
109
+ # the server.
110
+ attr_reader :encryption_client
92
111
 
93
- # The language that will be used in messages sent from the server.
94
- attr_reader :language_server
112
+ # The type of the cipher to use to decrypt packets arriving from the server.
113
+ attr_reader :encryption_server
95
114
 
96
- # The hash of algorithms preferred by the client, which will be told to
97
- # the server during algorithm negotiation.
98
- attr_reader :algorithms
115
+ # The type of HMAC to use to sign packets sent by the client.
116
+ attr_reader :hmac_client
99
117
 
100
- # The session-id for this session, as decided during the initial key exchange.
101
- attr_reader :session_id
118
+ # The type of HMAC to use to validate packets arriving from the server.
119
+ attr_reader :hmac_server
102
120
 
103
- # Returns true if the given packet can be processed during a key-exchange.
104
- def self.allowed_packet?(packet)
105
- ( 1.. 4).include?(packet.type) ||
106
- ( 6..19).include?(packet.type) ||
107
- (21..49).include?(packet.type)
108
- end
121
+ # The type of compression to use to compress packets being sent by the client.
122
+ attr_reader :compression_client
109
123
 
110
- # Instantiates a new Algorithms object, and prepares the hash of preferred
111
- # algorithms based on the options parameter and the ALGORITHMS constant.
112
- def initialize(session, options={})
113
- @session = session
114
- @logger = session.logger
115
- @options = options
116
- @algorithms = {}
117
- @pending = @initialized = false
118
- @client_packet = @server_packet = nil
119
- prepare_preferred_algorithms!
120
- end
124
+ # The type of compression to use to decompress packets arriving from the server.
125
+ attr_reader :compression_server
121
126
 
122
- # Start the algorithm negotation
123
- def start
124
- raise ArgumentError, "Cannot call start if it's negotiation started or done" if @pending || @initialized
125
- send_kexinit
126
- end
127
+ # The language that will be used in messages sent by the client.
128
+ attr_reader :language_client
127
129
 
128
- # Request a rekey operation. This will return immediately, and does not
129
- # actually perform the rekey operation. It does cause the session to change
130
- # state, however--until the key exchange finishes, no new packets will be
131
- # processed.
132
- def rekey!
133
- @client_packet = @server_packet = nil
134
- @initialized = false
135
- send_kexinit
136
- end
130
+ # The language that will be used in messages sent from the server.
131
+ attr_reader :language_server
137
132
 
138
- # Called by the transport layer when a KEXINIT packet is received, indicating
139
- # that the server wants to exchange keys. This can be spontaneous, or it
140
- # can be in response to a client-initiated rekey request (see #rekey!). Either
141
- # way, this will block until the key exchange completes.
142
- def accept_kexinit(packet)
143
- info { "got KEXINIT from server" }
144
- @server_data = parse_server_algorithm_packet(packet)
145
- @server_packet = @server_data[:raw]
146
- if !pending?
147
- send_kexinit
148
- else
149
- proceed!
150
- end
151
- end
133
+ # The hash of algorithms preferred by the client, which will be told to
134
+ # the server during algorithm negotiation.
135
+ attr_reader :algorithms
152
136
 
153
- # A convenience method for accessing the list of preferred types for a
154
- # specific algorithm (see #algorithms).
155
- def [](key)
156
- algorithms[key]
157
- end
137
+ # The session-id for this session, as decided during the initial key exchange.
138
+ attr_reader :session_id
158
139
 
159
- # Returns +true+ if a key-exchange is pending. This will be true from the
160
- # moment either the client or server requests the key exchange, until the
161
- # exchange completes. While an exchange is pending, only a limited number
162
- # of packets are allowed, so event processing essentially stops during this
163
- # period.
164
- def pending?
165
- @pending
166
- end
140
+ # Returns true if the given packet can be processed during a key-exchange.
141
+ def self.allowed_packet?(packet)
142
+ (1..4).include?(packet.type) ||
143
+ (6..19).include?(packet.type) ||
144
+ (21..49).include?(packet.type)
145
+ end
167
146
 
168
- # Returns true if no exchange is pending, and otherwise returns true or
169
- # false depending on whether the given packet is of a type that is allowed
170
- # during a key exchange.
171
- def allow?(packet)
172
- !pending? || Algorithms.allowed_packet?(packet)
173
- end
147
+ # Instantiates a new Algorithms object, and prepares the hash of preferred
148
+ # algorithms based on the options parameter and the ALGORITHMS constant.
149
+ def initialize(session, options = {})
150
+ @session = session
151
+ @logger = session.logger
152
+ @options = options
153
+ @algorithms = {}
154
+ @pending = @initialized = false
155
+ @client_packet = @server_packet = nil
156
+ prepare_preferred_algorithms!
157
+ end
174
158
 
175
- # Returns true if the algorithms have been negotiated at all.
176
- def initialized?
177
- @initialized
178
- end
159
+ # Start the algorithm negotation
160
+ def start
161
+ raise ArgumentError, "Cannot call start if it's negotiation started or done" if @pending || @initialized
179
162
 
180
- private
181
-
182
- # Sends a KEXINIT packet to the server. If a server KEXINIT has already
183
- # been received, this will then invoke #proceed! to proceed with the key
184
- # exchange, otherwise it returns immediately (but sets the object to the
185
- # pending state).
186
- def send_kexinit
187
- info { "sending KEXINIT" }
188
- @pending = true
189
- packet = build_client_algorithm_packet
190
- @client_packet = packet.to_s
191
- session.send_message(packet)
192
- proceed! if @server_packet
193
- end
163
+ send_kexinit
164
+ end
194
165
 
195
- # After both client and server have sent their KEXINIT packets, this
196
- # will do the algorithm negotiation and key exchange. Once both finish,
197
- # the object leaves the pending state and the method returns.
198
- def proceed!
199
- info { "negotiating algorithms" }
200
- negotiate_algorithms
201
- exchange_keys
202
- @pending = false
203
- end
166
+ # Request a rekey operation. This will return immediately, and does not
167
+ # actually perform the rekey operation. It does cause the session to change
168
+ # state, however--until the key exchange finishes, no new packets will be
169
+ # processed.
170
+ def rekey!
171
+ @client_packet = @server_packet = nil
172
+ @initialized = false
173
+ send_kexinit
174
+ end
204
175
 
205
- # Prepares the list of preferred algorithms, based on the options hash
206
- # that was given when the object was constructed, and the ALGORITHMS
207
- # constant. Also, when determining the host_key type to use, the known
208
- # hosts files are examined to see if the host has ever sent a host_key
209
- # before, and if so, that key type is used as the preferred type for
210
- # communicating with this server.
211
- def prepare_preferred_algorithms!
212
- options[:compression] = %w(zlib@openssh.com zlib) if options[:compression] == true
213
-
214
- ALGORITHMS.each do |algorithm, supported|
215
- algorithms[algorithm] = compose_algorithm_list(supported, options[algorithm], options[:append_all_supported_algorithms])
176
+ # Called by the transport layer when a KEXINIT packet is received, indicating
177
+ # that the server wants to exchange keys. This can be spontaneous, or it
178
+ # can be in response to a client-initiated rekey request (see #rekey!). Either
179
+ # way, this will block until the key exchange completes.
180
+ def accept_kexinit(packet)
181
+ info { "got KEXINIT from server" }
182
+ @server_data = parse_server_algorithm_packet(packet)
183
+ @server_packet = @server_data[:raw]
184
+ if !pending?
185
+ send_kexinit
186
+ else
187
+ proceed!
188
+ end
216
189
  end
217
190
 
218
- # for convention, make sure our list has the same keys as the server
219
- # list
191
+ # A convenience method for accessing the list of preferred types for a
192
+ # specific algorithm (see #algorithms).
193
+ def [](key)
194
+ algorithms[key]
195
+ end
220
196
 
221
- algorithms[:encryption_client ] = algorithms[:encryption_server ] = algorithms[:encryption]
222
- algorithms[:hmac_client ] = algorithms[:hmac_server ] = algorithms[:hmac]
223
- algorithms[:compression_client] = algorithms[:compression_server] = algorithms[:compression]
224
- algorithms[:language_client ] = algorithms[:language_server ] = algorithms[:language]
197
+ # Returns +true+ if a key-exchange is pending. This will be true from the
198
+ # moment either the client or server requests the key exchange, until the
199
+ # exchange completes. While an exchange is pending, only a limited number
200
+ # of packets are allowed, so event processing essentially stops during this
201
+ # period.
202
+ def pending?
203
+ @pending
204
+ end
225
205
 
226
- if !options.key?(:host_key)
227
- # make sure the host keys are specified in preference order, where any
228
- # existing known key for the host has preference.
206
+ # Returns true if no exchange is pending, and otherwise returns true or
207
+ # false depending on whether the given packet is of a type that is allowed
208
+ # during a key exchange.
209
+ def allow?(packet)
210
+ !pending? || Algorithms.allowed_packet?(packet)
211
+ end
212
+
213
+ # Returns true if the algorithms have been negotiated at all.
214
+ def initialized?
215
+ @initialized
216
+ end
229
217
 
230
- existing_keys = session.host_keys
231
- host_keys = existing_keys.map { |key| key.ssh_type }.uniq
232
- algorithms[:host_key].each do |name|
233
- host_keys << name unless host_keys.include?(name)
218
+ def host_key_format
219
+ case host_key
220
+ when /^([a-z0-9-]+)-cert-v\d{2}@openssh.com$/
221
+ Regexp.last_match[1]
222
+ else
223
+ host_key
234
224
  end
235
- algorithms[:host_key] = host_keys
236
225
  end
237
- end
238
226
 
239
- # Composes the list of algorithms by taking supported algorithms and matching with supplied options.
240
- def compose_algorithm_list(supported, option, append_all_supported_algorithms = false)
241
- return supported.dup unless option
227
+ private
228
+
229
+ # Sends a KEXINIT packet to the server. If a server KEXINIT has already
230
+ # been received, this will then invoke #proceed! to proceed with the key
231
+ # exchange, otherwise it returns immediately (but sets the object to the
232
+ # pending state).
233
+ def send_kexinit
234
+ info { "sending KEXINIT" }
235
+ @pending = true
236
+ packet = build_client_algorithm_packet
237
+ @client_packet = packet.to_s
238
+ session.send_message(packet)
239
+ proceed! if @server_packet
240
+ end
241
+
242
+ # After both client and server have sent their KEXINIT packets, this
243
+ # will do the algorithm negotiation and key exchange. Once both finish,
244
+ # the object leaves the pending state and the method returns.
245
+ def proceed!
246
+ info { "negotiating algorithms" }
247
+ negotiate_algorithms
248
+ exchange_keys
249
+ @pending = false
250
+ end
251
+
252
+ # Prepares the list of preferred algorithms, based on the options hash
253
+ # that was given when the object was constructed, and the ALGORITHMS
254
+ # constant. Also, when determining the host_key type to use, the known
255
+ # hosts files are examined to see if the host has ever sent a host_key
256
+ # before, and if so, that key type is used as the preferred type for
257
+ # communicating with this server.
258
+ def prepare_preferred_algorithms!
259
+ options[:compression] = %w[zlib@openssh.com zlib] if options[:compression] == true
260
+
261
+ ALGORITHMS.each do |algorithm, supported|
262
+ algorithms[algorithm] = compose_algorithm_list(
263
+ supported, options[algorithm] || DEFAULT_ALGORITHMS[algorithm],
264
+ options[:append_all_supported_algorithms]
265
+ )
266
+ end
267
+
268
+ # for convention, make sure our list has the same keys as the server
269
+ # list
242
270
 
243
- list = []
244
- option = Array(option).compact.uniq
271
+ algorithms[:encryption_client ] = algorithms[:encryption_server ] = algorithms[:encryption]
272
+ algorithms[:hmac_client ] = algorithms[:hmac_server ] = algorithms[:hmac]
273
+ algorithms[:compression_client] = algorithms[:compression_server] = algorithms[:compression]
274
+ algorithms[:language_client ] = algorithms[:language_server ] = algorithms[:language]
245
275
 
246
- if option.first && option.first.start_with?('+')
247
- list = supported.dup
248
- list << option.first[1..-1]
249
- list.concat(option[1..-1])
250
- list.uniq!
251
- else
252
- list = option
276
+ if !options.key?(:host_key)
277
+ # make sure the host keys are specified in preference order, where any
278
+ # existing known key for the host has preference.
253
279
 
254
- if append_all_supported_algorithms
255
- supported.each { |name| list << name unless list.include?(name) }
280
+ existing_keys = session.host_keys
281
+ host_keys = existing_keys.flat_map { |key| key.respond_to?(:ssh_types) ? key.ssh_types : [key.ssh_type] }.uniq
282
+ algorithms[:host_key].each do |name|
283
+ host_keys << name unless host_keys.include?(name)
284
+ end
285
+ algorithms[:host_key] = host_keys
256
286
  end
257
287
  end
258
288
 
259
- unsupported = []
260
- list.select! do |name|
261
- is_supported = supported.include?(name)
262
- unsupported << name unless is_supported
263
- is_supported
264
- end
289
+ # Composes the list of algorithms by taking supported algorithms and matching with supplied options.
290
+ def compose_algorithm_list(supported, option, append_all_supported_algorithms = false)
291
+ return supported.dup unless option
265
292
 
266
- lwarn { %(unsupported algorithm: `#{unsupported}') } unless unsupported.empty?
293
+ list = []
294
+ option = Array(option).compact.uniq
267
295
 
268
- list
269
- end
296
+ if option.first && option.first.start_with?('+', '-')
297
+ list = supported.dup
270
298
 
271
- # Parses a KEXINIT packet from the server.
272
- def parse_server_algorithm_packet(packet)
273
- data = { raw: packet.content }
274
-
275
- packet.read(16) # skip the cookie value
276
-
277
- data[:kex] = packet.read_string.split(/,/)
278
- data[:host_key] = packet.read_string.split(/,/)
279
- data[:encryption_client] = packet.read_string.split(/,/)
280
- data[:encryption_server] = packet.read_string.split(/,/)
281
- data[:hmac_client] = packet.read_string.split(/,/)
282
- data[:hmac_server] = packet.read_string.split(/,/)
283
- data[:compression_client] = packet.read_string.split(/,/)
284
- data[:compression_server] = packet.read_string.split(/,/)
285
- data[:language_client] = packet.read_string.split(/,/)
286
- data[:language_server] = packet.read_string.split(/,/)
287
-
288
- # TODO: if first_kex_packet_follows, we need to try to skip the
289
- # actual kexinit stuff and try to guess what the server is doing...
290
- # need to read more about this scenario.
291
- # first_kex_packet_follows = packet.read_bool
292
-
293
- return data
294
- end
299
+ appends = option.select { |opt| opt.start_with?('+') }.map { |opt| opt[1..-1] }
300
+ deletions = option.select { |opt| opt.start_with?('-') }.map { |opt| opt[1..-1] }
295
301
 
296
- # Given the #algorithms map of preferred algorithm types, this constructs
297
- # a KEXINIT packet to send to the server. It does not actually send it,
298
- # it simply builds the packet and returns it.
299
- def build_client_algorithm_packet
300
- kex = algorithms[:kex ].join(",")
301
- host_key = algorithms[:host_key ].join(",")
302
- encryption = algorithms[:encryption ].join(",")
303
- hmac = algorithms[:hmac ].join(",")
304
- compression = algorithms[:compression].join(",")
305
- language = algorithms[:language ].join(",")
306
-
307
- Net::SSH::Buffer.from(:byte, KEXINIT,
308
- :long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)],
309
- :mstring, [kex, host_key, encryption, encryption, hmac, hmac],
310
- :mstring, [compression, compression, language, language],
311
- :bool, false, :long, 0)
312
- end
302
+ list.concat(appends)
303
+
304
+ deletions.each do |opt|
305
+ if opt.include?('*')
306
+ opt_escaped = Regexp.escape(opt)
307
+ algo_re = /\A#{opt_escaped.gsub('\*', '[A-Za-z\d\-@\.]*')}\z/
308
+ list.delete_if { |existing_opt| algo_re.match(existing_opt) }
309
+ else
310
+ list.delete(opt)
311
+ end
312
+ end
313
313
 
314
- # Given the parsed server KEX packet, and the client's preferred algorithm
315
- # lists in #algorithms, determine which preferred algorithms each has
316
- # in common and set those as the selected algorithms. If, for any algorithm,
317
- # no type can be settled on, an exception is raised.
318
- def negotiate_algorithms
319
- @kex = negotiate(:kex)
320
- @host_key = negotiate(:host_key)
321
- @encryption_client = negotiate(:encryption_client)
322
- @encryption_server = negotiate(:encryption_server)
323
- @hmac_client = negotiate(:hmac_client)
324
- @hmac_server = negotiate(:hmac_server)
325
- @compression_client = negotiate(:compression_client)
326
- @compression_server = negotiate(:compression_server)
327
- @language_client = negotiate(:language_client) rescue ""
328
- @language_server = negotiate(:language_server) rescue ""
329
-
330
- debug do
331
- "negotiated:\n" +
332
- [:kex, :host_key, :encryption_server, :encryption_client, :hmac_client, :hmac_server, :compression_client, :compression_server, :language_client, :language_server].map do |key|
333
- "* #{key}: #{instance_variable_get("@#{key}")}"
334
- end.join("\n")
314
+ list.uniq!
315
+ else
316
+ list = option
317
+
318
+ if append_all_supported_algorithms
319
+ supported.each { |name| list << name unless list.include?(name) }
320
+ end
321
+ end
322
+
323
+ unsupported = []
324
+ list.select! do |name|
325
+ is_supported = supported.include?(name)
326
+ unsupported << name unless is_supported
327
+ is_supported
328
+ end
329
+
330
+ lwarn { %(unsupported algorithm: `#{unsupported}') } unless unsupported.empty?
331
+
332
+ list
335
333
  end
336
- end
337
334
 
338
- # Negotiates a single algorithm based on the preferences reported by the
339
- # server and those set by the client. This is called by
340
- # #negotiate_algorithms.
341
- def negotiate(algorithm)
342
- match = self[algorithm].find { |item| @server_data[algorithm].include?(item) }
335
+ # Parses a KEXINIT packet from the server.
336
+ def parse_server_algorithm_packet(packet)
337
+ data = { raw: packet.content }
338
+
339
+ packet.read(16) # skip the cookie value
340
+
341
+ data[:kex] = packet.read_string.split(/,/)
342
+ data[:host_key] = packet.read_string.split(/,/)
343
+ data[:encryption_client] = packet.read_string.split(/,/)
344
+ data[:encryption_server] = packet.read_string.split(/,/)
345
+ data[:hmac_client] = packet.read_string.split(/,/)
346
+ data[:hmac_server] = packet.read_string.split(/,/)
347
+ data[:compression_client] = packet.read_string.split(/,/)
348
+ data[:compression_server] = packet.read_string.split(/,/)
349
+ data[:language_client] = packet.read_string.split(/,/)
350
+ data[:language_server] = packet.read_string.split(/,/)
351
+
352
+ # TODO: if first_kex_packet_follows, we need to try to skip the
353
+ # actual kexinit stuff and try to guess what the server is doing...
354
+ # need to read more about this scenario.
355
+ # first_kex_packet_follows = packet.read_bool
356
+
357
+ return data
358
+ end
343
359
 
344
- if match.nil?
345
- raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm"
360
+ # Given the #algorithms map of preferred algorithm types, this constructs
361
+ # a KEXINIT packet to send to the server. It does not actually send it,
362
+ # it simply builds the packet and returns it.
363
+ def build_client_algorithm_packet
364
+ kex = algorithms[:kex].join(",")
365
+ host_key = algorithms[:host_key].join(",")
366
+ encryption = algorithms[:encryption].join(",")
367
+ hmac = algorithms[:hmac].join(",")
368
+ compression = algorithms[:compression].join(",")
369
+ language = algorithms[:language].join(",")
370
+
371
+ Net::SSH::Buffer.from(:byte, KEXINIT,
372
+ :long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)],
373
+ :mstring, [kex, host_key, encryption, encryption, hmac, hmac],
374
+ :mstring, [compression, compression, language, language],
375
+ :bool, false, :long, 0)
346
376
  end
347
377
 
348
- return match
349
- end
378
+ # Given the parsed server KEX packet, and the client's preferred algorithm
379
+ # lists in #algorithms, determine which preferred algorithms each has
380
+ # in common and set those as the selected algorithms. If, for any algorithm,
381
+ # no type can be settled on, an exception is raised.
382
+ def negotiate_algorithms
383
+ @kex = negotiate(:kex)
384
+ @host_key = negotiate(:host_key)
385
+ @encryption_client = negotiate(:encryption_client)
386
+ @encryption_server = negotiate(:encryption_server)
387
+ @hmac_client = negotiate(:hmac_client)
388
+ @hmac_server = negotiate(:hmac_server)
389
+ @compression_client = negotiate(:compression_client)
390
+ @compression_server = negotiate(:compression_server)
391
+ @language_client = negotiate(:language_client) rescue ""
392
+ @language_server = negotiate(:language_server) rescue ""
393
+
394
+ debug do
395
+ "negotiated:\n" +
396
+ %i[kex host_key encryption_server encryption_client hmac_client hmac_server
397
+ compression_client compression_server language_client language_server].map do |key|
398
+ "* #{key}: #{instance_variable_get("@#{key}")}"
399
+ end.join("\n")
400
+ end
401
+ end
350
402
 
351
- # Considers the sizes of the keys and block-sizes for the selected ciphers,
352
- # and the lengths of the hmacs, and returns the largest as the byte requirement
353
- # for the key-exchange algorithm.
354
- def kex_byte_requirement
355
- sizes = [8] # require at least 8 bytes
403
+ # Negotiates a single algorithm based on the preferences reported by the
404
+ # server and those set by the client. This is called by
405
+ # #negotiate_algorithms.
406
+ def negotiate(algorithm)
407
+ match = self[algorithm].find { |item| @server_data[algorithm].include?(item) }
356
408
 
357
- sizes.concat(CipherFactory.get_lengths(encryption_client))
358
- sizes.concat(CipherFactory.get_lengths(encryption_server))
409
+ if match.nil?
410
+ raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm\n"\
411
+ "Server #{algorithm} preferences: #{@server_data[algorithm].join(',')}\n"\
412
+ "Client #{algorithm} preferences: #{self[algorithm].join(',')}"
413
+ end
359
414
 
360
- sizes << HMAC.key_length(hmac_client)
361
- sizes << HMAC.key_length(hmac_server)
415
+ return match
416
+ end
362
417
 
363
- sizes.max
364
- end
418
+ # Considers the sizes of the keys and block-sizes for the selected ciphers,
419
+ # and the lengths of the hmacs, and returns the largest as the byte requirement
420
+ # for the key-exchange algorithm.
421
+ def kex_byte_requirement
422
+ sizes = [8] # require at least 8 bytes
365
423
 
366
- # Instantiates one of the Transport::Kex classes (based on the negotiated
367
- # kex algorithm), and uses it to exchange keys. Then, the ciphers and
368
- # HMACs are initialized and fed to the transport layer, to be used in
369
- # further communication with the server.
370
- def exchange_keys
371
- debug { "exchanging keys" }
372
-
373
- algorithm = Kex::MAP[kex].new(self, session,
374
- client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION,
375
- server_version_string: session.server_version.version,
376
- server_algorithm_packet: @server_packet,
377
- client_algorithm_packet: @client_packet,
378
- need_bytes: kex_byte_requirement,
379
- minimum_dh_bits: options[:minimum_dh_bits],
380
- logger: logger)
381
- result = algorithm.exchange_keys
382
-
383
- secret = result[:shared_secret].to_ssh
384
- hash = result[:session_id]
385
- digester = result[:hashing_algorithm]
386
-
387
- @session_id ||= hash
388
-
389
- key = Proc.new { |salt| digester.digest(secret + hash + salt + @session_id) }
390
-
391
- iv_client = key["A"]
392
- iv_server = key["B"]
393
- key_client = key["C"]
394
- key_server = key["D"]
395
- mac_key_client = key["E"]
396
- mac_key_server = key["F"]
397
-
398
- parameters = { shared: secret, hash: hash, digester: digester }
399
-
400
- cipher_client = CipherFactory.get(encryption_client, parameters.merge(iv: iv_client, key: key_client, encrypt: true))
401
- cipher_server = CipherFactory.get(encryption_server, parameters.merge(iv: iv_server, key: key_server, decrypt: true))
402
-
403
- mac_client = HMAC.get(hmac_client, mac_key_client, parameters)
404
- mac_server = HMAC.get(hmac_server, mac_key_server, parameters)
405
-
406
- session.configure_client cipher: cipher_client, hmac: mac_client,
407
- compression: normalize_compression_name(compression_client),
408
- compression_level: options[:compression_level],
409
- rekey_limit: options[:rekey_limit],
410
- max_packets: options[:rekey_packet_limit],
411
- max_blocks: options[:rekey_blocks_limit]
412
-
413
- session.configure_server cipher: cipher_server, hmac: mac_server,
414
- compression: normalize_compression_name(compression_server),
415
- rekey_limit: options[:rekey_limit],
416
- max_packets: options[:rekey_packet_limit],
417
- max_blocks: options[:rekey_blocks_limit]
418
-
419
- @initialized = true
420
- end
424
+ sizes.concat(CipherFactory.get_lengths(encryption_client))
425
+ sizes.concat(CipherFactory.get_lengths(encryption_server))
421
426
 
422
- # Given the SSH name for some compression algorithm, return a normalized
423
- # name as a symbol.
424
- def normalize_compression_name(name)
425
- case name
426
- when "none" then false
427
- when "zlib" then :standard
428
- when "zlib@openssh.com" then :delayed
429
- else raise ArgumentError, "unknown compression type `#{name}'"
427
+ sizes << HMAC.key_length(hmac_client)
428
+ sizes << HMAC.key_length(hmac_server)
429
+
430
+ sizes.max
431
+ end
432
+
433
+ # Instantiates one of the Transport::Kex classes (based on the negotiated
434
+ # kex algorithm), and uses it to exchange keys. Then, the ciphers and
435
+ # HMACs are initialized and fed to the transport layer, to be used in
436
+ # further communication with the server.
437
+ def exchange_keys
438
+ debug { "exchanging keys" }
439
+
440
+ algorithm = Kex::MAP[kex].new(self, session,
441
+ client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION,
442
+ server_version_string: session.server_version.version,
443
+ server_algorithm_packet: @server_packet,
444
+ client_algorithm_packet: @client_packet,
445
+ need_bytes: kex_byte_requirement,
446
+ minimum_dh_bits: options[:minimum_dh_bits],
447
+ logger: logger)
448
+ result = algorithm.exchange_keys
449
+
450
+ secret = result[:shared_secret].to_ssh
451
+ hash = result[:session_id]
452
+ digester = result[:hashing_algorithm]
453
+
454
+ @session_id ||= hash
455
+
456
+ key = Proc.new { |salt| digester.digest(secret + hash + salt + @session_id) }
457
+
458
+ iv_client = key["A"]
459
+ iv_server = key["B"]
460
+ key_client = key["C"]
461
+ key_server = key["D"]
462
+ mac_key_client = key["E"]
463
+ mac_key_server = key["F"]
464
+
465
+ parameters = { shared: secret, hash: hash, digester: digester }
466
+
467
+ cipher_client = CipherFactory.get(encryption_client, parameters.merge(iv: iv_client, key: key_client, encrypt: true))
468
+ cipher_server = CipherFactory.get(encryption_server, parameters.merge(iv: iv_server, key: key_server, decrypt: true))
469
+
470
+ mac_client = HMAC.get(hmac_client, mac_key_client, parameters)
471
+ mac_server = HMAC.get(hmac_server, mac_key_server, parameters)
472
+
473
+ session.configure_client cipher: cipher_client, hmac: mac_client,
474
+ compression: normalize_compression_name(compression_client),
475
+ compression_level: options[:compression_level],
476
+ rekey_limit: options[:rekey_limit],
477
+ max_packets: options[:rekey_packet_limit],
478
+ max_blocks: options[:rekey_blocks_limit]
479
+
480
+ session.configure_server cipher: cipher_server, hmac: mac_server,
481
+ compression: normalize_compression_name(compression_server),
482
+ rekey_limit: options[:rekey_limit],
483
+ max_packets: options[:rekey_packet_limit],
484
+ max_blocks: options[:rekey_blocks_limit]
485
+
486
+ @initialized = true
487
+ end
488
+
489
+ # Given the SSH name for some compression algorithm, return a normalized
490
+ # name as a symbol.
491
+ def normalize_compression_name(name)
492
+ case name
493
+ when "none" then false
494
+ when "zlib" then :standard
495
+ when "zlib@openssh.com" then :delayed
496
+ else raise ArgumentError, "unknown compression type `#{name}'"
497
+ end
430
498
  end
431
499
  end
500
+ end
432
501
  end
433
- end; end; end
502
+ end