net-ssh 2.7.0 → 7.3.0
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 +7 -0
- checksums.yaml.gz.sig +0 -0
- data/.dockerignore +6 -0
- data/.github/FUNDING.yml +1 -0
- data/.github/config/rubocop_linter_action.yml +4 -0
- data/.github/workflows/ci-with-docker.yml +44 -0
- data/.github/workflows/ci.yml +94 -0
- data/.github/workflows/rubocop.yml +16 -0
- data/.gitignore +15 -0
- data/.rubocop.yml +22 -0
- data/.rubocop_todo.yml +1081 -0
- data/CHANGES.txt +387 -0
- data/DEVELOPMENT.md +23 -0
- data/Dockerfile +29 -0
- data/Dockerfile.openssl3 +17 -0
- data/Gemfile +13 -0
- data/Gemfile.noed25519 +12 -0
- data/Gemfile.norbnacl +12 -0
- data/ISSUE_TEMPLATE.md +30 -0
- data/Manifest +4 -5
- data/README.md +303 -0
- data/Rakefile +174 -40
- data/SECURITY.md +4 -0
- data/THANKS.txt +25 -0
- data/appveyor.yml +58 -0
- data/docker-compose.yml +25 -0
- data/lib/net/ssh/authentication/agent.rb +279 -18
- data/lib/net/ssh/authentication/certificate.rb +183 -0
- data/lib/net/ssh/authentication/constants.rb +17 -15
- data/lib/net/ssh/authentication/ed25519.rb +184 -0
- data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
- data/lib/net/ssh/authentication/key_manager.rb +125 -54
- data/lib/net/ssh/authentication/methods/abstract.rb +67 -48
- data/lib/net/ssh/authentication/methods/hostbased.rb +34 -37
- data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +19 -12
- data/lib/net/ssh/authentication/methods/none.rb +16 -19
- data/lib/net/ssh/authentication/methods/password.rb +56 -19
- data/lib/net/ssh/authentication/methods/publickey.rb +96 -55
- data/lib/net/ssh/authentication/pageant.rb +483 -246
- data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
- data/lib/net/ssh/authentication/session.rb +138 -120
- data/lib/net/ssh/buffer.rb +399 -300
- data/lib/net/ssh/buffered_io.rb +154 -150
- data/lib/net/ssh/config.rb +361 -166
- data/lib/net/ssh/connection/channel.rb +640 -596
- data/lib/net/ssh/connection/constants.rb +29 -29
- data/lib/net/ssh/connection/event_loop.rb +123 -0
- data/lib/net/ssh/connection/keepalive.rb +59 -0
- data/lib/net/ssh/connection/session.rb +628 -548
- data/lib/net/ssh/connection/term.rb +125 -123
- data/lib/net/ssh/errors.rb +101 -95
- data/lib/net/ssh/key_factory.rb +198 -100
- data/lib/net/ssh/known_hosts.rb +221 -98
- data/lib/net/ssh/loggable.rb +50 -49
- data/lib/net/ssh/packet.rb +83 -79
- data/lib/net/ssh/prompt.rb +50 -81
- data/lib/net/ssh/proxy/command.rb +108 -60
- data/lib/net/ssh/proxy/errors.rb +12 -10
- data/lib/net/ssh/proxy/http.rb +82 -78
- data/lib/net/ssh/proxy/https.rb +50 -0
- data/lib/net/ssh/proxy/jump.rb +54 -0
- data/lib/net/ssh/proxy/socks4.rb +5 -8
- data/lib/net/ssh/proxy/socks5.rb +18 -20
- data/lib/net/ssh/service/forward.rb +383 -255
- data/lib/net/ssh/test/channel.rb +145 -136
- data/lib/net/ssh/test/extensions.rb +131 -110
- data/lib/net/ssh/test/kex.rb +34 -32
- data/lib/net/ssh/test/local_packet.rb +46 -44
- data/lib/net/ssh/test/packet.rb +89 -70
- data/lib/net/ssh/test/remote_packet.rb +32 -30
- data/lib/net/ssh/test/script.rb +156 -142
- data/lib/net/ssh/test/socket.rb +49 -48
- data/lib/net/ssh/test.rb +82 -77
- data/lib/net/ssh/transport/aes128_gcm.rb +40 -0
- data/lib/net/ssh/transport/aes256_gcm.rb +40 -0
- data/lib/net/ssh/transport/algorithms.rb +472 -348
- data/lib/net/ssh/transport/chacha20_poly1305_cipher.rb +117 -0
- data/lib/net/ssh/transport/chacha20_poly1305_cipher_loader.rb +17 -0
- data/lib/net/ssh/transport/cipher_factory.rb +124 -100
- data/lib/net/ssh/transport/constants.rb +32 -24
- data/lib/net/ssh/transport/ctr.rb +42 -22
- data/lib/net/ssh/transport/gcm_cipher.rb +207 -0
- data/lib/net/ssh/transport/hmac/abstract.rb +97 -63
- data/lib/net/ssh/transport/hmac/md5.rb +0 -2
- data/lib/net/ssh/transport/hmac/md5_96.rb +0 -2
- data/lib/net/ssh/transport/hmac/none.rb +0 -2
- data/lib/net/ssh/transport/hmac/ripemd160.rb +0 -2
- data/lib/net/ssh/transport/hmac/sha1.rb +0 -2
- data/lib/net/ssh/transport/hmac/sha1_96.rb +0 -2
- data/lib/net/ssh/transport/hmac/sha2_256.rb +7 -11
- data/lib/net/ssh/transport/hmac/sha2_256_96.rb +4 -8
- data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac/sha2_512.rb +6 -9
- data/lib/net/ssh/transport/hmac/sha2_512_96.rb +4 -8
- data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
- data/lib/net/ssh/transport/hmac.rb +14 -12
- data/lib/net/ssh/transport/identity_cipher.rb +54 -44
- data/lib/net/ssh/transport/kex/abstract.rb +130 -0
- data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
- data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +33 -40
- data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
- data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +119 -213
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +53 -61
- data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +5 -9
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +36 -90
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +18 -10
- data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +18 -10
- data/lib/net/ssh/transport/kex.rb +15 -12
- data/lib/net/ssh/transport/key_expander.rb +24 -20
- data/lib/net/ssh/transport/openssl.rb +161 -124
- data/lib/net/ssh/transport/openssl_cipher_extensions.rb +8 -0
- data/lib/net/ssh/transport/packet_stream.rb +246 -183
- data/lib/net/ssh/transport/server_version.rb +57 -51
- data/lib/net/ssh/transport/session.rb +307 -235
- data/lib/net/ssh/transport/state.rb +178 -176
- data/lib/net/ssh/verifiers/accept_new.rb +33 -0
- data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
- data/lib/net/ssh/verifiers/always.rb +58 -0
- data/lib/net/ssh/verifiers/never.rb +19 -0
- data/lib/net/ssh/version.rb +57 -51
- data/lib/net/ssh.rb +140 -40
- data/net-ssh-public_cert.pem +21 -0
- data/net-ssh.gemspec +39 -184
- data/support/ssh_tunnel_bug.rb +5 -5
- data.tar.gz.sig +0 -0
- metadata +205 -99
- metadata.gz.sig +0 -0
- data/README.rdoc +0 -219
- data/Rudyfile +0 -96
- data/gem-public_cert.pem +0 -20
- data/lib/net/ssh/authentication/agent/java_pageant.rb +0 -85
- data/lib/net/ssh/authentication/agent/socket.rb +0 -170
- data/lib/net/ssh/ruby_compat.rb +0 -51
- data/lib/net/ssh/verifiers/lenient.rb +0 -30
- data/lib/net/ssh/verifiers/null.rb +0 -12
- data/lib/net/ssh/verifiers/secure.rb +0 -54
- data/lib/net/ssh/verifiers/strict.rb +0 -24
- data/setup.rb +0 -1585
- data/support/arcfour_check.rb +0 -20
- data/test/README.txt +0 -47
- data/test/authentication/methods/common.rb +0 -28
- data/test/authentication/methods/test_abstract.rb +0 -51
- data/test/authentication/methods/test_hostbased.rb +0 -114
- data/test/authentication/methods/test_keyboard_interactive.rb +0 -100
- data/test/authentication/methods/test_none.rb +0 -41
- data/test/authentication/methods/test_password.rb +0 -52
- data/test/authentication/methods/test_publickey.rb +0 -148
- data/test/authentication/test_agent.rb +0 -205
- data/test/authentication/test_key_manager.rb +0 -218
- data/test/authentication/test_session.rb +0 -108
- data/test/common.rb +0 -108
- data/test/configs/eqsign +0 -3
- data/test/configs/exact_match +0 -8
- data/test/configs/host_plus +0 -10
- data/test/configs/multihost +0 -4
- data/test/configs/nohost +0 -19
- data/test/configs/numeric_host +0 -4
- data/test/configs/send_env +0 -2
- data/test/configs/substitutes +0 -8
- data/test/configs/wild_cards +0 -14
- data/test/connection/test_channel.rb +0 -467
- data/test/connection/test_session.rb +0 -526
- data/test/known_hosts/github +0 -1
- data/test/manual/test_forward.rb +0 -223
- data/test/start/test_options.rb +0 -36
- data/test/start/test_transport.rb +0 -28
- data/test/test_all.rb +0 -11
- data/test/test_buffer.rb +0 -433
- data/test/test_buffered_io.rb +0 -63
- data/test/test_config.rb +0 -151
- data/test/test_key_factory.rb +0 -173
- data/test/test_known_hosts.rb +0 -13
- data/test/transport/hmac/test_md5.rb +0 -41
- data/test/transport/hmac/test_md5_96.rb +0 -27
- data/test/transport/hmac/test_none.rb +0 -34
- data/test/transport/hmac/test_ripemd160.rb +0 -36
- data/test/transport/hmac/test_sha1.rb +0 -36
- data/test/transport/hmac/test_sha1_96.rb +0 -27
- data/test/transport/hmac/test_sha2_256.rb +0 -37
- data/test/transport/hmac/test_sha2_256_96.rb +0 -27
- data/test/transport/hmac/test_sha2_512.rb +0 -37
- data/test/transport/hmac/test_sha2_512_96.rb +0 -27
- data/test/transport/kex/test_diffie_hellman_group14_sha1.rb +0 -13
- data/test/transport/kex/test_diffie_hellman_group1_sha1.rb +0 -146
- data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +0 -92
- data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +0 -34
- data/test/transport/kex/test_ecdh_sha2_nistp256.rb +0 -161
- data/test/transport/kex/test_ecdh_sha2_nistp384.rb +0 -38
- data/test/transport/kex/test_ecdh_sha2_nistp521.rb +0 -38
- data/test/transport/test_algorithms.rb +0 -330
- data/test/transport/test_cipher_factory.rb +0 -443
- data/test/transport/test_hmac.rb +0 -34
- data/test/transport/test_identity_cipher.rb +0 -40
- data/test/transport/test_packet_stream.rb +0 -1755
- data/test/transport/test_server_version.rb +0 -78
- data/test/transport/test_session.rb +0 -319
- data/test/transport/test_state.rb +0 -181
@@ -1,238 +1,301 @@
|
|
1
1
|
require 'net/ssh/buffered_io'
|
2
2
|
require 'net/ssh/errors'
|
3
3
|
require 'net/ssh/packet'
|
4
|
-
require 'net/ssh/ruby_compat'
|
5
4
|
require 'net/ssh/transport/cipher_factory'
|
6
5
|
require 'net/ssh/transport/hmac'
|
7
6
|
require 'net/ssh/transport/state'
|
8
7
|
|
8
|
+
module Net
|
9
|
+
module SSH
|
10
|
+
module Transport
|
11
|
+
# A module that builds additional functionality onto the Net::SSH::BufferedIo
|
12
|
+
# module. It adds SSH encryption, compression, and packet validation, as
|
13
|
+
# per the SSH2 protocol. It also adds an abstraction for polling packets,
|
14
|
+
# to allow for both blocking and non-blocking reads.
|
15
|
+
module PacketStream # rubocop:disable Metrics/ModuleLength
|
16
|
+
PROXY_COMMAND_HOST_IP = '<no hostip for proxy command>'.freeze
|
9
17
|
|
10
|
-
|
18
|
+
include BufferedIo
|
11
19
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
# to allow for both blocking and non-blocking reads.
|
16
|
-
module PacketStream
|
17
|
-
include BufferedIo
|
18
|
-
|
19
|
-
def self.extended(object)
|
20
|
-
object.__send__(:initialize_ssh)
|
21
|
-
end
|
20
|
+
def self.extended(object)
|
21
|
+
object.__send__(:initialize_ssh)
|
22
|
+
end
|
22
23
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
begin
|
43
|
-
Socket.getnameinfo(sockaddr, Socket::NI_NAMEREQD).first
|
44
|
-
rescue
|
45
|
-
begin
|
46
|
-
Socket.getnameinfo(sockaddr).first
|
47
|
-
rescue
|
24
|
+
# The map of "hints" that can be used to modify the behavior of the packet
|
25
|
+
# stream. For instance, when authentication succeeds, an "authenticated"
|
26
|
+
# hint is set, which is used to determine whether or not to compress the
|
27
|
+
# data when using the "delayed" compression algorithm.
|
28
|
+
attr_reader :hints
|
29
|
+
|
30
|
+
# The server state object, which encapsulates the algorithms used to interpret
|
31
|
+
# packets coming from the server.
|
32
|
+
attr_reader :server
|
33
|
+
|
34
|
+
# The client state object, which encapsulates the algorithms used to build
|
35
|
+
# packets to send to the server.
|
36
|
+
attr_reader :client
|
37
|
+
|
38
|
+
# The name of the client (local) end of the socket, as reported by the
|
39
|
+
# socket.
|
40
|
+
def client_name
|
41
|
+
@client_name ||= begin
|
42
|
+
sockaddr = getsockname
|
48
43
|
begin
|
49
|
-
Socket.
|
50
|
-
rescue
|
51
|
-
|
52
|
-
|
44
|
+
Socket.getnameinfo(sockaddr, Socket::NI_NAMEREQD).first
|
45
|
+
rescue StandardError
|
46
|
+
begin
|
47
|
+
Socket.getnameinfo(sockaddr).first
|
48
|
+
rescue StandardError
|
49
|
+
begin
|
50
|
+
Socket.gethostbyname(Socket.gethostname).first
|
51
|
+
rescue StandardError
|
52
|
+
lwarn { "the client ipaddr/name could not be determined" }
|
53
|
+
"unknown"
|
54
|
+
end
|
55
|
+
end
|
53
56
|
end
|
54
57
|
end
|
55
58
|
end
|
56
|
-
end
|
57
|
-
end
|
58
59
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
60
|
+
# The IP address of the peer (remote) end of the socket, as reported by
|
61
|
+
# the socket.
|
62
|
+
def peer_ip
|
63
|
+
@peer_ip ||=
|
64
|
+
if respond_to?(:getpeername)
|
65
|
+
addr = getpeername
|
66
|
+
Socket.getnameinfo(addr, Socket::NI_NUMERICHOST | Socket::NI_NUMERICSERV).first
|
67
|
+
else
|
68
|
+
PROXY_COMMAND_HOST_IP
|
69
|
+
end
|
68
70
|
end
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
result && result.first.any?
|
75
|
-
end
|
76
|
-
|
77
|
-
# Returns the next full packet. If the mode parameter is :nonblock (the
|
78
|
-
# default), then this will return immediately, whether a packet is
|
79
|
-
# available or not, and will return nil if there is no packet ready to be
|
80
|
-
# returned. If the mode parameter is :block, then this method will block
|
81
|
-
# until a packet is available.
|
82
|
-
def next_packet(mode=:nonblock)
|
83
|
-
case mode
|
84
|
-
when :nonblock then
|
85
|
-
if available_for_read?
|
86
|
-
if fill <= 0
|
87
|
-
raise Net::SSH::Disconnect, "connection closed by remote host"
|
88
|
-
end
|
71
|
+
|
72
|
+
# Returns true if the IO is available for reading, and false otherwise.
|
73
|
+
def available_for_read?
|
74
|
+
result = IO.select([self], nil, nil, 0)
|
75
|
+
result && result.first.any?
|
89
76
|
end
|
90
|
-
poll_next_packet
|
91
77
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
78
|
+
# Returns the next full packet. If the mode parameter is :nonblock (the
|
79
|
+
# default), then this will return immediately, whether a packet is
|
80
|
+
# available or not, and will return nil if there is no packet ready to be
|
81
|
+
# returned. If the mode parameter is :block, then this method will block
|
82
|
+
# until a packet is available or timeout seconds have passed.
|
83
|
+
def next_packet(mode = :nonblock, timeout = nil)
|
84
|
+
case mode
|
85
|
+
when :nonblock then
|
86
|
+
packet = poll_next_packet
|
87
|
+
return packet if packet
|
88
|
+
|
89
|
+
if available_for_read?
|
90
|
+
if fill <= 0
|
91
|
+
result = poll_next_packet
|
92
|
+
if result.nil?
|
93
|
+
raise Net::SSH::Disconnect, "connection closed by remote host"
|
94
|
+
else
|
95
|
+
return result
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
poll_next_packet
|
96
100
|
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
+
when :block then
|
102
|
+
loop do
|
103
|
+
packet = poll_next_packet
|
104
|
+
return packet if packet
|
105
|
+
|
106
|
+
result = IO.select([self], nil, nil, timeout)
|
107
|
+
raise Net::SSH::ConnectionTimeout, "timeout waiting for next packet" unless result
|
108
|
+
raise Net::SSH::Disconnect, "connection closed by remote host" if fill <= 0
|
109
|
+
end
|
101
110
|
|
102
|
-
|
103
|
-
raise
|
111
|
+
else
|
112
|
+
raise ArgumentError, "expected :block or :nonblock, got #{mode.inspect}"
|
104
113
|
end
|
105
114
|
end
|
106
115
|
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
116
|
+
# Enqueues a packet to be sent, and blocks until the entire packet is
|
117
|
+
# sent.
|
118
|
+
def send_packet(payload)
|
119
|
+
enqueue_packet(payload)
|
120
|
+
wait_for_pending_sends
|
121
|
+
end
|
111
122
|
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
123
|
+
# Enqueues a packet to be sent, but does not immediately send the packet.
|
124
|
+
# The given payload is pre-processed according to the algorithms specified
|
125
|
+
# in the client state (compression, cipher, and hmac).
|
126
|
+
def enqueue_packet(payload) # rubocop:disable Metrics/AbcSize
|
127
|
+
# try to compress the packet
|
128
|
+
payload = client.compress(payload)
|
118
129
|
|
119
|
-
|
120
|
-
|
121
|
-
# in the client state (compression, cipher, and hmac).
|
122
|
-
def enqueue_packet(payload)
|
123
|
-
# try to compress the packet
|
124
|
-
payload = client.compress(payload)
|
130
|
+
# the length of the packet, minus the padding
|
131
|
+
actual_length = (client.hmac.etm || client.hmac.aead ? 0 : 4) + payload.bytesize + 1
|
125
132
|
|
126
|
-
|
127
|
-
|
133
|
+
# compute the padding length
|
134
|
+
padding_length = client.block_size - (actual_length % client.block_size)
|
135
|
+
padding_length += client.block_size if padding_length < 4
|
128
136
|
|
129
|
-
|
130
|
-
|
131
|
-
padding_length += client.block_size if padding_length < 4
|
137
|
+
# compute the packet length (sans the length field itself)
|
138
|
+
packet_length = payload.bytesize + padding_length + 1
|
132
139
|
|
133
|
-
|
134
|
-
|
140
|
+
if packet_length < 16
|
141
|
+
padding_length += client.block_size
|
142
|
+
packet_length = payload.bytesize + padding_length + 1
|
143
|
+
end
|
135
144
|
|
136
|
-
|
137
|
-
padding_length += client.block_size
|
138
|
-
packet_length = payload.bytesize + padding_length + 1
|
139
|
-
end
|
145
|
+
padding = Array.new(padding_length) { rand(256) }.pack("C*")
|
140
146
|
|
141
|
-
|
147
|
+
if client.cipher.implicit_mac?
|
148
|
+
unencrypted_data = [padding_length, payload, padding].pack("CA*A*")
|
149
|
+
message = client.cipher.update_cipher_mac(unencrypted_data, client.sequence_number)
|
150
|
+
elsif client.hmac.etm
|
151
|
+
debug { "using encrypt-then-mac" }
|
142
152
|
|
143
|
-
|
144
|
-
|
153
|
+
# Encrypt padding_length, payload, and padding. Take MAC
|
154
|
+
# from the unencrypted packet_length and the encrypted
|
155
|
+
# data.
|
156
|
+
length_data = [packet_length].pack("N")
|
145
157
|
|
146
|
-
|
147
|
-
message = encrypted_data + mac
|
158
|
+
unencrypted_data = [padding_length, payload, padding].pack("CA*A*")
|
148
159
|
|
149
|
-
|
150
|
-
enqueue(message)
|
160
|
+
encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
|
151
161
|
|
152
|
-
|
162
|
+
mac_data = length_data + encrypted_data
|
153
163
|
|
154
|
-
|
155
|
-
end
|
164
|
+
mac = client.hmac.digest([client.sequence_number, mac_data].pack("NA*"))
|
156
165
|
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
client.cleanup
|
161
|
-
server.cleanup
|
162
|
-
end
|
166
|
+
message = mac_data + mac
|
167
|
+
else
|
168
|
+
unencrypted_data = [packet_length, padding_length, payload, padding].pack("NCA*A*")
|
163
169
|
|
164
|
-
|
165
|
-
# client or server state objects, see State#needs_rekey?), this will
|
166
|
-
# yield. Otherwise, this does nothing.
|
167
|
-
def if_needs_rekey?
|
168
|
-
if client.needs_rekey? || server.needs_rekey?
|
169
|
-
yield
|
170
|
-
client.reset! if client.needs_rekey?
|
171
|
-
server.reset! if server.needs_rekey?
|
172
|
-
end
|
173
|
-
end
|
170
|
+
mac = client.hmac.digest([client.sequence_number, unencrypted_data].pack("NA*"))
|
174
171
|
|
175
|
-
|
172
|
+
encrypted_data = client.update_cipher(unencrypted_data) << client.final_cipher
|
176
173
|
|
177
|
-
|
178
|
-
|
179
|
-
def initialize_ssh
|
180
|
-
@hints = {}
|
181
|
-
@server = State.new(self, :server)
|
182
|
-
@client = State.new(self, :client)
|
183
|
-
@packet = nil
|
184
|
-
initialize_buffered_io
|
185
|
-
end
|
174
|
+
message = encrypted_data + mac
|
175
|
+
end
|
186
176
|
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
if @packet.nil?
|
194
|
-
minimum = server.block_size < 4 ? 4 : server.block_size
|
195
|
-
return nil if available < minimum
|
196
|
-
data = read_available(minimum)
|
197
|
-
|
198
|
-
# decipher it
|
199
|
-
@packet = Net::SSH::Buffer.new(server.update_cipher(data))
|
200
|
-
@packet_length = @packet.read_long
|
177
|
+
debug { "queueing packet nr #{client.sequence_number} type #{payload.getbyte(0)} len #{packet_length}" }
|
178
|
+
enqueue(message)
|
179
|
+
|
180
|
+
client.increment(packet_length)
|
181
|
+
|
182
|
+
self
|
201
183
|
end
|
202
184
|
|
203
|
-
|
204
|
-
|
185
|
+
# Performs any pending cleanup necessary on the IO and its associated
|
186
|
+
# state objects. (See State#cleanup).
|
187
|
+
def cleanup
|
188
|
+
client.cleanup
|
189
|
+
server.cleanup
|
190
|
+
end
|
191
|
+
|
192
|
+
# If the IO object requires a rekey operation (as indicated by either its
|
193
|
+
# client or server state objects, see State#needs_rekey?), this will
|
194
|
+
# yield. Otherwise, this does nothing.
|
195
|
+
def if_needs_rekey?
|
196
|
+
if client.needs_rekey? || server.needs_rekey?
|
197
|
+
yield
|
198
|
+
client.reset! if client.needs_rekey?
|
199
|
+
server.reset! if server.needs_rekey?
|
200
|
+
end
|
201
|
+
end
|
205
202
|
|
206
|
-
|
203
|
+
protected
|
207
204
|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
@
|
205
|
+
# Called when this module is used to extend an object. It initializes
|
206
|
+
# the states and generally prepares the object for use as a packet stream.
|
207
|
+
def initialize_ssh
|
208
|
+
@hints = {}
|
209
|
+
@server = State.new(self, :server)
|
210
|
+
@client = State.new(self, :client)
|
211
|
+
@packet = nil
|
212
|
+
initialize_buffered_io
|
212
213
|
end
|
213
214
|
|
214
|
-
#
|
215
|
-
#
|
216
|
-
|
215
|
+
# Tries to read the next packet. If there is insufficient data to read
|
216
|
+
# an entire packet, this returns immediately, otherwise the packet is
|
217
|
+
# read, post-processed according to the cipher, hmac, and compression
|
218
|
+
# algorithms specified in the server state object, and returned as a
|
219
|
+
# new Packet object.
|
220
|
+
# rubocop:disable Metrics/AbcSize
|
221
|
+
def poll_next_packet
|
222
|
+
aad_length = server.hmac.etm || server.hmac.aead ? 4 : 0
|
223
|
+
|
224
|
+
if @packet.nil?
|
225
|
+
minimum = server.block_size < 4 ? 4 : server.block_size
|
226
|
+
return nil if available < minimum + aad_length
|
227
|
+
|
228
|
+
data = read_available(minimum + aad_length)
|
229
|
+
|
230
|
+
# decipher it
|
231
|
+
if server.cipher.implicit_mac?
|
232
|
+
@packet_length = server.cipher.read_length(data[0...4], server.sequence_number)
|
233
|
+
@packet = Net::SSH::Buffer.new
|
234
|
+
@mac_data = data
|
235
|
+
elsif server.hmac.etm
|
236
|
+
@packet_length = data.unpack("N").first
|
237
|
+
@mac_data = data
|
238
|
+
@packet = Net::SSH::Buffer.new(server.update_cipher(data[aad_length..-1]))
|
239
|
+
else
|
240
|
+
@packet = Net::SSH::Buffer.new(server.update_cipher(data))
|
241
|
+
@packet_length = @packet.read_long
|
242
|
+
end
|
243
|
+
end
|
217
244
|
|
218
|
-
|
219
|
-
|
245
|
+
need = @packet_length + 4 - aad_length - server.block_size
|
246
|
+
raise Net::SSH::Exception, "padding error, need #{need} block #{server.block_size}" if need % server.block_size != 0
|
220
247
|
|
221
|
-
|
248
|
+
if server.cipher.implicit_mac?
|
249
|
+
return nil if available < need + server.cipher.mac_length
|
250
|
+
else
|
251
|
+
return nil if available < need + server.hmac.mac_length # rubocop:disable Style/IfInsideElse
|
252
|
+
end
|
222
253
|
|
223
|
-
|
224
|
-
|
254
|
+
if need > 0
|
255
|
+
# read the remainder of the packet and decrypt it.
|
256
|
+
data = read_available(need)
|
257
|
+
@mac_data += data if server.hmac.etm || server.cipher.implicit_mac?
|
258
|
+
unless server.cipher.implicit_mac?
|
259
|
+
@packet.append(
|
260
|
+
server.update_cipher(data)
|
261
|
+
)
|
262
|
+
end
|
263
|
+
end
|
225
264
|
|
226
|
-
|
227
|
-
|
265
|
+
if server.cipher.implicit_mac?
|
266
|
+
real_hmac = read_available(server.cipher.mac_length) || ""
|
267
|
+
@packet = Net::SSH::Buffer.new(server.cipher.read_and_mac(@mac_data, real_hmac, server.sequence_number))
|
268
|
+
padding_length = @packet.read_byte
|
269
|
+
payload = @packet.read(@packet_length - padding_length - 1)
|
270
|
+
else
|
271
|
+
# get the hmac from the tail of the packet (if one exists), and
|
272
|
+
# then validate it.
|
273
|
+
real_hmac = read_available(server.hmac.mac_length) || ""
|
274
|
+
|
275
|
+
@packet.append(server.final_cipher)
|
276
|
+
padding_length = @packet.read_byte
|
277
|
+
|
278
|
+
payload = @packet.read(@packet_length - padding_length - 1)
|
279
|
+
|
280
|
+
my_computed_hmac = if server.hmac.etm
|
281
|
+
server.hmac.digest([server.sequence_number, @mac_data].pack("NA*"))
|
282
|
+
else
|
283
|
+
server.hmac.digest([server.sequence_number, @packet.content].pack("NA*"))
|
284
|
+
end
|
285
|
+
raise Net::SSH::Exception, "corrupted hmac detected #{server.hmac.class}" if real_hmac != my_computed_hmac
|
286
|
+
end
|
287
|
+
# try to decompress the payload, in case compression is active
|
288
|
+
payload = server.decompress(payload)
|
228
289
|
|
229
|
-
|
290
|
+
debug { "received packet nr #{server.sequence_number} type #{payload.getbyte(0)} len #{@packet_length}" }
|
230
291
|
|
231
|
-
|
232
|
-
|
292
|
+
server.increment(@packet_length)
|
293
|
+
@packet = nil
|
233
294
|
|
234
|
-
|
295
|
+
return Packet.new(payload)
|
296
|
+
end
|
235
297
|
end
|
298
|
+
# rubocop:enable Metrics/AbcSize
|
299
|
+
end
|
236
300
|
end
|
237
|
-
|
238
|
-
end; end; end
|
301
|
+
end
|
@@ -2,70 +2,76 @@ require 'net/ssh/errors'
|
|
2
2
|
require 'net/ssh/loggable'
|
3
3
|
require 'net/ssh/version'
|
4
4
|
|
5
|
-
module Net
|
5
|
+
module Net
|
6
|
+
module SSH
|
7
|
+
module Transport
|
8
|
+
# Negotiates the SSH protocol version and trades information about server
|
9
|
+
# and client. This is never used directly--it is always called by the
|
10
|
+
# transport layer as part of the initialization process of the transport
|
11
|
+
# layer.
|
12
|
+
#
|
13
|
+
# Note that this class also encapsulates the negotiated version, and acts as
|
14
|
+
# the authoritative reference for any queries regarding the version in effect.
|
15
|
+
class ServerVersion
|
16
|
+
include Loggable
|
6
17
|
|
7
|
-
|
8
|
-
|
9
|
-
# transport layer as part of the initialization process of the transport
|
10
|
-
# layer.
|
11
|
-
#
|
12
|
-
# Note that this class also encapsulates the negotiated version, and acts as
|
13
|
-
# the authoritative reference for any queries regarding the version in effect.
|
14
|
-
class ServerVersion
|
15
|
-
include Loggable
|
18
|
+
# The SSH version string as reported by Net::SSH
|
19
|
+
PROTO_VERSION = "SSH-2.0-Ruby/Net::SSH_#{Net::SSH::Version::CURRENT} #{RUBY_PLATFORM}"
|
16
20
|
|
17
|
-
|
18
|
-
|
21
|
+
# Any header text sent by the server prior to sending the version.
|
22
|
+
attr_reader :header
|
19
23
|
|
20
|
-
|
21
|
-
|
24
|
+
# The version string reported by the server.
|
25
|
+
attr_reader :version
|
22
26
|
|
23
|
-
|
24
|
-
|
27
|
+
# Instantiates a new ServerVersion and immediately (and synchronously)
|
28
|
+
# negotiates the SSH protocol in effect, using the given socket.
|
29
|
+
def initialize(socket, logger, timeout = nil)
|
30
|
+
@header = String.new
|
31
|
+
@version = nil
|
32
|
+
@logger = logger
|
33
|
+
negotiate!(socket, timeout)
|
34
|
+
end
|
25
35
|
|
26
|
-
|
27
|
-
# negotiates the SSH protocol in effect, using the given socket.
|
28
|
-
def initialize(socket, logger)
|
29
|
-
@header = ""
|
30
|
-
@version = nil
|
31
|
-
@logger = logger
|
32
|
-
negotiate!(socket)
|
33
|
-
end
|
36
|
+
private
|
34
37
|
|
35
|
-
|
38
|
+
# Negotiates the SSH protocol to use, via the given socket. If the server
|
39
|
+
# reports an incompatible SSH version (e.g., SSH1), this will raise an
|
40
|
+
# exception.
|
41
|
+
def negotiate!(socket, timeout)
|
42
|
+
info { "negotiating protocol version" }
|
36
43
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
44
|
+
debug { "local is `#{PROTO_VERSION}'" }
|
45
|
+
socket.write "#{PROTO_VERSION}\r\n"
|
46
|
+
socket.flush
|
47
|
+
|
48
|
+
raise Net::SSH::ConnectionTimeout, "timeout during server version negotiating" if timeout && !IO.select([socket], nil, nil, timeout)
|
42
49
|
|
43
|
-
loop do
|
44
|
-
@version = ""
|
45
50
|
loop do
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
+
@version = String.new
|
52
|
+
loop do
|
53
|
+
begin
|
54
|
+
b = socket.readpartial(1)
|
55
|
+
raise Net::SSH::Disconnect, "connection closed by remote host" if b.nil?
|
56
|
+
rescue EOFError
|
57
|
+
raise Net::SSH::Disconnect, "connection closed by remote host"
|
58
|
+
end
|
59
|
+
@version << b
|
60
|
+
break if b == "\n"
|
51
61
|
end
|
52
|
-
@version
|
53
|
-
|
62
|
+
break if @version.match(/^SSH-/)
|
63
|
+
|
64
|
+
@header << @version
|
54
65
|
end
|
55
|
-
break if @version.match(/^SSH-/)
|
56
|
-
@header << @version
|
57
|
-
end
|
58
66
|
|
59
|
-
|
60
|
-
|
67
|
+
@version.chomp!
|
68
|
+
debug { "remote is `#{@version}'" }
|
61
69
|
|
62
|
-
|
63
|
-
raise Net::SSH::Exception, "incompatible SSH version `#{@version}'"
|
64
|
-
end
|
70
|
+
raise Net::SSH::Exception, "incompatible SSH version `#{@version}'" unless @version.match(/^SSH-(1\.99|2\.0)-/)
|
65
71
|
|
66
|
-
|
67
|
-
|
68
|
-
socket.flush
|
72
|
+
raise Net::SSH::ConnectionTimeout, "timeout during client version negotiating" if timeout && !IO.select(nil, [socket], nil, timeout)
|
73
|
+
end
|
69
74
|
end
|
75
|
+
end
|
70
76
|
end
|
71
|
-
end
|
77
|
+
end
|