net-ssh 8.1.0 → 8.1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8a3eb4e5ac79bff25a68aaecd501c437bf0145a7458c8edd00f90e3b9ae14641
4
- data.tar.gz: cabb7b40f56ed19ca61a1a09eccff1745618a62e258a9c2ba1804fdfbf02c44b
3
+ metadata.gz: 07eacca644427ed0543e1a428d7075c1d5fd4a0da378e83959dcd312cd7f3500
4
+ data.tar.gz: eb50984faecccca334a68cd2b8fc04a749010e85e3cad2acb5e868b44bd168e6
5
5
  SHA512:
6
- metadata.gz: e748a76a0dff8328c8d12da44f8372814c15260fa04f428459145f626cbde231173475d0b8547f907f0d5c983e4abf9ca8f23ef0eb26469a0558d259ee7997e1
7
- data.tar.gz: c54425a607b9056a9bbd15985791911948a86548a1a6b0f8894aa193dc35aa703a2435ad25c11a0b7c622a7b6605d4815dcd8006864f311c8c0443b8a02acd63
6
+ metadata.gz: 0eab425719d3294df5ece8aff6758c4a627a67b366b661d1a4475b9979c5dea5e614ab583f559fd3a4d36ccd7d8f0cb35776e99a955007830a6ed0b3b37c04bb
7
+ data.tar.gz: 4c5a4eaeff4882604a01156bf22322d81c864f26b0637cb0400554206c514aa137a00f9b83ed637001a7ffb4ef599d49d512862963b1dd1041db6886dae44754
checksums.yaml.gz.sig CHANGED
Binary file
data/CHANGES.txt CHANGED
@@ -1,3 +1,7 @@
1
+ === 8.1.1
2
+
3
+ * Security: refuse host keys marked `@revoked` in known_hosts, and certificates of such a key or signed by it, by raising the new `Net::SSH::HostKeyRevoked`, which `remember_host!` can't override; these lines were loaded as trusted keys, so every verifier except `:never` accepted the revoked key [GHSA-hjwv-j52r-xcg7]
4
+
1
5
  === 8.1.0
2
6
 
3
7
  * BREAKING: Publickey auth offers each RSA key and certificate once, with the first `pubkey_algorithms` entry the server lists in RFC 8308 `server-sig-algs`, as OpenSSH does, so refused RSA keys no longer use up `MaxAuthTries` two or three at a time. Servers that send no `server-sig-algs` (OpenSSH 7.1 and older, some other servers) are offered RSA keys only as `ssh-rsa`, and only when it's enabled (e.g. `pubkey_algorithms: %w[+ssh-rsa]`); by default RSA keys are skipped there instead of being tried with rsa-sha2-512/256; OpenSSH 7.2 to 9.5 servers that accept only `ssh-rsa` still list rsa-sha2-* in `server-sig-algs`, so they need `ssh-rsa` first (`pubkey_algorithms: %w[^ssh-rsa]`) [#886, #1077]
@@ -120,5 +120,15 @@ module Net
120
120
  # you want to proceed anyway, simply call the remember_host! method on the
121
121
  # exception, and then retry.
122
122
  class HostKeyUnknown < HostKeyError; end
123
+
124
+ # Raised when the key given by the host, or the CA that signed its
125
+ # certificate, is marked @revoked in a known hosts file. A revoked key must
126
+ # never be accepted, so unlike the other host key errors this one can't be
127
+ # overridden: #remember_host! just raises it again.
128
+ class HostKeyRevoked < HostKeyError
129
+ def remember_host!
130
+ raise self
131
+ end
132
+ end
123
133
  end
124
134
  end
@@ -103,6 +103,22 @@ module Net
103
103
  server_key.critical_options.empty?
104
104
  end
105
105
  end
106
+
107
+ # @revoked entry
108
+ class Revoked
109
+ def initialize(key, comment: nil)
110
+ @key = key
111
+ @comment = comment
112
+ end
113
+
114
+ # Returns true for the revoked key itself and for any certificate that
115
+ # wraps it or is signed by it, matching OpenSSH's check_key_not_revoked.
116
+ def matches_key?(server_key)
117
+ keys = [server_key]
118
+ keys += [server_key.key, server_key.signature_key] if server_key.respond_to?(:signature_key)
119
+ keys.any? { |key| key.ssh_type == @key.ssh_type && key.to_blob == @key.to_blob }
120
+ end
121
+ end
106
122
  end
107
123
 
108
124
  # Represents the result of a search in known hosts
@@ -111,11 +127,17 @@ module Net
111
127
  include Enumerable
112
128
  attr_reader :host
113
129
 
114
- def initialize(host_keys, host, known_hosts, options = {})
130
+ def initialize(host_keys, host, known_hosts, options = {}, revoked_keys = [])
115
131
  @host_keys = host_keys
116
132
  @host = host
117
133
  @known_hosts = known_hosts
118
134
  @options = options
135
+ @revoked_keys = revoked_keys
136
+ end
137
+
138
+ # Returns true if a @revoked known-hosts entry for this host matches +key+.
139
+ def revoked?(key)
140
+ @revoked_keys.any? { |entry| entry.matches_key?(key) }
119
141
  end
120
142
 
121
143
  def add_host_key(key)
@@ -161,11 +183,14 @@ module Net
161
183
  # Searches all known host files (see KnownHosts.hostfiles) for all keys
162
184
  # of the given host. Returns an enumerable of keys found.
163
185
  def search_for(host, options = {})
164
- HostKeys.new(search_in(hostfiles(options), host, options), host, self, options)
186
+ files = hostfiles(options)
187
+ revoked_keys = files.flat_map { |file| KnownHosts.new(file).revoked_keys_for(host) }
188
+ HostKeys.new(search_in(files, host, options), host, self, options, revoked_keys)
165
189
  end
166
190
 
167
191
  # Search for all known keys for the given host, in every file given in
168
- # the +files+ array. Returns the list of keys.
192
+ # the +files+ array. Returns the list of keys. @revoked entries are not
193
+ # included.
169
194
  def search_in(files, host, options = {})
170
195
  files.flat_map { |file| KnownHosts.new(file).keys_for(host, options) }
171
196
  end
@@ -229,46 +254,50 @@ module Net
229
254
  # "[net.ssh.test]:5555"
230
255
  # "[1,2,3,4]:5555"
231
256
  # "[net.ssh.test]:5555,[1.2.3.4]:5555
257
+ #
258
+ # @revoked entries are not included, see revoked_keys_for.
232
259
  def keys_for(host, options = {})
233
260
  keys = []
234
- return keys unless File.readable?(source)
235
-
236
261
  entries = host.split(/,/)
237
262
  host_name = entries[0]
238
263
  host_ip = entries[1]
239
264
 
240
- File.open(source) do |file|
241
- file.each_line do |line|
242
- if line.start_with?('@')
243
- marker, hosts, type, key_content, comment = line.split(' ')
244
- else
245
- marker = nil
246
- hosts, type, key_content, comment = line.split(' ')
247
- end
265
+ each_entry do |marker, hostlist, key_content, comment|
266
+ next if marker == "@revoked"
248
267
 
249
- # Skip empty line or one that is commented
250
- next if hosts.nil? || hosts.start_with?('#')
268
+ found = hostlist.any? { |pattern| match(host_name, pattern) } || known_host_hash?(hostlist, entries)
269
+ next unless found
251
270
 
252
- hostlist = hosts.split(',')
271
+ found = hostlist.include?(host_ip) if options[:check_host_ip] && entries.size > 1 && hostlist.size > 1
272
+ next unless found
253
273
 
254
- next unless SUPPORTED_TYPE.include?(type)
274
+ raw_key = read_key(key_content)
275
+
276
+ keys <<
277
+ if marker == "@cert-authority"
278
+ HostKeyEntries::CertAuthority.new(raw_key, comment: comment)
279
+ else
280
+ HostKeyEntries::PubKey.new(raw_key, comment: comment)
281
+ end
282
+ end
255
283
 
256
- found = hostlist.any? { |pattern| match(host_name, pattern) } || known_host_hash?(hostlist, entries)
257
- next unless found
284
+ keys
285
+ end
258
286
 
259
- found = hostlist.include?(host_ip) if options[:check_host_ip] && entries.size > 1 && hostlist.size > 1
260
- next unless found
287
+ # Returns the @revoked entries for the given host, which takes the same
288
+ # formats as keys_for. An entry matching any of the comma-separated names
289
+ # counts, so :check_host_ip can't narrow a revocation.
290
+ def revoked_keys_for(host)
291
+ keys = []
292
+ entries = host.split(/,/)
261
293
 
262
- blob = key_content.unpack("m*").first
263
- raw_key = Net::SSH::Buffer.new(blob).read_key
294
+ each_entry do |marker, hostlist, key_content, comment|
295
+ next unless marker == "@revoked"
264
296
 
265
- keys <<
266
- if marker == "@cert-authority"
267
- HostKeyEntries::CertAuthority.new(raw_key, comment: comment)
268
- else
269
- HostKeyEntries::PubKey.new(raw_key, comment: comment)
270
- end
271
- end
297
+ found = entries.any? { |entry| hostlist.any? { |pattern| match(entry, pattern) } }
298
+ next unless found || known_host_hash?(hostlist, entries)
299
+
300
+ keys << HostKeyEntries::Revoked.new(read_key(key_content), comment: comment)
272
301
  end
273
302
 
274
303
  keys
@@ -313,6 +342,32 @@ module Net
313
342
  file.puts "#{host} #{key.ssh_type} #{blob}"
314
343
  end
315
344
  end
345
+
346
+ private
347
+
348
+ # Yields the marker, host patterns, key and comment of each usable line.
349
+ def each_entry
350
+ return unless File.readable?(source)
351
+
352
+ File.open(source) do |file|
353
+ file.each_line do |line|
354
+ fields = line.split(' ')
355
+ marker = fields.shift if fields.first&.start_with?('@')
356
+ hosts, type, key_content, comment = fields
357
+
358
+ # Skip empty line or one that is commented
359
+ next if hosts.nil? || hosts.start_with?('#')
360
+
361
+ next unless SUPPORTED_TYPE.include?(type)
362
+
363
+ yield marker, hosts.split(','), key_content, comment
364
+ end
365
+ end
366
+ end
367
+
368
+ def read_key(key_content)
369
+ Net::SSH::Buffer.new(key_content.unpack("m*").first).read_key
370
+ end
316
371
  end
317
372
  end
318
373
  end
@@ -7,11 +7,13 @@ module Net
7
7
  # verify a connection if the server is the localhost and the port is a
8
8
  # nonstandard port number. Those two conditions will typically mean the
9
9
  # connection is being tunnelled through a forwarded port, so the known-hosts
10
- # file will not be helpful (in general).
10
+ # file will not be helpful (in general). A key marked @revoked is refused
11
+ # either way.
11
12
  class AcceptNewOrLocalTunnel < AcceptNew
12
13
  # Tries to determine if the connection is being tunnelled, and if so,
13
14
  # returns true. Otherwise, performs the standard strict verification.
14
15
  def verify(arguments)
16
+ refuse_revoked!(arguments)
15
17
  return true if tunnelled?(arguments)
16
18
 
17
19
  super
@@ -10,10 +10,13 @@ module Net
10
10
  # (HostKeyUnknown). This is in contrast to the "Strict" class, which will
11
11
  # silently add the key to your known_hosts file. If the server does appear at
12
12
  # least once, but the key given does not match any known for the server, an
13
- # exception will be raised (HostKeyMismatch).
13
+ # exception will be raised (HostKeyMismatch). A key marked @revoked in a
14
+ # host file is always refused (HostKeyRevoked).
14
15
  # Otherwise, this returns true.
15
16
  class Always
16
17
  def verify(arguments)
18
+ refuse_revoked!(arguments)
19
+
17
20
  host_keys = arguments[:session].host_keys
18
21
 
19
22
  # We've never seen this host before, so raise an exception.
@@ -47,6 +50,18 @@ module Net
47
50
 
48
51
  private
49
52
 
53
+ # Custom :known_hosts objects may return a host_keys collection with no
54
+ # revocation support.
55
+ def refuse_revoked!(arguments)
56
+ host_keys = arguments[:session].host_keys
57
+ return unless host_keys.respond_to?(:revoked?) && host_keys.revoked?(arguments[:key])
58
+
59
+ exception = HostKeyRevoked.new("fingerprint #{arguments[:fingerprint]} " +
60
+ "is revoked for #{host_keys.host.inspect}")
61
+ exception.data = arguments
62
+ raise exception
63
+ end
64
+
50
65
  # Enforces the @cert-authority certificate constraints for entries that
51
66
  # implement them (Net::SSH::HostKeyEntries::CertAuthority). Non-certificate
52
67
  # entries do not respond to these predicates and are left untouched.
@@ -3,7 +3,8 @@ module Net
3
3
  module Verifiers
4
4
  # This host key verifier simply allows every key it sees, without
5
5
  # any verification. This is simple, but very insecure because it
6
- # exposes you to MiTM attacks.
6
+ # exposes you to MiTM attacks. It does not read the known hosts files,
7
+ # so it accepts keys marked @revoked as well.
7
8
  class Never
8
9
  # Returns true.
9
10
  def verify(arguments)
@@ -52,7 +52,7 @@ module Net
52
52
  MINOR = 1
53
53
 
54
54
  # The tiny component of this version of the Net::SSH library
55
- TINY = 0
55
+ TINY = 1
56
56
 
57
57
  # The prerelease component of this version of the Net::SSH library
58
58
  # nil allowed
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: net-ssh
3
3
  version: !ruby/object:Gem::Version
4
- version: 8.1.0
4
+ version: 8.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Jamis Buck
@@ -32,7 +32,7 @@ cert_chain:
32
32
  ufHf4AX2UIkJbh7zCPkiNCqIr7MSWLNFG/9lOlHYsEJM8XujT1ofPobYx6YSFx/C
33
33
  7HBrI7UX7awt6UvBZebhcHzyMHxg/B5PVQllPA==
34
34
  -----END CERTIFICATE-----
35
- date: 2026-11-02 00:00:00.000000000 Z
35
+ date: 2026-11-06 00:00:00.000000000 Z
36
36
  dependencies:
37
37
  - !ruby/object:Gem::Dependency
38
38
  name: logger
metadata.gz.sig CHANGED
@@ -1 +1 @@
1
- !�o���!Uљ��w̱ˋM�!�#:�u,�oP�\f ��+,�2��a���tT���_�6J�hՍu�YLVI;��Ÿ�t����^�94(��M�
1
+ ����K�#��إ�TJ�q�3v|�6e`�gI;f�s�"O)�R��/�?��