net-ssh 8.0.0.rc2 → 8.0.0.rc4
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 +4 -4
- checksums.yaml.gz.sig +0 -0
- data/.github/workflows/rubocop.yml +3 -1
- data/CHANGES.txt +22 -0
- data/README.md +10 -1
- data/lib/net/ssh/authentication/agent.rb +27 -6
- data/lib/net/ssh/authentication/ed25519.rb +1 -1
- data/lib/net/ssh/authentication/methods/abstract.rb +2 -8
- data/lib/net/ssh/authentication/methods/publickey.rb +3 -1
- data/lib/net/ssh/authentication/security_key.rb +70 -0
- data/lib/net/ssh/authentication/session.rb +29 -17
- data/lib/net/ssh/buffer.rb +4 -0
- data/lib/net/ssh/buffered_io.rb +40 -3
- data/lib/net/ssh/config.rb +22 -12
- data/lib/net/ssh/connection/channel.rb +45 -1
- data/lib/net/ssh/connection/session.rb +51 -4
- data/lib/net/ssh/errors.rb +14 -6
- data/lib/net/ssh/key_factory.rb +7 -1
- data/lib/net/ssh/service/forward.rb +26 -21
- data/lib/net/ssh/transport/algorithms.rb +40 -18
- data/lib/net/ssh/transport/packet_stream.rb +12 -0
- data/lib/net/ssh/transport/session.rb +4 -0
- data/lib/net/ssh/verifiers/always.rb +3 -0
- data/lib/net/ssh/version.rb +1 -1
- data/lib/net/ssh.rb +7 -5
- data.tar.gz.sig +0 -0
- metadata +3 -2
- metadata.gz.sig +0 -0
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 475db99174313b32d90dc650a358dd0cbec01ae59b0daca8f2e02eeb97a8cb9e
|
|
4
|
+
data.tar.gz: 9cdb86ac170fa3b3ca90357a106bc9742210dd1eae61e7525f4b36f71342a590
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6ada5144a2d5b86aae33b317a42a915b9107731b921fd01bfac3678da06499e9e1fd9c5f4bb310787f345ff8279445c82b00fb129c1837341d40fe7f9eea1779
|
|
7
|
+
data.tar.gz: 0cf535957b80c52960df55d93073efff00aa7cf89a9e7323a4dc38e59833ae0bf3a2e419c032c282ffe38bb14234486436c4ff151931dff73d0330229a83508c
|
checksums.yaml.gz.sig
CHANGED
|
Binary file
|
data/CHANGES.txt
CHANGED
|
@@ -1,3 +1,25 @@
|
|
|
1
|
+
=== 8.0.0 rc4
|
|
2
|
+
|
|
3
|
+
* Security: RSA signatures using SHA-1 (`ssh-rsa`, `ssh-rsa-cert-v01@openssh.com`) are no longer offered by default for host keys or public key authentication, as in OpenSSH since 8.8; RSA keys keep working via rsa-sha2-256/512. For servers that only support SHA-1 add `HostKeyAlgorithms +ssh-rsa` / `PubkeyAcceptedAlgorithms +ssh-rsa` in ssh_config (or `host_key: '+ssh-rsa'` / `pubkey_algorithms: %w[+ssh-rsa]`), or pass `append_all_supported_algorithms: true` [#1039]
|
|
4
|
+
* Security: ssh_config `PubkeyAcceptedAlgorithms` (and its old name `PubkeyAcceptedKeyTypes`) is now honoured, including `+`, `-` and `^`, and `pubkey_algorithms` now applies to all key types instead of only RSA, adding `rsa-sha2-512`; DSA (`ssh-dss`) keys are no longer offered by default, as in OpenSSH since 7.0 — add `+ssh-dss` or pass `append_all_supported_algorithms: true` to re-enable [#923]
|
|
5
|
+
* Security: ssh_config `Ciphers`, `MACs`, `KexAlgorithms` and `HostKeyAlgorithms` lists starting with `+`, `-` or `^` now append to, remove from or prepend to the default list, as in OpenSSH, instead of silently enabling every supported algorithm including weak ones that are off by default; every listed item is honoured and `-` accepts wildcards. Pass `append_all_supported_algorithms: true` to also get the full supported list [#925]
|
|
6
|
+
* Support agent-backed FIDO/U2F security keys (sk-ecdsa-sha2-nistp256@openssh.com, sk-ssh-ed25519@openssh.com) for publickey auth [#1007]
|
|
7
|
+
* Port forwarding no longer buffers a whole stream in memory when one side reads slowly: like OpenSSH, window is granted only as data is written to the local socket, and the local socket is read only while the remote window has room. An application that writes everything to a forwarded port before reading anything now blocks once the window fills, as it would with `ssh -L` or a direct TCP connection; the old unbounded buffering hid this [#1037, #1003]
|
|
8
|
+
* Data forwarded to a local socket just before the remote end sends EOF or closes the channel is no longer truncated or dropped: it is written out before the socket is shut down or closed, without blocking the event loop until the local application reads [#1037]
|
|
9
|
+
* Multi-factor authentication works when the server's AuthenticationMethods order differs from net-ssh's: after each partial success the allowed methods are tried again, as OpenSSH does [#873, #872]
|
|
10
|
+
* `HostKeyError#host`, `#port` and `#ip` now return the connected host, port and IP (`ip` is nil through a ProxyCommand) instead of always nil [#963]
|
|
11
|
+
* OpenSSH-format private keys with Windows (CRLF) line endings now load [#922, #921]
|
|
12
|
+
* Send CHANNEL_EOF before CHANNEL_CLOSE when closing a session's remaining channels, as OpenSSH does; fixes hangs at close with servers that wait for EOF, such as some SFTP servers [#672]
|
|
13
|
+
* ssh_config: trailing `# comments` are now ignored, as in OpenSSH; a `#` inside quotes or in the middle of a word is kept [#990]
|
|
14
|
+
|
|
15
|
+
=== 8.0.0 rc3
|
|
16
|
+
|
|
17
|
+
* Fix "Calling Net::SSH::Buffer methods on HostKeyEntries PubKey is deprecated" warnings printed on every connection to a host in known_hosts [#1026, #1027]
|
|
18
|
+
* `ProxyJump none` now means no proxy instead of jumping through a host named "none", and `ProxyCommand none` is matched case-insensitively, as in OpenSSH [#893, #972, #1030]
|
|
19
|
+
* Send SIGHUP to the ProxyCommand process on close, as OpenSSH does, so tunnels such as EC2 Instance Connect Endpoint no longer hang at exit [#947]
|
|
20
|
+
* Reconnect when the ssh-agent closes the connection on the legacy version request (e.g. older Proton Pass) instead of raising FrozenError [#1008]
|
|
21
|
+
* ssh_config: parse `Key = value` lines like OpenSSH (the value no longer keeps the leading space, and the keyword ends at the first `=`), and honor ProxyJump/ProxyCommand before the first Host [#1034]
|
|
22
|
+
|
|
1
23
|
=== 8.0.0 rc2
|
|
2
24
|
|
|
3
25
|
* Security: refuse agent channels opened by the server unless agent forwarding was requested (`forward_agent`, ForwardAgent, or `Forward#agent`); any use of port forwarding let a malicious server use the local ssh-agent [GHSA-qpg5-fx6p-8cq3]
|
data/README.md
CHANGED
|
@@ -39,13 +39,17 @@ Unsecure algoritms will definitely be removed in Net::SSH 8.*.
|
|
|
39
39
|
|
|
40
40
|
| Name | Support | Details |
|
|
41
41
|
|----------------------|-----------------------|----------|
|
|
42
|
-
|
|
|
42
|
+
| rsa-sha2-512 | OK | |
|
|
43
|
+
| rsa-sha2-256 | OK | |
|
|
44
|
+
| ssh-rsa | Disabled by default in 8.0 | SHA-1 signatures, off by default as in OpenSSH 8.8; enable with `HostKeyAlgorithms +ssh-rsa` or `host_key: '+ssh-rsa'` |
|
|
43
45
|
| ssh-ed25519 | OK | Backed by Ruby OpenSSL raw Ed25519 APIs (`openssl >= 3.2.0`) |
|
|
44
46
|
| ecdsa-sha2-nistp521 | OK | [using weak elliptic curves](https://safecurves.cr.yp.to/) |
|
|
45
47
|
| ecdsa-sha2-nistp384 | OK | [using weak elliptic curves](https://safecurves.cr.yp.to/) |
|
|
46
48
|
| ecdsa-sha2-nistp256 | OK | [using weak elliptic curves](https://safecurves.cr.yp.to/) |
|
|
47
49
|
| ssh-dss | Deprecated in 6.0 | unsecure, will be removed in 8.0 |
|
|
48
50
|
|
|
51
|
+
RSA public key authentication likewise uses rsa-sha2-512 or rsa-sha2-256 by default; ssh-rsa and ssh-dss keys are only offered when enabled, e.g. with `PubkeyAcceptedAlgorithms +ssh-rsa` or `pubkey_algorithms: %w[+ssh-rsa]`.
|
|
52
|
+
|
|
49
53
|
### Key Exchange
|
|
50
54
|
|
|
51
55
|
| Name | Support | Details |
|
|
@@ -194,6 +198,11 @@ Ed25519 public key auth is backed by Ruby OpenSSL. The `bcrypt_pbkdf` gem is onl
|
|
|
194
198
|
gem install bcrypt_pbkdf
|
|
195
199
|
```
|
|
196
200
|
|
|
201
|
+
### FIDO/U2F security keys
|
|
202
|
+
|
|
203
|
+
`sk-ecdsa-sha2-nistp256@openssh.com` and `sk-ssh-ed25519@openssh.com` keys work when held in `ssh-agent`.
|
|
204
|
+
Loading `sk-*` private key files from disk is not supported.
|
|
205
|
+
|
|
197
206
|
## RUBY SUPPORT
|
|
198
207
|
|
|
199
208
|
* See [net-ssh.gemspec](https://github.com/net-ssh/net-ssh/blob/master/net-ssh.gemspec) for current versions ruby requirements
|
|
@@ -17,6 +17,9 @@ module Net
|
|
|
17
17
|
# An exception for indicating that the SSH agent is not available.
|
|
18
18
|
class AgentNotAvailable < AgentError; end
|
|
19
19
|
|
|
20
|
+
# Raised when the agent closes the connection before replying.
|
|
21
|
+
class AgentClosedConnection < AgentError; end
|
|
22
|
+
|
|
20
23
|
# This class implements a simple client for the ssh-agent protocol. It
|
|
21
24
|
# does not implement any specific protocol, but instead copies the
|
|
22
25
|
# behavior of the ssh-agent functions in the OpenSSH library (3.8).
|
|
@@ -83,12 +86,18 @@ module Net
|
|
|
83
86
|
# socket reports that it is an SSH2-compatible agent, this will fail
|
|
84
87
|
# (it only supports the ssh-agent distributed by OpenSSH).
|
|
85
88
|
def connect!(agent_socket_factory = nil, identity_agent = nil)
|
|
89
|
+
@agent_socket_factory = agent_socket_factory
|
|
90
|
+
@identity_agent = identity_agent
|
|
91
|
+
open_agent_socket!
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def open_agent_socket!
|
|
86
95
|
debug { "connecting to ssh-agent" }
|
|
87
96
|
@socket =
|
|
88
|
-
if agent_socket_factory
|
|
89
|
-
agent_socket_factory.call
|
|
90
|
-
elsif identity_agent
|
|
91
|
-
unix_socket_class.open(File.expand_path(identity_agent))
|
|
97
|
+
if @agent_socket_factory
|
|
98
|
+
@agent_socket_factory.call
|
|
99
|
+
elsif @identity_agent
|
|
100
|
+
unix_socket_class.open(File.expand_path(@identity_agent))
|
|
92
101
|
elsif ENV['SSH_AUTH_SOCK'] && unix_socket_class
|
|
93
102
|
unix_socket_class.open(File.expand_path(ENV['SSH_AUTH_SOCK']))
|
|
94
103
|
elsif Gem.win_platform? && RUBY_ENGINE != "jruby"
|
|
@@ -100,6 +109,7 @@ module Net
|
|
|
100
109
|
error { "could not connect to ssh-agent: #{e.message}" }
|
|
101
110
|
raise AgentNotAvailable, $!.message
|
|
102
111
|
end
|
|
112
|
+
private :open_agent_socket!
|
|
103
113
|
|
|
104
114
|
# Attempts to negotiate the SSH agent protocol version. Raises an error
|
|
105
115
|
# if the version could not be negotiated successfully.
|
|
@@ -114,6 +124,11 @@ module Net
|
|
|
114
124
|
elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2
|
|
115
125
|
raise AgentNotAvailable, "unknown response from agent: #{type}, #{body.to_s.inspect}"
|
|
116
126
|
end
|
|
127
|
+
rescue AgentClosedConnection
|
|
128
|
+
# Agents built on ssh-agent-lib <= 0.6.0 (e.g. older Proton Pass) drop the connection on
|
|
129
|
+
# this legacy request but handle the rest of the protocol (#1008).
|
|
130
|
+
debug { "ssh-agent closed the connection on the version request; assuming a modern agent and reconnecting" }
|
|
131
|
+
open_agent_socket!
|
|
117
132
|
end
|
|
118
133
|
|
|
119
134
|
# Return an array of all identities (public keys) known to the agent.
|
|
@@ -223,8 +238,14 @@ module Net
|
|
|
223
238
|
# tuple consisting of the packet type, and the packet's body (which
|
|
224
239
|
# is returned as a Net::SSH::Buffer).
|
|
225
240
|
def read_packet
|
|
226
|
-
|
|
227
|
-
|
|
241
|
+
header = @socket.read(4)
|
|
242
|
+
raise AgentClosedConnection, "agent closed the connection" if header.nil?
|
|
243
|
+
|
|
244
|
+
buffer = Net::SSH::Buffer.new(header)
|
|
245
|
+
body = @socket.read(buffer.read_long)
|
|
246
|
+
raise AgentClosedConnection, "agent closed the connection" if body.nil?
|
|
247
|
+
|
|
248
|
+
buffer.append(body)
|
|
228
249
|
type = buffer.read_byte
|
|
229
250
|
debug { "received agent packet #{type} len #{buffer.length - 4}" }
|
|
230
251
|
return type, buffer
|
|
@@ -74,7 +74,7 @@ module Net
|
|
|
74
74
|
end
|
|
75
75
|
|
|
76
76
|
def self.read(datafull, password)
|
|
77
|
-
datafull = datafull.strip
|
|
77
|
+
datafull = datafull.strip.encode(universal_newline: true)
|
|
78
78
|
raise ArgumentError.new("Expected #{MBEGIN} at start of private key") unless datafull.start_with?(MBEGIN)
|
|
79
79
|
raise ArgumentError.new("Expected #{MEND} at end of private key") unless datafull.end_with?(MEND)
|
|
80
80
|
|
|
@@ -20,9 +20,7 @@ module Net
|
|
|
20
20
|
# this.
|
|
21
21
|
attr_reader :key_manager
|
|
22
22
|
|
|
23
|
-
#
|
|
24
|
-
# extended to other keys, e.g after reading of
|
|
25
|
-
# PubkeyAcceptedAlgorithms option from ssh_config file is implemented.
|
|
23
|
+
# public key algorithms to be used
|
|
26
24
|
attr_reader :pubkey_algorithms
|
|
27
25
|
|
|
28
26
|
# Instantiates a new authentication method.
|
|
@@ -31,11 +29,7 @@ module Net
|
|
|
31
29
|
@key_manager = options[:key_manager]
|
|
32
30
|
@options = options
|
|
33
31
|
@prompt = options[:password_prompt]
|
|
34
|
-
@pubkey_algorithms =
|
|
35
|
-
|| %w[rsa-sha2-256-cert-v01@openssh.com
|
|
36
|
-
ssh-rsa-cert-v01@openssh.com
|
|
37
|
-
rsa-sha2-256
|
|
38
|
-
ssh-rsa]
|
|
32
|
+
@pubkey_algorithms = session.transport.pubkey_algorithms
|
|
39
33
|
self.logger = session.logger
|
|
40
34
|
end
|
|
41
35
|
|
|
@@ -15,6 +15,8 @@ module Net
|
|
|
15
15
|
def authenticate(next_service, username, password = nil)
|
|
16
16
|
return false unless key_manager
|
|
17
17
|
|
|
18
|
+
debug { "pubkey_algorithms: #{pubkey_algorithms.join(', ')}" }
|
|
19
|
+
|
|
18
20
|
key_manager.each_identity do |identity|
|
|
19
21
|
return true if authenticate_with(identity, next_service, username)
|
|
20
22
|
end
|
|
@@ -123,7 +125,7 @@ module Net
|
|
|
123
125
|
end
|
|
124
126
|
end
|
|
125
127
|
end
|
|
126
|
-
elsif authenticate_with_alg(identity, next_service, username, type)
|
|
128
|
+
elsif pubkey_algorithms.include?(type) && authenticate_with_alg(identity, next_service, username, type)
|
|
127
129
|
# success
|
|
128
130
|
return true
|
|
129
131
|
end
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
require 'net/ssh/authentication/pub_key_fingerprint'
|
|
2
|
+
|
|
3
|
+
module Net
|
|
4
|
+
module SSH
|
|
5
|
+
module Authentication
|
|
6
|
+
# FIDO/U2F security keys held by ssh-agent. The agent does the signing (incl. user
|
|
7
|
+
# presence); net-ssh only parses and re-serialises the public key blob (see PROTOCOL.u2f).
|
|
8
|
+
module SecurityKey
|
|
9
|
+
SK_ECDSA_SHA2_NISTP256 = "sk-ecdsa-sha2-nistp256@openssh.com"
|
|
10
|
+
SK_SSH_ED25519 = "sk-ssh-ed25519@openssh.com"
|
|
11
|
+
TYPES = [SK_ECDSA_SHA2_NISTP256, SK_SSH_ED25519].freeze
|
|
12
|
+
|
|
13
|
+
def self.understands?(type)
|
|
14
|
+
TYPES.include?(type)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Public key of an agent-held security key; signing is delegated to the agent.
|
|
18
|
+
class PubKey
|
|
19
|
+
include Net::SSH::Authentication::PubKeyFingerprint
|
|
20
|
+
|
|
21
|
+
attr_reader :ssh_type, :curve, :public_key_data, :application
|
|
22
|
+
|
|
23
|
+
# Wire formats:
|
|
24
|
+
# sk-ecdsa-sha2-nistp256@openssh.com: string curve, string Q, string application
|
|
25
|
+
# sk-ssh-ed25519@openssh.com: string public_key, string application
|
|
26
|
+
def self.read_keyblob(type, buffer)
|
|
27
|
+
case type
|
|
28
|
+
when SK_ECDSA_SHA2_NISTP256
|
|
29
|
+
curve = buffer.read_string
|
|
30
|
+
public_key_data = buffer.read_string
|
|
31
|
+
application = buffer.read_string
|
|
32
|
+
new(type, public_key_data, application, curve: curve)
|
|
33
|
+
when SK_SSH_ED25519
|
|
34
|
+
public_key_data = buffer.read_string
|
|
35
|
+
application = buffer.read_string
|
|
36
|
+
new(type, public_key_data, application)
|
|
37
|
+
else
|
|
38
|
+
raise NotImplementedError, "unsupported security key type `#{type}'"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def initialize(ssh_type, public_key_data, application, curve: nil)
|
|
43
|
+
@ssh_type = ssh_type
|
|
44
|
+
@public_key_data = public_key_data
|
|
45
|
+
@application = application
|
|
46
|
+
@curve = curve
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def to_blob
|
|
50
|
+
buffer = Net::SSH::Buffer.new
|
|
51
|
+
buffer.write_string(ssh_type)
|
|
52
|
+
buffer.write_string(curve) if curve
|
|
53
|
+
buffer.write_string(public_key_data)
|
|
54
|
+
buffer.write_string(application)
|
|
55
|
+
buffer.to_s
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def ssh_signature_type
|
|
59
|
+
ssh_type
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Only used to de-duplicate identities, so a stable key-specific string suffices.
|
|
63
|
+
def to_pem
|
|
64
|
+
"#{ssh_type} #{[to_blob].pack('m0')}"
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
@@ -69,27 +69,22 @@ module Net
|
|
|
69
69
|
|
|
70
70
|
attempted = []
|
|
71
71
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
pubkey_algorithms: options[:pubkey_algorithms] || nil)
|
|
83
|
-
rescue NameError
|
|
84
|
-
debug {"Mechanism #{name} was requested, but isn't a known type. Ignoring it."}
|
|
85
|
-
next
|
|
72
|
+
# After a partial success (sshd AuthenticationMethods) restart from the most preferred method the server now allows, as OpenSSH does.
|
|
73
|
+
loop do
|
|
74
|
+
@partial_success = false
|
|
75
|
+
|
|
76
|
+
@auth_methods.each do |name|
|
|
77
|
+
next unless @allowed_auth_methods.include?(name)
|
|
78
|
+
|
|
79
|
+
attempted << name
|
|
80
|
+
return true if try_authenticate(name, next_service, username, password, key_manager)
|
|
81
|
+
break if @partial_success
|
|
86
82
|
end
|
|
87
83
|
|
|
88
|
-
|
|
89
|
-
rescue Net::SSH::Authentication::DisallowedMethod
|
|
84
|
+
break unless @partial_success
|
|
90
85
|
end
|
|
91
86
|
|
|
92
|
-
error { "all authorization methods failed (tried #{attempted.join(', ')})" }
|
|
87
|
+
error { "all authorization methods failed (tried #{attempted.uniq.join(', ')})" }
|
|
93
88
|
return false
|
|
94
89
|
ensure
|
|
95
90
|
key_manager.finish if key_manager
|
|
@@ -109,6 +104,7 @@ module Net
|
|
|
109
104
|
|
|
110
105
|
when USERAUTH_FAILURE
|
|
111
106
|
@allowed_auth_methods = packet[:authentications].split(/,/)
|
|
107
|
+
@partial_success ||= packet[:partial_success]
|
|
112
108
|
debug { "allowed methods: #{packet[:authentications]}" }
|
|
113
109
|
return packet
|
|
114
110
|
|
|
@@ -136,6 +132,22 @@ module Net
|
|
|
136
132
|
|
|
137
133
|
private
|
|
138
134
|
|
|
135
|
+
def try_authenticate(method_name, next_service, username, password, key_manager)
|
|
136
|
+
debug { "trying #{method_name}" }
|
|
137
|
+
begin
|
|
138
|
+
auth_class = Methods.const_get(method_name.split(/\W+/).map { |p| p.capitalize }.join)
|
|
139
|
+
method = auth_class.new(self,
|
|
140
|
+
key_manager: key_manager, password_prompt: options[:password_prompt])
|
|
141
|
+
rescue NameError
|
|
142
|
+
debug {"Mechanism #{method_name} was requested, but isn't a known type. Ignoring it."}
|
|
143
|
+
return false
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
method.authenticate(next_service, username, password)
|
|
147
|
+
rescue Net::SSH::Authentication::DisallowedMethod
|
|
148
|
+
false
|
|
149
|
+
end
|
|
150
|
+
|
|
139
151
|
# Returns an array of paths to the key files usually defined
|
|
140
152
|
# by system default.
|
|
141
153
|
def default_keys
|
data/lib/net/ssh/buffer.rb
CHANGED
|
@@ -2,6 +2,7 @@ require 'net/ssh/transport/openssl'
|
|
|
2
2
|
|
|
3
3
|
require 'net/ssh/authentication/certificate'
|
|
4
4
|
require 'net/ssh/authentication/ed25519_loader'
|
|
5
|
+
require 'net/ssh/authentication/security_key'
|
|
5
6
|
|
|
6
7
|
module Net
|
|
7
8
|
module SSH
|
|
@@ -338,6 +339,9 @@ module Net
|
|
|
338
339
|
key = Net::SSH::Authentication::ED25519::PubKey.read_keyblob(self)
|
|
339
340
|
when /^ecdsa\-sha2\-(\w*)$/
|
|
340
341
|
key = OpenSSL::PKey::EC.read_keyblob($1, self)
|
|
342
|
+
when Net::SSH::Authentication::SecurityKey::SK_ECDSA_SHA2_NISTP256,
|
|
343
|
+
Net::SSH::Authentication::SecurityKey::SK_SSH_ED25519
|
|
344
|
+
key = Net::SSH::Authentication::SecurityKey::PubKey.read_keyblob(type, self)
|
|
341
345
|
else
|
|
342
346
|
raise NotImplementedError, "unsupported key type `#{type}'"
|
|
343
347
|
end
|
data/lib/net/ssh/buffered_io.rb
CHANGED
|
@@ -166,6 +166,11 @@ module Net
|
|
|
166
166
|
# http://github.com/net-ssh/net-ssh/tree/portfwfix
|
|
167
167
|
#
|
|
168
168
|
module ForwardedBufferedIo
|
|
169
|
+
def self.extended(object) # :nodoc:
|
|
170
|
+
object.instance_variable_set(:@on_sent, nil)
|
|
171
|
+
object.instance_variable_set(:@shutdown_write_when_drained, false)
|
|
172
|
+
end
|
|
173
|
+
|
|
169
174
|
def fill(n = 8192)
|
|
170
175
|
begin
|
|
171
176
|
super(n)
|
|
@@ -182,11 +187,25 @@ module Net
|
|
|
182
187
|
end
|
|
183
188
|
end
|
|
184
189
|
|
|
190
|
+
# Internal and experimental: registers a block called with the number of
|
|
191
|
+
# bytes each #send_pending wrote to the socket.
|
|
192
|
+
def on_sent(&block) # :nodoc:
|
|
193
|
+
@on_sent = block
|
|
194
|
+
end
|
|
195
|
+
|
|
185
196
|
def send_pending
|
|
186
197
|
begin
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
198
|
+
pending = output.length
|
|
199
|
+
result = super
|
|
200
|
+
@on_sent.call(pending - output.length) if @on_sent && output.length < pending
|
|
201
|
+
shutdown_write_if_drained
|
|
202
|
+
result
|
|
203
|
+
rescue Errno::EPIPE, Errno::ECONNRESET => e
|
|
204
|
+
# Like OpenSSH's chan_write_failed: the peer is gone, so drop what it
|
|
205
|
+
# will never read and close, letting the forwarded channel close too.
|
|
206
|
+
debug { "write failed => discarding #{output.length} bytes and closing:#{e}" }
|
|
207
|
+
output.clear!
|
|
208
|
+
close
|
|
190
209
|
return 0
|
|
191
210
|
rescue IOError => e
|
|
192
211
|
if e.message =~ /closed/ then
|
|
@@ -197,6 +216,24 @@ module Net
|
|
|
197
216
|
end
|
|
198
217
|
end
|
|
199
218
|
end
|
|
219
|
+
|
|
220
|
+
# Internal: shuts down the write side once pending output has been sent,
|
|
221
|
+
# as OpenSSH does on CHANNEL_EOF (chan_rcvd_ieof, chan_obuf_empty).
|
|
222
|
+
def shutdown_write_when_drained # :nodoc:
|
|
223
|
+
@shutdown_write_when_drained = true
|
|
224
|
+
shutdown_write_if_drained
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
private
|
|
228
|
+
|
|
229
|
+
def shutdown_write_if_drained
|
|
230
|
+
return unless @shutdown_write_when_drained && output.empty? && !closed?
|
|
231
|
+
|
|
232
|
+
@shutdown_write_when_drained = false
|
|
233
|
+
shutdown(Socket::SHUT_WR)
|
|
234
|
+
rescue Errno::EPIPE, Errno::ENOTCONN => e
|
|
235
|
+
debug { "shutdown failed => shallowing exception:#{e}" }
|
|
236
|
+
end
|
|
200
237
|
end
|
|
201
238
|
end
|
|
202
239
|
end
|
data/lib/net/ssh/config.rb
CHANGED
|
@@ -31,6 +31,7 @@ module Net
|
|
|
31
31
|
# * PreferredAuthentications => maps to the :auth_methods option
|
|
32
32
|
# * ProxyCommand => maps to the :proxy option
|
|
33
33
|
# * ProxyJump => maps to the :proxy option
|
|
34
|
+
# * PubkeyAcceptedAlgorithms (or PubkeyAcceptedKeyTypes) => maps to :pubkey_algorithms
|
|
34
35
|
# * PubKeyAuthentication => maps to the :auth_methods option
|
|
35
36
|
# * RekeyLimit => :rekey_limit
|
|
36
37
|
# * StrictHostKeyChecking => :verify_host_key
|
|
@@ -88,16 +89,14 @@ module Net
|
|
|
88
89
|
IO.foreach(file) do |line|
|
|
89
90
|
next if line =~ /^\s*(?:#.*)?$/
|
|
90
91
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
else
|
|
94
|
-
key, value = line.strip.split(/\s+/, 2)
|
|
95
|
-
end
|
|
92
|
+
# As in OpenSSH, the keyword ends at the first whitespace or "=".
|
|
93
|
+
key, value = line.strip.split(/\s*=\s*|\s+/, 2)
|
|
96
94
|
|
|
97
95
|
# silently ignore malformed entries
|
|
98
96
|
next if value.nil?
|
|
99
97
|
|
|
100
98
|
key.downcase!
|
|
99
|
+
value = uncomment(value)
|
|
101
100
|
value = unquote(value)
|
|
102
101
|
|
|
103
102
|
value = case value.strip
|
|
@@ -152,9 +151,11 @@ module Net
|
|
|
152
151
|
end
|
|
153
152
|
|
|
154
153
|
# ProxyCommand and ProxyJump override each other so they need to be tracked togeather
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
[globals, settings].each do |hash|
|
|
155
|
+
%w[proxyjump proxycommand].each do |proxy_key|
|
|
156
|
+
if (proxy_value = hash.delete(proxy_key))
|
|
157
|
+
hash['proxy'] ||= [proxy_key, proxy_value]
|
|
158
|
+
end
|
|
158
159
|
end
|
|
159
160
|
end
|
|
160
161
|
end
|
|
@@ -231,7 +232,7 @@ module Net
|
|
|
231
232
|
user: :user,
|
|
232
233
|
checkhostip: :check_host_ip
|
|
233
234
|
}.freeze
|
|
234
|
-
def translate_config_key(hash, key, value, settings)
|
|
235
|
+
def translate_config_key(hash, key, value, settings) # rubocop:disable Metrics/AbcSize
|
|
235
236
|
case key
|
|
236
237
|
when :stricthostkeychecking
|
|
237
238
|
hash[:verify_host_key] = translate_verify_host_key(value)
|
|
@@ -277,6 +278,8 @@ module Net
|
|
|
277
278
|
if (proxy = setup_proxy(*value))
|
|
278
279
|
hash[:proxy] = proxy
|
|
279
280
|
end
|
|
281
|
+
when :pubkeyacceptedalgorithms, :pubkeyacceptedkeytypes
|
|
282
|
+
hash[:pubkey_algorithms] = value.split(/,/)
|
|
280
283
|
when :pubkeyauthentication
|
|
281
284
|
if value
|
|
282
285
|
(hash[:auth_methods] << 'publickey').uniq!
|
|
@@ -302,13 +305,15 @@ module Net
|
|
|
302
305
|
def setup_proxy(type, value)
|
|
303
306
|
case type
|
|
304
307
|
when 'proxycommand'
|
|
305
|
-
if value !~ /^none$/
|
|
308
|
+
if value !~ /^none$/i
|
|
306
309
|
require 'net/ssh/proxy/command'
|
|
307
310
|
Net::SSH::Proxy::Command.new(value)
|
|
308
311
|
end
|
|
309
312
|
when 'proxyjump'
|
|
310
|
-
|
|
311
|
-
|
|
313
|
+
if value !~ /^none$/i
|
|
314
|
+
require 'net/ssh/proxy/jump'
|
|
315
|
+
Net::SSH::Proxy::Jump.new(value)
|
|
316
|
+
end
|
|
312
317
|
end
|
|
313
318
|
end
|
|
314
319
|
|
|
@@ -398,6 +403,11 @@ module Net
|
|
|
398
403
|
!condition_matches.empty? && condition_matches.all?
|
|
399
404
|
end
|
|
400
405
|
|
|
406
|
+
# As in OpenSSH's argv_split, "#" starts a comment only at the start of a token and outside quotes.
|
|
407
|
+
def uncomment(string)
|
|
408
|
+
string[/\A(?:\s*(?!#)(?:\\.|"[^"]*"?|'[^']*'?|[^\s"'])+)*/]
|
|
409
|
+
end
|
|
410
|
+
|
|
401
411
|
def unquote(string)
|
|
402
412
|
string =~ /^"(.*)"$/ ? Regexp.last_match(1) : string
|
|
403
413
|
end
|
|
@@ -118,6 +118,8 @@ module Net
|
|
|
118
118
|
|
|
119
119
|
@local_maximum_packet_size = max_pkt_size
|
|
120
120
|
@local_window_size = @local_maximum_window_size = max_win_size
|
|
121
|
+
@local_consumed = 0
|
|
122
|
+
@grant_window_on_consume = false
|
|
121
123
|
|
|
122
124
|
@on_confirm_open = on_confirm_open
|
|
123
125
|
|
|
@@ -254,6 +256,26 @@ module Net
|
|
|
254
256
|
output.append(data.to_s)
|
|
255
257
|
end
|
|
256
258
|
|
|
259
|
+
# Internal and experimental, used by port forwarding: from now on window
|
|
260
|
+
# is granted only for bytes reported via #consumed, so a slow consumer
|
|
261
|
+
# stalls the remote sender instead of buffering everything it sends.
|
|
262
|
+
# Must be called before any data is received.
|
|
263
|
+
def grant_window_on_consume! # :nodoc:
|
|
264
|
+
raise ArgumentError, "grant_window_on_consume! must be called before any data is received" if local_window_size != local_maximum_window_size
|
|
265
|
+
|
|
266
|
+
@grant_window_on_consume = true
|
|
267
|
+
end
|
|
268
|
+
|
|
269
|
+
# Internal and experimental: reports that +bytes+ of received data have
|
|
270
|
+
# been processed, letting the remote end send that much more.
|
|
271
|
+
def consumed(bytes) # :nodoc:
|
|
272
|
+
raise ArgumentError, "consumed requires grant_window_on_consume!" unless @grant_window_on_consume
|
|
273
|
+
raise ArgumentError, "consumed bytes must not be negative" if bytes.negative?
|
|
274
|
+
|
|
275
|
+
@local_consumed += bytes
|
|
276
|
+
adjust_window_for_consumed
|
|
277
|
+
end
|
|
278
|
+
|
|
257
279
|
# Returns true if the channel exists in the channel list of the session,
|
|
258
280
|
# and false otherwise. This can be used to determine whether a channel has
|
|
259
281
|
# been closed or not.
|
|
@@ -598,7 +620,12 @@ module Net
|
|
|
598
620
|
# type, and data.
|
|
599
621
|
def do_extended_data(type, data)
|
|
600
622
|
update_local_window_size(data.length)
|
|
601
|
-
|
|
623
|
+
if @on_extended_data
|
|
624
|
+
@on_extended_data.call(self, type, data)
|
|
625
|
+
elsif @grant_window_on_consume
|
|
626
|
+
# Nothing will ever consume it, so don't let it shrink the window.
|
|
627
|
+
consumed(data.length)
|
|
628
|
+
end
|
|
602
629
|
end
|
|
603
630
|
|
|
604
631
|
# Invokes the #on_eof callback when the server indicates that no
|
|
@@ -651,6 +678,8 @@ module Net
|
|
|
651
678
|
# server telling it that the window size has grown.
|
|
652
679
|
def update_local_window_size(size)
|
|
653
680
|
@local_window_size -= size
|
|
681
|
+
return adjust_window_for_consumed if @grant_window_on_consume
|
|
682
|
+
|
|
654
683
|
if local_window_size < local_maximum_window_size / 2
|
|
655
684
|
connection.send_message(
|
|
656
685
|
Buffer.from(:byte, CHANNEL_WINDOW_ADJUST, :long, remote_id, :long, LOCAL_WINDOW_SIZE_INCREMENT)
|
|
@@ -663,6 +692,21 @@ module Net
|
|
|
663
692
|
end
|
|
664
693
|
end
|
|
665
694
|
|
|
695
|
+
# Mirrors OpenSSH's channel_check_window: once the window is below half,
|
|
696
|
+
# give back what was consumed, growing the maximum window the same way
|
|
697
|
+
# #update_local_window_size does.
|
|
698
|
+
def adjust_window_for_consumed
|
|
699
|
+
return if @local_consumed.zero? || local_window_size >= local_maximum_window_size / 2
|
|
700
|
+
return if remote_id.nil? || local_closed? || remote_closed?
|
|
701
|
+
|
|
702
|
+
growth = (GOOD_LOCAL_MAXIUMUM_WINDOW_SIZE - local_maximum_window_size).clamp(0, LOCAL_WINDOW_SIZE_INCREMENT)
|
|
703
|
+
adjust = @local_consumed + growth
|
|
704
|
+
connection.send_message(Buffer.from(:byte, CHANNEL_WINDOW_ADJUST, :long, remote_id, :long, adjust))
|
|
705
|
+
@local_window_size += adjust
|
|
706
|
+
@local_maximum_window_size += growth
|
|
707
|
+
@local_consumed = 0
|
|
708
|
+
end
|
|
709
|
+
|
|
666
710
|
# Gets an +Array+ of local environment variables in the remote process'
|
|
667
711
|
# environment.
|
|
668
712
|
# A variable name can either be described by a +Regexp+ or +String+.
|
|
@@ -24,7 +24,7 @@ module Net
|
|
|
24
24
|
# # 'ssh' is an instance of Net::SSH::Connection::Session
|
|
25
25
|
# ssh.exec! "/etc/init.d/some_process start"
|
|
26
26
|
# end
|
|
27
|
-
class Session
|
|
27
|
+
class Session # rubocop:disable Metrics/ClassLength
|
|
28
28
|
include Loggable
|
|
29
29
|
include Constants
|
|
30
30
|
|
|
@@ -74,6 +74,8 @@ module Net
|
|
|
74
74
|
@channel_id_counter = -1
|
|
75
75
|
@channels = Hash.new(NilChannel.new(self))
|
|
76
76
|
@listeners = { transport.socket => nil }
|
|
77
|
+
@read_paused = {}
|
|
78
|
+
@draining = {}
|
|
77
79
|
@pending_requests = []
|
|
78
80
|
@channel_open_handlers = {}
|
|
79
81
|
@on_global_request = {}
|
|
@@ -120,12 +122,19 @@ module Net
|
|
|
120
122
|
# connection.
|
|
121
123
|
def close
|
|
122
124
|
info { "closing remaining channels (#{channels.length} open)" }
|
|
123
|
-
|
|
125
|
+
|
|
126
|
+
channels.each do |id, channel|
|
|
127
|
+
channel.eof!
|
|
128
|
+
channel.close
|
|
129
|
+
end
|
|
130
|
+
|
|
124
131
|
begin
|
|
125
132
|
loop(0.1) { channels.any? }
|
|
126
133
|
rescue Net::SSH::Disconnect
|
|
127
134
|
raise unless channels.empty?
|
|
128
135
|
end
|
|
136
|
+
@draining.each_key(&:close)
|
|
137
|
+
@draining.clear
|
|
129
138
|
transport.close
|
|
130
139
|
end
|
|
131
140
|
|
|
@@ -146,10 +155,13 @@ module Net
|
|
|
146
155
|
# a +true+ value for +include_invisible+, then those will be counted.
|
|
147
156
|
#
|
|
148
157
|
# This can be useful for determining whether the event loop should continue
|
|
149
|
-
# to be run.
|
|
158
|
+
# to be run. A forwarded socket still writing out data that arrived before
|
|
159
|
+
# its channel closed also counts as busy.
|
|
150
160
|
#
|
|
151
161
|
# ssh.loop { ssh.busy? }
|
|
152
162
|
def busy?(include_invisible = false)
|
|
163
|
+
return true if @draining.any?
|
|
164
|
+
|
|
153
165
|
if include_invisible
|
|
154
166
|
channels.any?
|
|
155
167
|
else
|
|
@@ -255,7 +267,7 @@ module Net
|
|
|
255
267
|
def ev_do_calculate_rw_wait(wait)
|
|
256
268
|
r = listeners.keys
|
|
257
269
|
w = r.select { |w2| w2.respond_to?(:pending_write?) && w2.pending_write? }
|
|
258
|
-
[r, w, io_select_wait(wait)]
|
|
270
|
+
[r.reject { |io| @read_paused.key?(io) }, w, io_select_wait(wait)]
|
|
259
271
|
end
|
|
260
272
|
|
|
261
273
|
# This is called internally as part of #process.
|
|
@@ -281,6 +293,7 @@ module Net
|
|
|
281
293
|
|
|
282
294
|
Array(writers).each do |writer|
|
|
283
295
|
writer.send_pending
|
|
296
|
+
close_if_drained(writer) if @draining.key?(writer)
|
|
284
297
|
end
|
|
285
298
|
end
|
|
286
299
|
|
|
@@ -485,9 +498,36 @@ module Net
|
|
|
485
498
|
# Removes the given io object from the listeners collection, so that the
|
|
486
499
|
# event loop will no longer monitor it.
|
|
487
500
|
def stop_listening_to(io)
|
|
501
|
+
@read_paused.delete(io)
|
|
502
|
+
@draining.delete(io)
|
|
488
503
|
listeners.delete(io)
|
|
489
504
|
end
|
|
490
505
|
|
|
506
|
+
# Internal and experimental, used by port forwarding: stops the event
|
|
507
|
+
# loop from reading +io+ while it keeps flushing its pending output.
|
|
508
|
+
def pause_reading(io) # :nodoc:
|
|
509
|
+
raise ArgumentError, "the transport socket can't be paused" if io.equal?(transport.socket)
|
|
510
|
+
|
|
511
|
+
@read_paused[io] = true if listeners.key?(io)
|
|
512
|
+
end
|
|
513
|
+
|
|
514
|
+
def resume_reading(io) # :nodoc:
|
|
515
|
+
@read_paused.delete(io)
|
|
516
|
+
end
|
|
517
|
+
|
|
518
|
+
def reading_paused?(io) # :nodoc:
|
|
519
|
+
@read_paused.key?(io)
|
|
520
|
+
end
|
|
521
|
+
|
|
522
|
+
# Internal and experimental, used by port forwarding: stops reading +io+
|
|
523
|
+
# but keeps flushing its pending output, then closes it and stops
|
|
524
|
+
# listening, as OpenSSH's chan_rcvd_oclose does.
|
|
525
|
+
def close_when_drained(io) # :nodoc:
|
|
526
|
+
pause_reading(io)
|
|
527
|
+
@draining[io] = true
|
|
528
|
+
close_if_drained(io)
|
|
529
|
+
end
|
|
530
|
+
|
|
491
531
|
# Returns a reference to the Net::SSH::Service::Forward service, which can
|
|
492
532
|
# be used for forwarding ports over SSH.
|
|
493
533
|
def forward
|
|
@@ -565,6 +605,13 @@ module Net
|
|
|
565
605
|
end
|
|
566
606
|
end
|
|
567
607
|
|
|
608
|
+
def close_if_drained(io)
|
|
609
|
+
return if io.pending_write? && !io.closed? && !closed? && listeners.key?(io)
|
|
610
|
+
|
|
611
|
+
io.close unless io.closed?
|
|
612
|
+
stop_listening_to(io)
|
|
613
|
+
end
|
|
614
|
+
|
|
568
615
|
def channel_closed(channel)
|
|
569
616
|
channel.remote_closed!
|
|
570
617
|
channel.close
|
data/lib/net/ssh/errors.rb
CHANGED
|
@@ -62,19 +62,21 @@ module Net
|
|
|
62
62
|
@data && @data[:fingerprint]
|
|
63
63
|
end
|
|
64
64
|
|
|
65
|
-
# Returns the host name
|
|
65
|
+
# Returns the host name that was connected to.
|
|
66
66
|
def host
|
|
67
|
-
|
|
67
|
+
session&.host
|
|
68
68
|
end
|
|
69
69
|
|
|
70
|
-
# Returns the port number
|
|
70
|
+
# Returns the port number that was connected to.
|
|
71
71
|
def port
|
|
72
|
-
|
|
72
|
+
session&.port
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
# Returns the IP address of the remote host, as reported by the socket
|
|
75
|
+
# Returns the IP address of the remote host, as reported by the socket,
|
|
76
|
+
# or nil when connected through a ProxyCommand.
|
|
76
77
|
def ip
|
|
77
|
-
|
|
78
|
+
peer_ip = session&.socket&.peer_ip
|
|
79
|
+
peer_ip unless peer_ip == Net::SSH::Transport::PacketStream::PROXY_COMMAND_HOST_IP
|
|
78
80
|
end
|
|
79
81
|
|
|
80
82
|
# Returns the key itself, as reported by the remote host.
|
|
@@ -87,6 +89,12 @@ module Net
|
|
|
87
89
|
def remember_host!
|
|
88
90
|
@callback.call
|
|
89
91
|
end
|
|
92
|
+
|
|
93
|
+
private
|
|
94
|
+
|
|
95
|
+
def session
|
|
96
|
+
@data && @data[:session]
|
|
97
|
+
end
|
|
90
98
|
end
|
|
91
99
|
|
|
92
100
|
# Raised when the cached key for a particular host does not match the
|
data/lib/net/ssh/key_factory.rb
CHANGED
|
@@ -2,6 +2,7 @@ require 'net/ssh/transport/openssl'
|
|
|
2
2
|
require 'net/ssh/prompt'
|
|
3
3
|
|
|
4
4
|
require 'net/ssh/authentication/ed25519_loader'
|
|
5
|
+
require 'net/ssh/authentication/security_key'
|
|
5
6
|
|
|
6
7
|
module Net
|
|
7
8
|
module SSH
|
|
@@ -92,7 +93,7 @@ module Net
|
|
|
92
93
|
blob = nil
|
|
93
94
|
begin
|
|
94
95
|
blob = fields.shift
|
|
95
|
-
end while !blob.nil? &&
|
|
96
|
+
end while !blob.nil? && !public_key_type?(blob)
|
|
96
97
|
blob = fields.shift
|
|
97
98
|
|
|
98
99
|
raise Net::SSH::Exception, "public key at #{filename} is not valid" if blob.nil?
|
|
@@ -104,6 +105,11 @@ module Net
|
|
|
104
105
|
|
|
105
106
|
private
|
|
106
107
|
|
|
108
|
+
def public_key_type?(type)
|
|
109
|
+
/^(ssh-(rsa|dss|ed25519)|ecdsa-sha2-nistp\d+)(-cert-v01@openssh\.com)?$/.match?(type) ||
|
|
110
|
+
Net::SSH::Authentication::SecurityKey.understands?(type)
|
|
111
|
+
end
|
|
112
|
+
|
|
107
113
|
# rubocop:disable Style/Documentation, Lint/DuplicateMethods
|
|
108
114
|
class KeyType
|
|
109
115
|
def self.read(key_data, passphrase)
|
|
@@ -320,6 +320,11 @@ module Net
|
|
|
320
320
|
session.listen_to(client)
|
|
321
321
|
channel[:socket] = client
|
|
322
322
|
|
|
323
|
+
# Grant window only for data the local end has accepted, so a slow
|
|
324
|
+
# reader stalls the remote sender instead of filling our memory.
|
|
325
|
+
channel.grant_window_on_consume!
|
|
326
|
+
client.on_sent { |bytes| channel.consumed(bytes) }
|
|
327
|
+
|
|
323
328
|
channel.on_data do |ch, data|
|
|
324
329
|
debug { "data:#{data.length} on #{type} forwarded channel" }
|
|
325
330
|
ch[:socket].enqueue(data)
|
|
@@ -327,36 +332,36 @@ module Net
|
|
|
327
332
|
|
|
328
333
|
channel.on_eof do |ch|
|
|
329
334
|
debug { "eof #{type} on #{type} forwarded channel" }
|
|
330
|
-
|
|
331
|
-
ch[:socket].send_pending
|
|
332
|
-
ch[:socket].shutdown Socket::SHUT_WR
|
|
333
|
-
rescue IOError => e
|
|
334
|
-
if e.message =~ /closed/ then
|
|
335
|
-
debug { "epipe in on_eof => shallowing exception:#{e}" }
|
|
336
|
-
else
|
|
337
|
-
raise
|
|
338
|
-
end
|
|
339
|
-
rescue Errno::EPIPE => e
|
|
340
|
-
debug { "epipe in on_eof => shallowing exception:#{e}" }
|
|
341
|
-
rescue Errno::ENOTCONN => e
|
|
342
|
-
debug { "enotconn in on_eof => shallowing exception:#{e}" }
|
|
343
|
-
end
|
|
335
|
+
ch[:socket].shutdown_write_when_drained
|
|
344
336
|
end
|
|
345
337
|
|
|
346
338
|
channel.on_close do |ch|
|
|
347
339
|
debug { "closing #{type} forwarded channel" }
|
|
348
|
-
ch[:socket]
|
|
349
|
-
session.stop_listening_to(ch[:socket])
|
|
340
|
+
session.close_when_drained(ch[:socket])
|
|
350
341
|
end
|
|
351
342
|
|
|
352
343
|
channel.on_process do |ch|
|
|
353
|
-
|
|
344
|
+
socket = ch[:socket]
|
|
345
|
+
if socket.closed?
|
|
354
346
|
ch.info { "#{type} forwarded connection closed" }
|
|
347
|
+
session.stop_listening_to(socket)
|
|
355
348
|
ch.close
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
349
|
+
else
|
|
350
|
+
if socket.available > 0
|
|
351
|
+
data = socket.read_available(8192)
|
|
352
|
+
ch.debug { "read #{data.length} bytes from client, sending over #{type} forwarded connection" }
|
|
353
|
+
ch.send_data(data)
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
# Like OpenSSH's channel_pre_open, only read more once the remote
|
|
357
|
+
# window can take what is already queued; writes to the socket keep
|
|
358
|
+
# flowing. ch.output is still unsent here because Channel#process
|
|
359
|
+
# runs this block before enqueue_pending_output.
|
|
360
|
+
if ch.output.length + socket.available < ch.remote_window_size.to_i
|
|
361
|
+
session.resume_reading(socket)
|
|
362
|
+
else
|
|
363
|
+
session.pause_reading(socket)
|
|
364
|
+
end
|
|
360
365
|
end
|
|
361
366
|
end
|
|
362
367
|
end
|
|
@@ -8,6 +8,7 @@ require 'net/ssh/transport/kex'
|
|
|
8
8
|
require 'net/ssh/transport/kex/curve25519_sha256_loader'
|
|
9
9
|
require 'net/ssh/transport/server_version'
|
|
10
10
|
require 'net/ssh/authentication/ed25519_loader'
|
|
11
|
+
require 'net/ssh/authentication/security_key'
|
|
11
12
|
|
|
12
13
|
module Net
|
|
13
14
|
module SSH
|
|
@@ -19,7 +20,7 @@ module Net
|
|
|
19
20
|
#
|
|
20
21
|
# You will never instantiate or reference this directly. It is used
|
|
21
22
|
# internally by the transport layer.
|
|
22
|
-
class Algorithms
|
|
23
|
+
class Algorithms # rubocop:disable Metrics/ClassLength
|
|
23
24
|
include Loggable
|
|
24
25
|
include Constants
|
|
25
26
|
|
|
@@ -31,11 +32,8 @@ module Net
|
|
|
31
32
|
ecdsa-sha2-nistp521
|
|
32
33
|
ecdsa-sha2-nistp384
|
|
33
34
|
ecdsa-sha2-nistp256
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
ssh-rsa
|
|
37
|
-
rsa-sha2-256
|
|
38
|
-
rsa-sha2-512],
|
|
35
|
+
rsa-sha2-512
|
|
36
|
+
rsa-sha2-256],
|
|
39
37
|
|
|
40
38
|
kex: %w[ecdh-sha2-nistp521
|
|
41
39
|
ecdh-sha2-nistp384
|
|
@@ -52,7 +50,16 @@ module Net
|
|
|
52
50
|
|
|
53
51
|
hmac: %w[hmac-sha2-512-etm@openssh.com hmac-sha2-256-etm@openssh.com
|
|
54
52
|
hmac-sha2-512 hmac-sha2-256
|
|
55
|
-
hmac-sha1]
|
|
53
|
+
hmac-sha1],
|
|
54
|
+
|
|
55
|
+
pubkey_algorithms: %w[ssh-ed25519-cert-v01@openssh.com
|
|
56
|
+
ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp384-cert-v01@openssh.com
|
|
57
|
+
ecdsa-sha2-nistp521-cert-v01@openssh.com
|
|
58
|
+
rsa-sha2-512-cert-v01@openssh.com rsa-sha2-256-cert-v01@openssh.com
|
|
59
|
+
ssh-ed25519
|
|
60
|
+
ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521] +
|
|
61
|
+
[Authentication::SecurityKey::SK_SSH_ED25519, Authentication::SecurityKey::SK_ECDSA_SHA2_NISTP256] +
|
|
62
|
+
%w[rsa-sha2-512 rsa-sha2-256]
|
|
56
63
|
}.freeze
|
|
57
64
|
|
|
58
65
|
if Net::SSH::Transport::ChaCha20Poly1305CipherLoader::LOADED
|
|
@@ -76,7 +83,8 @@ module Net
|
|
|
76
83
|
|
|
77
84
|
# Define all algorithms, with the deprecated, supported by Net::SSH.
|
|
78
85
|
ALGORITHMS = {
|
|
79
|
-
host_key: DEFAULT_ALGORITHMS[:host_key] +
|
|
86
|
+
host_key: DEFAULT_ALGORITHMS[:host_key] +
|
|
87
|
+
%w[ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com ssh-rsa ssh-dss],
|
|
80
88
|
|
|
81
89
|
kex: DEFAULT_ALGORITHMS[:kex] +
|
|
82
90
|
%w[diffie-hellman-group-exchange-sha1
|
|
@@ -98,6 +106,8 @@ module Net
|
|
|
98
106
|
hmac-md5 hmac-md5-96
|
|
99
107
|
none],
|
|
100
108
|
|
|
109
|
+
pubkey_algorithms: DEFAULT_ALGORITHMS[:pubkey_algorithms] + %w[ssh-rsa-cert-v01@openssh.com ssh-rsa ssh-dss],
|
|
110
|
+
|
|
101
111
|
compression: %w[none zlib@openssh.com zlib],
|
|
102
112
|
language: %w[]
|
|
103
113
|
}.freeze
|
|
@@ -269,7 +279,9 @@ module Net
|
|
|
269
279
|
|
|
270
280
|
ALGORITHMS.each do |algorithm, supported|
|
|
271
281
|
algorithms[algorithm] = compose_algorithm_list(
|
|
272
|
-
supported,
|
|
282
|
+
supported,
|
|
283
|
+
DEFAULT_ALGORITHMS[algorithm],
|
|
284
|
+
options[algorithm] || DEFAULT_ALGORITHMS[algorithm],
|
|
273
285
|
options[:append_all_supported_algorithms]
|
|
274
286
|
)
|
|
275
287
|
end
|
|
@@ -287,7 +299,11 @@ module Net
|
|
|
287
299
|
# existing known key for the host has preference.
|
|
288
300
|
|
|
289
301
|
existing_keys = session.host_keys
|
|
290
|
-
host_keys = existing_keys.flat_map { |key| key.respond_to?(:ssh_types) ? key.ssh_types : [key.ssh_type] }
|
|
302
|
+
host_keys = existing_keys.flat_map { |key| key.respond_to?(:ssh_types) ? key.ssh_types : [key.ssh_type] }
|
|
303
|
+
# A known ssh-rsa key also verifies with rsa-sha2-*, and must not re-enable disabled SHA-1 algorithms.
|
|
304
|
+
disabled_sha1 = %w[ssh-rsa ssh-rsa-cert-v01@openssh.com ssh-rsa-cert-v00@openssh.com] - algorithms[:host_key]
|
|
305
|
+
host_keys = host_keys.flat_map { |type| type == 'ssh-rsa' ? %w[rsa-sha2-512 rsa-sha2-256 ssh-rsa] : [type] }
|
|
306
|
+
host_keys = (host_keys - disabled_sha1).uniq
|
|
291
307
|
algorithms[:host_key].each do |name|
|
|
292
308
|
host_keys << name unless host_keys.include?(name)
|
|
293
309
|
end
|
|
@@ -296,21 +312,27 @@ module Net
|
|
|
296
312
|
end
|
|
297
313
|
|
|
298
314
|
# Composes the list of algorithms by taking supported algorithms and matching with supplied options.
|
|
299
|
-
def compose_algorithm_list(supported, option, append_all_supported_algorithms = false)
|
|
315
|
+
def compose_algorithm_list(supported, defaults, option, append_all_supported_algorithms = false)
|
|
300
316
|
return supported.dup unless option
|
|
301
317
|
|
|
302
318
|
list = []
|
|
303
319
|
option = Array(option).compact.uniq
|
|
304
320
|
|
|
305
|
-
if option.first && option.first.start_with?('+', '-')
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
321
|
+
if option.first && option.first.start_with?('+', '-', '^')
|
|
322
|
+
ops = { '^' => [], '+' => [], '-' => [] }
|
|
323
|
+
cur_list = nil
|
|
324
|
+
option.each do |cur_opt|
|
|
325
|
+
if ops.key?(cur_opt[0])
|
|
326
|
+
cur_list = ops[cur_opt[0]]
|
|
327
|
+
cur_opt = cur_opt[1..-1]
|
|
328
|
+
end
|
|
329
|
+
cur_list << cur_opt
|
|
330
|
+
end
|
|
310
331
|
|
|
311
|
-
list
|
|
332
|
+
list = ops['^'] + defaults + ops['+']
|
|
333
|
+
list.concat(supported) if append_all_supported_algorithms
|
|
312
334
|
|
|
313
|
-
|
|
335
|
+
ops['-'].each do |opt|
|
|
314
336
|
if opt.include?('*')
|
|
315
337
|
opt_escaped = Regexp.escape(opt)
|
|
316
338
|
algo_re = /\A#{opt_escaped.gsub('\*', '[A-Za-z\d\-@\.]*')}\z/
|
|
@@ -190,6 +190,7 @@ module Net
|
|
|
190
190
|
def cleanup
|
|
191
191
|
client.cleanup
|
|
192
192
|
server.cleanup
|
|
193
|
+
hang_up_proxy_command
|
|
193
194
|
end
|
|
194
195
|
|
|
195
196
|
# If the IO object requires a rekey operation (as indicated by either its
|
|
@@ -205,6 +206,17 @@ module Net
|
|
|
205
206
|
|
|
206
207
|
protected
|
|
207
208
|
|
|
209
|
+
# Like OpenSSH, hang up a ProxyCommand child: tunnels such as EC2 Instance
|
|
210
|
+
# Connect don't exit on their own, and IO#close waits for them.
|
|
211
|
+
def hang_up_proxy_command
|
|
212
|
+
return unless respond_to?(:pid)
|
|
213
|
+
|
|
214
|
+
child = pid
|
|
215
|
+
Process.kill('HUP', child) if child
|
|
216
|
+
rescue IOError, Errno::ESRCH
|
|
217
|
+
# already closed, or the child has already been reaped
|
|
218
|
+
end
|
|
219
|
+
|
|
208
220
|
# Called when this module is used to extend an object. It initializes
|
|
209
221
|
# the states and generally prepares the object for use as a packet stream.
|
|
210
222
|
def initialize_ssh
|
|
@@ -51,6 +51,9 @@ module Net
|
|
|
51
51
|
# implement them (Net::SSH::HostKeyEntries::CertAuthority). Non-certificate
|
|
52
52
|
# entries do not respond to these predicates and are left untouched.
|
|
53
53
|
def verify_certificate!(entry, host_keys, arguments)
|
|
54
|
+
# Plain keys carry no certificate constraints, and respond_to? on this delegator prints a deprecation warning (#1026).
|
|
55
|
+
return if entry.is_a?(Net::SSH::HostKeyEntries::PubKey)
|
|
56
|
+
|
|
54
57
|
cert = arguments[:key]
|
|
55
58
|
|
|
56
59
|
if entry.respond_to?(:matches_validity?) && !entry.matches_validity?(cert)
|
data/lib/net/ssh/version.rb
CHANGED
data/lib/net/ssh.rb
CHANGED
|
@@ -174,11 +174,13 @@ module Net
|
|
|
174
174
|
# * :properties => a hash of key/value pairs to add to the new connection's
|
|
175
175
|
# properties (see Net::SSH::Connection::Session#properties)
|
|
176
176
|
# * :proxy => a proxy instance (see Proxy) to use when connecting
|
|
177
|
-
# * :pubkey_algorithms => the public key authentication algorithms to
|
|
178
|
-
#
|
|
179
|
-
#
|
|
180
|
-
#
|
|
181
|
-
#
|
|
177
|
+
# * :pubkey_algorithms => the public key authentication algorithms to offer,
|
|
178
|
+
# in order of preference (e.g. %w[ssh-ed25519 rsa-sha2-512 rsa-sha2-256]).
|
|
179
|
+
# Keys whose type isn't listed are skipped. Like PubkeyAcceptedAlgorithms,
|
|
180
|
+
# entries starting with +, - or ^ modify the default list. ssh-rsa (SHA-1),
|
|
181
|
+
# ssh-rsa-cert-v01@openssh.com and ssh-dss are supported but not offered
|
|
182
|
+
# unless listed here (e.g. '+ssh-rsa') or :append_all_supported_algorithms
|
|
183
|
+
# is set.
|
|
182
184
|
# * :rekey_blocks_limit => the max number of blocks to process before rekeying
|
|
183
185
|
# * :rekey_limit => the max number of bytes to process before rekeying
|
|
184
186
|
# * :rekey_packet_limit => the max number of packets to process before rekeying
|
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.0.0.
|
|
4
|
+
version: 8.0.0.rc4
|
|
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-10-
|
|
35
|
+
date: 2026-10-11 00:00:00.000000000 Z
|
|
36
36
|
dependencies:
|
|
37
37
|
- !ruby/object:Gem::Dependency
|
|
38
38
|
name: logger
|
|
@@ -224,6 +224,7 @@ files:
|
|
|
224
224
|
- lib/net/ssh/authentication/methods/publickey.rb
|
|
225
225
|
- lib/net/ssh/authentication/pageant.rb
|
|
226
226
|
- lib/net/ssh/authentication/pub_key_fingerprint.rb
|
|
227
|
+
- lib/net/ssh/authentication/security_key.rb
|
|
227
228
|
- lib/net/ssh/authentication/session.rb
|
|
228
229
|
- lib/net/ssh/buffer.rb
|
|
229
230
|
- lib/net/ssh/buffered_io.rb
|
metadata.gz.sig
CHANGED
|
Binary file
|