net-ssh 5.0.2 → 6.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/.gitignore +1 -0
  5. data/.rubocop.yml +8 -2
  6. data/.rubocop_todo.yml +392 -379
  7. data/.travis.yml +22 -22
  8. data/CHANGES.txt +63 -0
  9. data/Manifest +0 -1
  10. data/README.md +287 -0
  11. data/Rakefile +1 -2
  12. data/appveyor.yml +4 -2
  13. data/lib/net/ssh.rb +13 -4
  14. data/lib/net/ssh/authentication/agent.rb +9 -3
  15. data/lib/net/ssh/authentication/certificate.rb +10 -1
  16. data/lib/net/ssh/authentication/ed25519.rb +75 -46
  17. data/lib/net/ssh/authentication/ed25519_loader.rb +1 -1
  18. data/lib/net/ssh/authentication/key_manager.rb +35 -6
  19. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +3 -1
  20. data/lib/net/ssh/authentication/methods/publickey.rb +2 -0
  21. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +0 -1
  22. data/lib/net/ssh/authentication/session.rb +9 -6
  23. data/lib/net/ssh/buffer.rb +41 -10
  24. data/lib/net/ssh/buffered_io.rb +0 -1
  25. data/lib/net/ssh/config.rb +68 -35
  26. data/lib/net/ssh/connection/channel.rb +17 -5
  27. data/lib/net/ssh/connection/event_loop.rb +0 -1
  28. data/lib/net/ssh/connection/session.rb +7 -4
  29. data/lib/net/ssh/key_factory.rb +104 -17
  30. data/lib/net/ssh/known_hosts.rb +41 -26
  31. data/lib/net/ssh/loggable.rb +2 -2
  32. data/lib/net/ssh/proxy/command.rb +0 -1
  33. data/lib/net/ssh/proxy/socks5.rb +0 -1
  34. data/lib/net/ssh/service/forward.rb +2 -1
  35. data/lib/net/ssh/test.rb +8 -7
  36. data/lib/net/ssh/test/extensions.rb +2 -0
  37. data/lib/net/ssh/transport/algorithms.rb +161 -105
  38. data/lib/net/ssh/transport/cipher_factory.rb +11 -27
  39. data/lib/net/ssh/transport/constants.rb +10 -6
  40. data/lib/net/ssh/transport/ctr.rb +1 -7
  41. data/lib/net/ssh/transport/hmac.rb +15 -13
  42. data/lib/net/ssh/transport/hmac/abstract.rb +16 -0
  43. data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
  44. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
  45. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  46. data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
  47. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
  48. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  49. data/lib/net/ssh/transport/kex.rb +14 -11
  50. data/lib/net/ssh/transport/kex/abstract.rb +123 -0
  51. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  52. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +38 -0
  53. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  54. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +1 -15
  55. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +9 -118
  56. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +0 -6
  57. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
  58. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +18 -79
  59. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +5 -4
  60. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +5 -4
  61. data/lib/net/ssh/transport/openssl.rb +106 -93
  62. data/lib/net/ssh/transport/packet_stream.rb +52 -20
  63. data/lib/net/ssh/transport/session.rb +26 -4
  64. data/lib/net/ssh/transport/state.rb +1 -1
  65. data/lib/net/ssh/verifiers/accept_new.rb +7 -0
  66. data/lib/net/ssh/verifiers/always.rb +4 -0
  67. data/lib/net/ssh/verifiers/never.rb +4 -0
  68. data/lib/net/ssh/version.rb +3 -3
  69. data/net-ssh-public_cert.pem +18 -19
  70. data/net-ssh.gemspec +9 -7
  71. metadata +56 -40
  72. metadata.gz.sig +0 -0
  73. data/Gemfile.noed25519.lock +0 -41
  74. data/README.rdoc +0 -169
  75. data/lib/net/ssh/ruby_compat.rb +0 -13
  76. data/support/arcfour_check.rb +0 -20
@@ -2,6 +2,7 @@ require 'strscan'
2
2
  require 'openssl'
3
3
  require 'base64'
4
4
  require 'net/ssh/buffer'
5
+ require 'net/ssh/authentication/ed25519_loader'
5
6
 
6
7
  module Net
7
8
  module SSH
@@ -40,26 +41,24 @@ module Net
40
41
  # This is used internally by Net::SSH, and will never need to be used directly
41
42
  # by consumers of the library.
42
43
  class KnownHosts
43
- if defined?(OpenSSL::PKey::EC)
44
- SUPPORTED_TYPE = %w[ssh-rsa ssh-dss
45
- ecdsa-sha2-nistp256
46
- ecdsa-sha2-nistp384
47
- ecdsa-sha2-nistp521]
48
- else
49
- SUPPORTED_TYPE = %w[ssh-rsa ssh-dss]
50
- end
44
+ SUPPORTED_TYPE = %w[ssh-rsa ssh-dss
45
+ ecdsa-sha2-nistp256
46
+ ecdsa-sha2-nistp384
47
+ ecdsa-sha2-nistp521]
48
+
49
+ SUPPORTED_TYPE.push('ssh-ed25519') if Net::SSH::Authentication::ED25519Loader::LOADED
51
50
 
52
51
  class <<self
53
52
  # Searches all known host files (see KnownHosts.hostfiles) for all keys
54
53
  # of the given host. Returns an enumerable of keys found.
55
54
  def search_for(host, options={})
56
- HostKeys.new(search_in(hostfiles(options), host), host, self, options)
55
+ HostKeys.new(search_in(hostfiles(options), host, options), host, self, options)
57
56
  end
58
57
 
59
58
  # Search for all known keys for the given host, in every file given in
60
59
  # the +files+ array. Returns the list of keys.
61
- def search_in(files, host)
62
- files.flat_map { |file| KnownHosts.new(file).keys_for(host) }
60
+ def search_in(files, host, options = {})
61
+ files.flat_map { |file| KnownHosts.new(file).keys_for(host, options) }
63
62
  end
64
63
 
65
64
  # Looks in the given +options+ hash for the :user_known_hosts_file and
@@ -76,7 +75,9 @@ module Net
76
75
 
77
76
  files += Array(options[:user_known_hosts_file] || %w[~/.ssh/known_hosts ~/.ssh/known_hosts2]) if which == :all || which == :user
78
77
 
79
- files += Array(options[:global_known_hosts_file] || %w[/etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2]) if which == :all || which == :global
78
+ if which == :all || which == :global
79
+ files += Array(options[:global_known_hosts_file] || %w[/etc/ssh/ssh_known_hosts /etc/ssh/ssh_known_hosts2])
80
+ end
80
81
 
81
82
  return files
82
83
  end
@@ -119,32 +120,31 @@ module Net
119
120
  # "[net.ssh.test]:5555"
120
121
  # "[1,2,3,4]:5555"
121
122
  # "[net.ssh.test]:5555,[1.2.3.4]:5555
122
- def keys_for(host)
123
+ def keys_for(host, options = {})
123
124
  keys = []
124
125
  return keys unless File.readable?(source)
125
126
 
126
127
  entries = host.split(/,/)
128
+ host_name = entries[0]
129
+ host_ip = entries[1]
127
130
 
128
131
  File.open(source) do |file|
129
- scanner = StringScanner.new("")
130
132
  file.each_line do |line|
131
- scanner.string = line
133
+ hosts, type, key_content = line.split(' ')
134
+ # Skip empty line or one that is commented
135
+ next if hosts.nil? || hosts.start_with?('#')
132
136
 
133
- scanner.skip(/\s*/)
134
- next if scanner.match?(/$|#/)
137
+ hostlist = hosts.split(',')
135
138
 
136
- hostlist = scanner.scan(/\S+/).split(/,/)
137
- found = entries.all? { |entry| hostlist.include?(entry) } ||
138
- known_host_hash?(hostlist, entries)
139
- next unless found
139
+ next unless SUPPORTED_TYPE.include?(type)
140
140
 
141
- scanner.skip(/\s*/)
142
- type = scanner.scan(/\S+/)
141
+ found = hostlist.any? { |pattern| match(host_name, pattern) } || known_host_hash?(hostlist, entries)
142
+ next unless found
143
143
 
144
- next unless SUPPORTED_TYPE.include?(type)
144
+ found = hostlist.include?(host_ip) if options[:check_host_ip] && entries.size > 1 && hostlist.size > 1
145
+ next unless found
145
146
 
146
- scanner.skip(/\s*/)
147
- blob = scanner.rest.unpack("m*").first
147
+ blob = key_content.unpack("m*").first
148
148
  keys << Net::SSH::Buffer.new(blob).read_key
149
149
  end
150
150
  end
@@ -152,6 +152,21 @@ module Net
152
152
  keys
153
153
  end
154
154
 
155
+ def match(host, pattern)
156
+ if pattern.include?('*') || pattern.include?('?')
157
+ # see man 8 sshd for pattern details
158
+ pattern_regexp = pattern.split('*').map do |x|
159
+ x.split('?').map do |y|
160
+ Regexp.escape(y)
161
+ end.join('.')
162
+ end.join('[^.]*')
163
+
164
+ host =~ Regexp.new("\\A#{pattern_regexp}\\z")
165
+ else
166
+ host == pattern
167
+ end
168
+ end
169
+
155
170
  # Indicates whether one of the entries matches an hostname that has been
156
171
  # stored as a HMAC-SHA1 hash in the known hosts.
157
172
  def known_host_hash?(hostlist, entries)
@@ -56,8 +56,8 @@ module Net
56
56
  # originates. It defaults to the name of class with the object_id
57
57
  # appended.
58
58
  def facility
59
- @facility ||= self.class.name.gsub(/::/, ".").gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase + "[%x]" % object_id
59
+ @facility ||= self.class.to_s.gsub(/::/, ".").gsub(/([a-z])([A-Z])/, "\\1_\\2").downcase + "[%x]" % object_id
60
60
  end
61
61
  end
62
62
  end
63
- end
63
+ end
@@ -1,7 +1,6 @@
1
1
  require 'socket'
2
2
  require 'rubygems'
3
3
  require 'net/ssh/proxy/errors'
4
- require 'net/ssh/ruby_compat'
5
4
 
6
5
  module Net
7
6
  module SSH
@@ -1,5 +1,4 @@
1
1
  require 'socket'
2
- require 'net/ssh/ruby_compat'
3
2
  require 'net/ssh/proxy/errors'
4
3
 
5
4
  module Net
@@ -85,7 +85,8 @@ module Net
85
85
  client = server.accept
86
86
  debug { "received connection on #{socket}" }
87
87
 
88
- channel = session.open_channel("direct-tcpip", :string, remote_host, :long, remote_port, :string, bind_address, local_port_type, local_port) do |achannel|
88
+ channel = session.open_channel("direct-tcpip", :string, remote_host, :long,
89
+ remote_port, :string, bind_address, local_port_type, local_port) do |achannel|
89
90
  achannel.info { "direct channel established" }
90
91
  end
91
92
 
@@ -3,7 +3,7 @@ require 'net/ssh/connection/session'
3
3
  require 'net/ssh/test/kex'
4
4
  require 'net/ssh/test/socket'
5
5
 
6
- module Net
6
+ module Net
7
7
  module SSH
8
8
 
9
9
  # This module may be used in unit tests, for when you want to test that your
@@ -54,30 +54,30 @@ module Net
54
54
  Net::SSH::Test::Extensions::IO.with_test_extension { yield socket.script if block_given? }
55
55
  return socket.script
56
56
  end
57
-
57
+
58
58
  # Returns the test socket instance to use for these tests (see
59
59
  # Net::SSH::Test::Socket).
60
60
  def socket(options={})
61
61
  @socket ||= Net::SSH::Test::Socket.new
62
62
  end
63
-
63
+
64
64
  # Returns the connection session (Net::SSH::Connection::Session) for use
65
65
  # in these tests. It is a fully functional SSH session, operating over
66
66
  # a mock socket (#socket).
67
67
  def connection(options={})
68
68
  @connection ||= Net::SSH::Connection::Session.new(transport(options), options)
69
69
  end
70
-
70
+
71
71
  # Returns the transport session (Net::SSH::Transport::Session) for use
72
72
  # in these tests. It is a fully functional SSH transport session, operating
73
73
  # over a mock socket (#socket).
74
74
  def transport(options={})
75
75
  @transport ||= Net::SSH::Transport::Session.new(
76
76
  options[:host] || "localhost",
77
- options.merge(kex: "test", host_key: "ssh-rsa", verify_host_key: false, proxy: socket(options))
77
+ options.merge(kex: "test", host_key: "ssh-rsa", append_all_supported_algorithms: true, verify_host_key: :never, proxy: socket(options))
78
78
  )
79
79
  end
80
-
80
+
81
81
  # First asserts that a story has been described (see #story). Then yields,
82
82
  # and then asserts that all items described in the script have been
83
83
  # processed. Typically, this is called immediately after a story has
@@ -86,7 +86,8 @@ module Net
86
86
  def assert_scripted
87
87
  raise "there is no script to be processed" if socket.script.events.empty?
88
88
  Net::SSH::Test::Extensions::IO.with_test_extension { yield }
89
- assert socket.script.events.empty?, "there should not be any remaining scripted events, but there are still #{socket.script.events.length} pending"
89
+ assert socket.script.events.empty?, "there should not be any remaining scripted events, but there are still" \
90
+ "#{socket.script.events.length} pending"
90
91
  end
91
92
  end
92
93
 
@@ -156,6 +156,8 @@ module Net
156
156
  end
157
157
 
158
158
  raise "no readers were ready for reading, and none had any incoming packets" if processed == 0 && wait != 0
159
+
160
+ [[], [], []]
159
161
  end
160
162
  end
161
163
  end
@@ -5,13 +5,13 @@ 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
12
- module SSH
12
+ module Net
13
+ module SSH
13
14
  module Transport
14
-
15
15
  # Implements the higher-level logic behind an SSH key-exchange. It handles
16
16
  # both the initial exchange, as well as subsequent re-exchanges (as needed).
17
17
  # It also encapsulates the negotiation of the algorithms, and provides a
@@ -22,92 +22,125 @@ module Net
22
22
  class Algorithms
23
23
  include Loggable
24
24
  include Constants
25
-
26
- # Define the default algorithms, in order of preference, supported by
27
- # Net::SSH.
28
- ALGORITHMS = {
29
- host_key: %w[ssh-rsa ssh-dss
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
30
34
  ssh-rsa-cert-v01@openssh.com
31
- ssh-rsa-cert-v00@openssh.com],
32
- kex: %w[diffie-hellman-group-exchange-sha1
33
- diffie-hellman-group1-sha1
34
- diffie-hellman-group14-sha1
35
- diffie-hellman-group-exchange-sha256],
36
- encryption: %w[aes128-cbc 3des-cbc blowfish-cbc cast128-cbc
37
- aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se
38
- idea-cbc arcfour128 arcfour256 arcfour
39
- aes128-ctr aes192-ctr aes256-ctr
40
- cast128-ctr blowfish-ctr 3des-ctr none],
41
-
42
- hmac: %w[hmac-sha1 hmac-md5 hmac-sha1-96 hmac-md5-96
35
+ ssh-rsa-cert-v00@openssh.com
36
+ ssh-rsa],
37
+
38
+ kex: %w[ecdh-sha2-nistp521
39
+ ecdh-sha2-nistp384
40
+ ecdh-sha2-nistp256
41
+ diffie-hellman-group-exchange-sha256
42
+ diffie-hellman-group14-sha1],
43
+
44
+ encryption: %w[aes256-ctr aes192-ctr aes128-ctr],
45
+
46
+ hmac: %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com
47
+ hmac-sha2-512 hmac-sha2-256
48
+ hmac-sha1]
49
+ }.freeze
50
+
51
+ if Net::SSH::Authentication::ED25519Loader::LOADED
52
+ DEFAULT_ALGORITHMS[:host_key].unshift(
53
+ 'ssh-ed25519-cert-v01@openssh.com',
54
+ 'ssh-ed25519'
55
+ )
56
+ end
57
+
58
+ if Net::SSH::Transport::Kex::Curve25519Sha256Loader::LOADED
59
+ DEFAULT_ALGORITHMS[:kex].unshift(
60
+ 'curve25519-sha256',
61
+ 'curve25519-sha256@libssh.org'
62
+ )
63
+ end
64
+
65
+ # Define all algorithms, with the deprecated, supported by Net::SSH.
66
+ ALGORITHMS = {
67
+ host_key: DEFAULT_ALGORITHMS[:host_key] + %w[ssh-dss],
68
+
69
+ kex: DEFAULT_ALGORITHMS[:kex] +
70
+ %w[diffie-hellman-group-exchange-sha1
71
+ diffie-hellman-group1-sha1],
72
+
73
+ encryption: DEFAULT_ALGORITHMS[:encryption] +
74
+ %w[aes256-cbc aes192-cbc aes128-cbc
75
+ rijndael-cbc@lysator.liu.se
76
+ blowfish-ctr blowfish-cbc
77
+ cast128-ctr cast128-cbc
78
+ 3des-ctr 3des-cbc
79
+ idea-cbc
80
+ none],
81
+
82
+ hmac: DEFAULT_ALGORITHMS[:hmac] +
83
+ %w[hmac-sha2-512-96 hmac-sha2-256-96
84
+ hmac-sha1-96
43
85
  hmac-ripemd160 hmac-ripemd160@openssh.com
44
- hmac-sha2-256 hmac-sha2-512 hmac-sha2-256-96
45
- hmac-sha2-512-96 none],
46
-
86
+ hmac-md5 hmac-md5-96
87
+ none],
88
+
47
89
  compression: %w[none zlib@openssh.com zlib],
48
90
  language: %w[]
49
- }
50
- if defined?(OpenSSL::PKey::EC)
51
- ALGORITHMS[:host_key] += %w[ecdsa-sha2-nistp256
52
- ecdsa-sha2-nistp384
53
- ecdsa-sha2-nistp521]
54
- ALGORITHMS[:host_key] += %w[ssh-ed25519] if Net::SSH::Authentication::ED25519Loader::LOADED
55
- ALGORITHMS[:kex] += %w[ecdh-sha2-nistp256
56
- ecdh-sha2-nistp384
57
- ecdh-sha2-nistp521]
58
- end
59
-
91
+ }.freeze
92
+
60
93
  # The underlying transport layer session that supports this object
61
94
  attr_reader :session
62
-
95
+
63
96
  # The hash of options used to initialize this object
64
97
  attr_reader :options
65
-
98
+
66
99
  # The kex algorithm to use settled on between the client and server.
67
100
  attr_reader :kex
68
-
101
+
69
102
  # The type of host key that will be used for this session.
70
103
  attr_reader :host_key
71
-
104
+
72
105
  # The type of the cipher to use to encrypt packets sent from the client to
73
106
  # the server.
74
107
  attr_reader :encryption_client
75
-
108
+
76
109
  # The type of the cipher to use to decrypt packets arriving from the server.
77
110
  attr_reader :encryption_server
78
-
111
+
79
112
  # The type of HMAC to use to sign packets sent by the client.
80
113
  attr_reader :hmac_client
81
-
114
+
82
115
  # The type of HMAC to use to validate packets arriving from the server.
83
116
  attr_reader :hmac_server
84
-
117
+
85
118
  # The type of compression to use to compress packets being sent by the client.
86
119
  attr_reader :compression_client
87
-
120
+
88
121
  # The type of compression to use to decompress packets arriving from the server.
89
122
  attr_reader :compression_server
90
-
123
+
91
124
  # The language that will be used in messages sent by the client.
92
125
  attr_reader :language_client
93
-
126
+
94
127
  # The language that will be used in messages sent from the server.
95
128
  attr_reader :language_server
96
-
129
+
97
130
  # The hash of algorithms preferred by the client, which will be told to
98
131
  # the server during algorithm negotiation.
99
132
  attr_reader :algorithms
100
-
133
+
101
134
  # The session-id for this session, as decided during the initial key exchange.
102
135
  attr_reader :session_id
103
-
136
+
104
137
  # Returns true if the given packet can be processed during a key-exchange.
105
138
  def self.allowed_packet?(packet)
106
139
  (1..4).include?(packet.type) ||
107
140
  (6..19).include?(packet.type) ||
108
141
  (21..49).include?(packet.type)
109
142
  end
110
-
143
+
111
144
  # Instantiates a new Algorithms object, and prepares the hash of preferred
112
145
  # algorithms based on the options parameter and the ALGORITHMS constant.
113
146
  def initialize(session, options={})
@@ -119,13 +152,14 @@ module Net
119
152
  @client_packet = @server_packet = nil
120
153
  prepare_preferred_algorithms!
121
154
  end
122
-
155
+
123
156
  # Start the algorithm negotation
124
157
  def start
125
158
  raise ArgumentError, "Cannot call start if it's negotiation started or done" if @pending || @initialized
159
+
126
160
  send_kexinit
127
161
  end
128
-
162
+
129
163
  # Request a rekey operation. This will return immediately, and does not
130
164
  # actually perform the rekey operation. It does cause the session to change
131
165
  # state, however--until the key exchange finishes, no new packets will be
@@ -135,7 +169,7 @@ module Net
135
169
  @initialized = false
136
170
  send_kexinit
137
171
  end
138
-
172
+
139
173
  # Called by the transport layer when a KEXINIT packet is received, indicating
140
174
  # that the server wants to exchange keys. This can be spontaneous, or it
141
175
  # can be in response to a client-initiated rekey request (see #rekey!). Either
@@ -150,13 +184,13 @@ module Net
150
184
  proceed!
151
185
  end
152
186
  end
153
-
187
+
154
188
  # A convenience method for accessing the list of preferred types for a
155
189
  # specific algorithm (see #algorithms).
156
190
  def [](key)
157
191
  algorithms[key]
158
192
  end
159
-
193
+
160
194
  # Returns +true+ if a key-exchange is pending. This will be true from the
161
195
  # moment either the client or server requests the key exchange, until the
162
196
  # exchange completes. While an exchange is pending, only a limited number
@@ -180,8 +214,8 @@ module Net
180
214
 
181
215
  def host_key_format
182
216
  case host_key
183
- when "ssh-rsa-cert-v01@openssh.com", "ssh-rsa-cert-v00@openssh.com"
184
- "ssh-rsa"
217
+ when /^([a-z0-9-]+)-cert-v\d{2}@openssh.com$/
218
+ Regexp.last_match[1]
185
219
  else
186
220
  host_key
187
221
  end
@@ -201,7 +235,7 @@ module Net
201
235
  session.send_message(packet)
202
236
  proceed! if @server_packet
203
237
  end
204
-
238
+
205
239
  # After both client and server have sent their KEXINIT packets, this
206
240
  # will do the algorithm negotiation and key exchange. Once both finish,
207
241
  # the object leaves the pending state and the method returns.
@@ -211,7 +245,7 @@ module Net
211
245
  exchange_keys
212
246
  @pending = false
213
247
  end
214
-
248
+
215
249
  # Prepares the list of preferred algorithms, based on the options hash
216
250
  # that was given when the object was constructed, and the ALGORITHMS
217
251
  # constant. Also, when determining the host_key type to use, the known
@@ -220,23 +254,26 @@ module Net
220
254
  # communicating with this server.
221
255
  def prepare_preferred_algorithms!
222
256
  options[:compression] = %w[zlib@openssh.com zlib] if options[:compression] == true
223
-
257
+
224
258
  ALGORITHMS.each do |algorithm, supported|
225
- algorithms[algorithm] = compose_algorithm_list(supported, options[algorithm], options[:append_all_supported_algorithms])
259
+ algorithms[algorithm] = compose_algorithm_list(
260
+ supported, options[algorithm] || DEFAULT_ALGORITHMS[algorithm],
261
+ options[:append_all_supported_algorithms]
262
+ )
226
263
  end
227
-
264
+
228
265
  # for convention, make sure our list has the same keys as the server
229
266
  # list
230
-
267
+
231
268
  algorithms[:encryption_client ] = algorithms[:encryption_server ] = algorithms[:encryption]
232
269
  algorithms[:hmac_client ] = algorithms[:hmac_server ] = algorithms[:hmac]
233
270
  algorithms[:compression_client] = algorithms[:compression_server] = algorithms[:compression]
234
271
  algorithms[:language_client ] = algorithms[:language_server ] = algorithms[:language]
235
-
272
+
236
273
  if !options.key?(:host_key)
237
274
  # make sure the host keys are specified in preference order, where any
238
275
  # existing known key for the host has preference.
239
-
276
+
240
277
  existing_keys = session.host_keys
241
278
  host_keys = existing_keys.map { |key| key.ssh_type }.uniq
242
279
  algorithms[:host_key].each do |name|
@@ -245,45 +282,59 @@ module Net
245
282
  algorithms[:host_key] = host_keys
246
283
  end
247
284
  end
248
-
285
+
249
286
  # Composes the list of algorithms by taking supported algorithms and matching with supplied options.
250
287
  def compose_algorithm_list(supported, option, append_all_supported_algorithms = false)
251
288
  return supported.dup unless option
252
-
289
+
253
290
  list = []
254
291
  option = Array(option).compact.uniq
255
-
256
- if option.first && option.first.start_with?('+')
292
+
293
+ if option.first && option.first.start_with?('+', '-')
257
294
  list = supported.dup
258
- list << option.first[1..-1]
259
- list.concat(option[1..-1])
295
+
296
+ appends = option.select { |opt| opt.start_with?('+') }.map { |opt| opt[1..-1] }
297
+ deletions = option.select { |opt| opt.start_with?('-') }.map { |opt| opt[1..-1] }
298
+
299
+ list.concat(appends)
300
+
301
+ deletions.each do |opt|
302
+ if opt.include?('*')
303
+ opt_escaped = Regexp.escape(opt)
304
+ algo_re = /\A#{opt_escaped.gsub('\*', '[A-Za-z\d\-@\.]*')}\z/
305
+ list.delete_if { |existing_opt| algo_re.match(existing_opt) }
306
+ else
307
+ list.delete(opt)
308
+ end
309
+ end
310
+
260
311
  list.uniq!
261
312
  else
262
313
  list = option
263
-
314
+
264
315
  if append_all_supported_algorithms
265
316
  supported.each { |name| list << name unless list.include?(name) }
266
317
  end
267
318
  end
268
-
319
+
269
320
  unsupported = []
270
321
  list.select! do |name|
271
322
  is_supported = supported.include?(name)
272
323
  unsupported << name unless is_supported
273
324
  is_supported
274
325
  end
275
-
326
+
276
327
  lwarn { %(unsupported algorithm: `#{unsupported}') } unless unsupported.empty?
277
-
328
+
278
329
  list
279
330
  end
280
-
331
+
281
332
  # Parses a KEXINIT packet from the server.
282
333
  def parse_server_algorithm_packet(packet)
283
334
  data = { raw: packet.content }
284
-
335
+
285
336
  packet.read(16) # skip the cookie value
286
-
337
+
287
338
  data[:kex] = packet.read_string.split(/,/)
288
339
  data[:host_key] = packet.read_string.split(/,/)
289
340
  data[:encryption_client] = packet.read_string.split(/,/)
@@ -294,15 +345,15 @@ module Net
294
345
  data[:compression_server] = packet.read_string.split(/,/)
295
346
  data[:language_client] = packet.read_string.split(/,/)
296
347
  data[:language_server] = packet.read_string.split(/,/)
297
-
348
+
298
349
  # TODO: if first_kex_packet_follows, we need to try to skip the
299
350
  # actual kexinit stuff and try to guess what the server is doing...
300
351
  # need to read more about this scenario.
301
352
  # first_kex_packet_follows = packet.read_bool
302
-
353
+
303
354
  return data
304
355
  end
305
-
356
+
306
357
  # Given the #algorithms map of preferred algorithm types, this constructs
307
358
  # a KEXINIT packet to send to the server. It does not actually send it,
308
359
  # it simply builds the packet and returns it.
@@ -313,14 +364,14 @@ module Net
313
364
  hmac = algorithms[:hmac].join(",")
314
365
  compression = algorithms[:compression].join(",")
315
366
  language = algorithms[:language].join(",")
316
-
367
+
317
368
  Net::SSH::Buffer.from(:byte, KEXINIT,
318
369
  :long, [rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF), rand(0xFFFFFFFF)],
319
370
  :mstring, [kex, host_key, encryption, encryption, hmac, hmac],
320
371
  :mstring, [compression, compression, language, language],
321
372
  :bool, false, :long, 0)
322
373
  end
323
-
374
+
324
375
  # Given the parsed server KEX packet, and the client's preferred algorithm
325
376
  # lists in #algorithms, determine which preferred algorithms each has
326
377
  # in common and set those as the selected algorithms. If, for any algorithm,
@@ -336,48 +387,53 @@ module Net
336
387
  @compression_server = negotiate(:compression_server)
337
388
  @language_client = negotiate(:language_client) rescue ""
338
389
  @language_server = negotiate(:language_server) rescue ""
339
-
390
+
340
391
  debug do
341
392
  "negotiated:\n" +
342
- %i[kex host_key encryption_server encryption_client hmac_client hmac_server compression_client compression_server language_client language_server].map do |key|
393
+ %i[kex host_key encryption_server encryption_client hmac_client hmac_server
394
+ compression_client compression_server language_client language_server].map do |key|
343
395
  "* #{key}: #{instance_variable_get("@#{key}")}"
344
396
  end.join("\n")
345
397
  end
346
398
  end
347
-
399
+
348
400
  # Negotiates a single algorithm based on the preferences reported by the
349
401
  # server and those set by the client. This is called by
350
402
  # #negotiate_algorithms.
351
403
  def negotiate(algorithm)
352
404
  match = self[algorithm].find { |item| @server_data[algorithm].include?(item) }
353
-
354
- raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm" if match.nil?
355
-
405
+
406
+ if match.nil?
407
+ raise Net::SSH::Exception, "could not settle on #{algorithm} algorithm\n"\
408
+ "Server #{algorithm} preferences: #{@server_data[algorithm].join(',')}\n"\
409
+ "Client #{algorithm} preferences: #{self[algorithm].join(',')}"
410
+ end
411
+
356
412
  return match
357
413
  end
358
-
414
+
359
415
  # Considers the sizes of the keys and block-sizes for the selected ciphers,
360
416
  # and the lengths of the hmacs, and returns the largest as the byte requirement
361
417
  # for the key-exchange algorithm.
362
418
  def kex_byte_requirement
363
419
  sizes = [8] # require at least 8 bytes
364
-
420
+
365
421
  sizes.concat(CipherFactory.get_lengths(encryption_client))
366
422
  sizes.concat(CipherFactory.get_lengths(encryption_server))
367
-
423
+
368
424
  sizes << HMAC.key_length(hmac_client)
369
425
  sizes << HMAC.key_length(hmac_server)
370
-
426
+
371
427
  sizes.max
372
428
  end
373
-
429
+
374
430
  # Instantiates one of the Transport::Kex classes (based on the negotiated
375
431
  # kex algorithm), and uses it to exchange keys. Then, the ciphers and
376
432
  # HMACs are initialized and fed to the transport layer, to be used in
377
433
  # further communication with the server.
378
434
  def exchange_keys
379
435
  debug { "exchanging keys" }
380
-
436
+
381
437
  algorithm = Kex::MAP[kex].new(self, session,
382
438
  client_version_string: Net::SSH::Transport::ServerVersion::PROTO_VERSION,
383
439
  server_version_string: session.server_version.version,
@@ -387,46 +443,46 @@ module Net
387
443
  minimum_dh_bits: options[:minimum_dh_bits],
388
444
  logger: logger)
389
445
  result = algorithm.exchange_keys
390
-
446
+
391
447
  secret = result[:shared_secret].to_ssh
392
448
  hash = result[:session_id]
393
449
  digester = result[:hashing_algorithm]
394
-
450
+
395
451
  @session_id ||= hash
396
-
452
+
397
453
  key = Proc.new { |salt| digester.digest(secret + hash + salt + @session_id) }
398
-
454
+
399
455
  iv_client = key["A"]
400
456
  iv_server = key["B"]
401
457
  key_client = key["C"]
402
458
  key_server = key["D"]
403
459
  mac_key_client = key["E"]
404
460
  mac_key_server = key["F"]
405
-
461
+
406
462
  parameters = { shared: secret, hash: hash, digester: digester }
407
-
463
+
408
464
  cipher_client = CipherFactory.get(encryption_client, parameters.merge(iv: iv_client, key: key_client, encrypt: true))
409
465
  cipher_server = CipherFactory.get(encryption_server, parameters.merge(iv: iv_server, key: key_server, decrypt: true))
410
-
466
+
411
467
  mac_client = HMAC.get(hmac_client, mac_key_client, parameters)
412
468
  mac_server = HMAC.get(hmac_server, mac_key_server, parameters)
413
-
469
+
414
470
  session.configure_client cipher: cipher_client, hmac: mac_client,
415
471
  compression: normalize_compression_name(compression_client),
416
472
  compression_level: options[:compression_level],
417
473
  rekey_limit: options[:rekey_limit],
418
474
  max_packets: options[:rekey_packet_limit],
419
475
  max_blocks: options[:rekey_blocks_limit]
420
-
476
+
421
477
  session.configure_server cipher: cipher_server, hmac: mac_server,
422
478
  compression: normalize_compression_name(compression_server),
423
479
  rekey_limit: options[:rekey_limit],
424
480
  max_packets: options[:rekey_packet_limit],
425
481
  max_blocks: options[:rekey_blocks_limit]
426
-
482
+
427
483
  @initialized = true
428
484
  end
429
-
485
+
430
486
  # Given the SSH name for some compression algorithm, return a normalized
431
487
  # name as a symbol.
432
488
  def normalize_compression_name(name)