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
@@ -0,0 +1,184 @@
|
|
1
|
+
gem 'ed25519', '~> 1.2'
|
2
|
+
gem 'bcrypt_pbkdf', '~> 1.0' unless RUBY_PLATFORM == "java"
|
3
|
+
|
4
|
+
require 'ed25519'
|
5
|
+
|
6
|
+
require 'net/ssh/transport/cipher_factory'
|
7
|
+
require 'net/ssh/authentication/pub_key_fingerprint'
|
8
|
+
require 'bcrypt_pbkdf' unless RUBY_PLATFORM == "java"
|
9
|
+
|
10
|
+
module Net
|
11
|
+
module SSH
|
12
|
+
module Authentication
|
13
|
+
module ED25519
|
14
|
+
class SigningKeyFromFile < SimpleDelegator
|
15
|
+
def initialize(pk, sk)
|
16
|
+
key = ::Ed25519::SigningKey.from_keypair(sk)
|
17
|
+
raise ArgumentError, "pk does not match sk" unless pk == key.verify_key.to_bytes
|
18
|
+
|
19
|
+
super(key)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
class OpenSSHPrivateKeyLoader
|
24
|
+
CipherFactory = Net::SSH::Transport::CipherFactory
|
25
|
+
|
26
|
+
MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
|
27
|
+
MEND = "-----END OPENSSH PRIVATE KEY-----"
|
28
|
+
MAGIC = "openssh-key-v1"
|
29
|
+
|
30
|
+
class DecryptError < ArgumentError
|
31
|
+
def initialize(message, encrypted_key: false)
|
32
|
+
super(message)
|
33
|
+
@encrypted_key = encrypted_key
|
34
|
+
end
|
35
|
+
|
36
|
+
def encrypted_key?
|
37
|
+
return @encrypted_key
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def self.read(datafull, password)
|
42
|
+
datafull = datafull.strip
|
43
|
+
raise ArgumentError.new("Expected #{MBEGIN} at start of private key") unless datafull.start_with?(MBEGIN)
|
44
|
+
raise ArgumentError.new("Expected #{MEND} at end of private key") unless datafull.end_with?(MEND)
|
45
|
+
|
46
|
+
datab64 = datafull[MBEGIN.size...-MEND.size]
|
47
|
+
data = datab64.unpack1("m")
|
48
|
+
raise ArgumentError.new("Expected #{MAGIC} at start of decoded private key") unless data.start_with?(MAGIC)
|
49
|
+
|
50
|
+
buffer = Net::SSH::Buffer.new(data[MAGIC.size + 1..-1])
|
51
|
+
|
52
|
+
ciphername = buffer.read_string
|
53
|
+
raise ArgumentError.new("#{ciphername} in private key is not supported") unless
|
54
|
+
CipherFactory.supported?(ciphername)
|
55
|
+
|
56
|
+
kdfname = buffer.read_string
|
57
|
+
raise ArgumentError.new("Expected #{kdfname} to be or none or bcrypt") unless %w[none bcrypt].include?(kdfname)
|
58
|
+
|
59
|
+
kdfopts = Net::SSH::Buffer.new(buffer.read_string)
|
60
|
+
num_keys = buffer.read_long
|
61
|
+
raise ArgumentError.new("Only 1 key is supported in ssh keys #{num_keys} was in private key") unless num_keys == 1
|
62
|
+
|
63
|
+
_pubkey = buffer.read_string
|
64
|
+
|
65
|
+
len = buffer.read_long
|
66
|
+
|
67
|
+
keylen, blocksize, ivlen = CipherFactory.get_lengths(ciphername, iv_len: true)
|
68
|
+
raise ArgumentError.new("Private key len:#{len} is not a multiple of #{blocksize}") if
|
69
|
+
((len < blocksize) || ((blocksize > 0) && (len % blocksize) != 0))
|
70
|
+
|
71
|
+
if kdfname == 'bcrypt'
|
72
|
+
salt = kdfopts.read_string
|
73
|
+
rounds = kdfopts.read_long
|
74
|
+
|
75
|
+
raise "BCryptPbkdf is not implemented for jruby" if RUBY_PLATFORM == "java"
|
76
|
+
|
77
|
+
key = BCryptPbkdf::key(password, salt, keylen + ivlen, rounds)
|
78
|
+
raise DecryptError.new("BCyryptPbkdf failed", encrypted_key: true) unless key
|
79
|
+
else
|
80
|
+
key = '\x00' * (keylen + ivlen)
|
81
|
+
end
|
82
|
+
|
83
|
+
cipher = CipherFactory.get(ciphername, key: key[0...keylen], iv: key[keylen...keylen + ivlen], decrypt: true)
|
84
|
+
|
85
|
+
decoded = cipher.update(buffer.remainder_as_buffer.to_s)
|
86
|
+
decoded << cipher.final
|
87
|
+
|
88
|
+
decoded = Net::SSH::Buffer.new(decoded)
|
89
|
+
check1 = decoded.read_long
|
90
|
+
check2 = decoded.read_long
|
91
|
+
|
92
|
+
raise DecryptError.new("Decrypt failed on private key", encrypted_key: kdfname == 'bcrypt') if (check1 != check2)
|
93
|
+
|
94
|
+
type_name = decoded.read_string
|
95
|
+
case type_name
|
96
|
+
when "ssh-ed25519"
|
97
|
+
PrivKey.new(decoded)
|
98
|
+
else
|
99
|
+
decoded.read_private_keyblob(type_name)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
class PubKey
|
105
|
+
include Net::SSH::Authentication::PubKeyFingerprint
|
106
|
+
|
107
|
+
attr_reader :verify_key
|
108
|
+
|
109
|
+
def initialize(data)
|
110
|
+
@verify_key = ::Ed25519::VerifyKey.new(data)
|
111
|
+
end
|
112
|
+
|
113
|
+
def self.read_keyblob(buffer)
|
114
|
+
PubKey.new(buffer.read_string)
|
115
|
+
end
|
116
|
+
|
117
|
+
def to_blob
|
118
|
+
Net::SSH::Buffer.from(:mstring, "ssh-ed25519".dup, :string, @verify_key.to_bytes).to_s
|
119
|
+
end
|
120
|
+
|
121
|
+
def ssh_type
|
122
|
+
"ssh-ed25519"
|
123
|
+
end
|
124
|
+
|
125
|
+
def ssh_signature_type
|
126
|
+
ssh_type
|
127
|
+
end
|
128
|
+
|
129
|
+
def ssh_do_verify(sig, data, options = {})
|
130
|
+
@verify_key.verify(sig, data)
|
131
|
+
end
|
132
|
+
|
133
|
+
def to_pem
|
134
|
+
# TODO this is not pem
|
135
|
+
ssh_type + [@verify_key.to_bytes].pack("m")
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
class PrivKey
|
140
|
+
CipherFactory = Net::SSH::Transport::CipherFactory
|
141
|
+
|
142
|
+
MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
|
143
|
+
MEND = "-----END OPENSSH PRIVATE KEY-----\n"
|
144
|
+
MAGIC = "openssh-key-v1"
|
145
|
+
|
146
|
+
attr_reader :sign_key
|
147
|
+
|
148
|
+
def initialize(buffer)
|
149
|
+
pk = buffer.read_string
|
150
|
+
sk = buffer.read_string
|
151
|
+
_comment = buffer.read_string
|
152
|
+
|
153
|
+
@pk = pk
|
154
|
+
@sign_key = SigningKeyFromFile.new(pk, sk)
|
155
|
+
end
|
156
|
+
|
157
|
+
def to_blob
|
158
|
+
public_key.to_blob
|
159
|
+
end
|
160
|
+
|
161
|
+
def ssh_type
|
162
|
+
"ssh-ed25519"
|
163
|
+
end
|
164
|
+
|
165
|
+
def ssh_signature_type
|
166
|
+
ssh_type
|
167
|
+
end
|
168
|
+
|
169
|
+
def public_key
|
170
|
+
PubKey.new(@pk)
|
171
|
+
end
|
172
|
+
|
173
|
+
def ssh_do_sign(data, sig_alg = nil)
|
174
|
+
@sign_key.sign(data)
|
175
|
+
end
|
176
|
+
|
177
|
+
def self.read(data, password)
|
178
|
+
OpenSSHPrivateKeyLoader.read(data, password)
|
179
|
+
end
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
183
|
+
end
|
184
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Net
|
2
|
+
module SSH
|
3
|
+
module Authentication
|
4
|
+
# Loads ED25519 support which requires optinal dependecies like
|
5
|
+
# ed25519, bcrypt_pbkdf
|
6
|
+
module ED25519Loader
|
7
|
+
begin
|
8
|
+
require 'net/ssh/authentication/ed25519'
|
9
|
+
LOADED = true
|
10
|
+
ERROR = nil
|
11
|
+
rescue LoadError => e
|
12
|
+
ERROR = e
|
13
|
+
LOADED = false
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.raiseUnlessLoaded(message)
|
17
|
+
description = ERROR.is_a?(LoadError) ? dependenciesRequiredForED25519 : ''
|
18
|
+
description << "#{ERROR.class} : \"#{ERROR.message}\"\n" if ERROR
|
19
|
+
raise NotImplementedError, "#{message}\n#{description}" unless LOADED
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.dependenciesRequiredForED25519
|
23
|
+
result = "net-ssh requires the following gems for ed25519 support:\n"
|
24
|
+
result << " * ed25519 (>= 1.2, < 2.0)\n"
|
25
|
+
result << " * bcrypt_pbkdf (>= 1.0, < 2.0)\n" unless RUBY_PLATFORM == "java"
|
26
|
+
result << "See https://github.com/net-ssh/net-ssh/issues/565 for more information\n"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -6,13 +6,12 @@ require 'net/ssh/authentication/agent'
|
|
6
6
|
module Net
|
7
7
|
module SSH
|
8
8
|
module Authentication
|
9
|
-
|
10
9
|
# A trivial exception class used to report errors in the key manager.
|
11
10
|
class KeyManagerError < Net::SSH::Exception; end
|
12
11
|
|
13
12
|
# This class encapsulates all operations done by clients on a user's
|
14
13
|
# private keys. In practice, the client should never need a reference
|
15
|
-
# to a private key; instead, they grab a list of "identities" (public
|
14
|
+
# to a private key; instead, they grab a list of "identities" (public
|
16
15
|
# keys) that are available from the KeyManager, and then use
|
17
16
|
# the KeyManager to do various private key operations using those
|
18
17
|
# identities.
|
@@ -30,6 +29,12 @@ module Net
|
|
30
29
|
# The list of user key data that will be examined
|
31
30
|
attr_reader :key_data
|
32
31
|
|
32
|
+
# The list of user key certificate files that will be examined
|
33
|
+
attr_reader :keycert_files
|
34
|
+
|
35
|
+
# The list of user key certificate data that will be examined
|
36
|
+
attr_reader :keycert_data
|
37
|
+
|
33
38
|
# The map of loaded identities
|
34
39
|
attr_reader :known_identities
|
35
40
|
|
@@ -37,12 +42,15 @@ module Net
|
|
37
42
|
attr_reader :options
|
38
43
|
|
39
44
|
# Create a new KeyManager. By default, the manager will
|
40
|
-
# use the ssh-agent
|
41
|
-
|
45
|
+
# use the ssh-agent if it is running and the `:use_agent` option
|
46
|
+
# is not false.
|
47
|
+
def initialize(logger, options = {})
|
42
48
|
self.logger = logger
|
43
49
|
@key_files = []
|
44
50
|
@key_data = []
|
45
|
-
@
|
51
|
+
@keycert_files = []
|
52
|
+
@keycert_data = []
|
53
|
+
@use_agent = options[:use_agent] != false
|
46
54
|
@known_identities = {}
|
47
55
|
@agent = nil
|
48
56
|
@options = options
|
@@ -55,6 +63,7 @@ module Net
|
|
55
63
|
def clear!
|
56
64
|
key_files.clear
|
57
65
|
key_data.clear
|
66
|
+
keycert_data.clear
|
58
67
|
known_identities.clear
|
59
68
|
self
|
60
69
|
end
|
@@ -65,6 +74,18 @@ module Net
|
|
65
74
|
self
|
66
75
|
end
|
67
76
|
|
77
|
+
# Add the given keycert_file to the list of keycert files that will be used.
|
78
|
+
def add_keycert(keycert_file)
|
79
|
+
keycert_files.push(File.expand_path(keycert_file)).uniq!
|
80
|
+
self
|
81
|
+
end
|
82
|
+
|
83
|
+
# Add the given keycert_data to the list of keycerts that will be used.
|
84
|
+
def add_keycert_data(keycert_data_)
|
85
|
+
keycert_data.push(keycert_data_).uniq!
|
86
|
+
self
|
87
|
+
end
|
88
|
+
|
68
89
|
# Add the given key_file to the list of keys that will be used.
|
69
90
|
def add_key_data(key_data_)
|
70
91
|
key_data.push(key_data_).uniq!
|
@@ -97,7 +118,7 @@ module Net
|
|
97
118
|
def each_identity
|
98
119
|
prepared_identities = prepare_identities_from_files + prepare_identities_from_data
|
99
120
|
|
100
|
-
user_identities = load_identities(prepared_identities, false)
|
121
|
+
user_identities = load_identities(prepared_identities, false, true)
|
101
122
|
|
102
123
|
if agent
|
103
124
|
agent.identities.each do |key|
|
@@ -107,13 +128,13 @@ module Net
|
|
107
128
|
user_identities.delete(corresponding_user_identity) if corresponding_user_identity
|
108
129
|
|
109
130
|
if !options[:keys_only] || corresponding_user_identity
|
110
|
-
known_identities[key] = { :
|
131
|
+
known_identities[key] = { from: :agent, identity: key }
|
111
132
|
yield key
|
112
133
|
end
|
113
134
|
end
|
114
135
|
end
|
115
136
|
|
116
|
-
user_identities = load_identities(user_identities,
|
137
|
+
user_identities = load_identities(user_identities, !options[:non_interactive], false)
|
117
138
|
|
118
139
|
user_identities.each do |identity|
|
119
140
|
key = identity.delete(:public_key)
|
@@ -121,6 +142,21 @@ module Net
|
|
121
142
|
yield key
|
122
143
|
end
|
123
144
|
|
145
|
+
known_identity_blobs = known_identities.keys.map(&:to_blob)
|
146
|
+
|
147
|
+
keycerts.each do |keycert|
|
148
|
+
next if known_identity_blobs.include?(keycert.to_blob)
|
149
|
+
|
150
|
+
(_, corresponding_identity) = known_identities.detect { |public_key, _|
|
151
|
+
public_key.to_pem == keycert.to_pem
|
152
|
+
}
|
153
|
+
|
154
|
+
if corresponding_identity
|
155
|
+
known_identities[keycert] = corresponding_identity
|
156
|
+
yield keycert
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
124
160
|
self
|
125
161
|
end
|
126
162
|
|
@@ -133,25 +169,39 @@ module Net
|
|
133
169
|
# Regardless of the identity's origin or who does the signing, this
|
134
170
|
# will always return the signature in an SSH2-specified "signature
|
135
171
|
# blob" format.
|
136
|
-
def sign(identity, data)
|
172
|
+
def sign(identity, data, sig_alg = nil)
|
137
173
|
info = known_identities[identity] or raise KeyManagerError, "the given identity is unknown to the key manager"
|
138
174
|
|
139
175
|
if info[:key].nil? && info[:from] == :file
|
140
176
|
begin
|
141
|
-
info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase],
|
142
|
-
rescue
|
177
|
+
info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase], !options[:non_interactive], options[:password_prompt])
|
178
|
+
rescue OpenSSL::OpenSSLError, Exception => e
|
143
179
|
raise KeyManagerError, "the given identity is known, but the private key could not be loaded: #{e.class} (#{e.message})"
|
144
180
|
end
|
145
181
|
end
|
146
182
|
|
147
183
|
if info[:key]
|
148
|
-
|
149
|
-
|
184
|
+
if sig_alg.nil?
|
185
|
+
signed = info[:key].ssh_do_sign(data.to_s)
|
186
|
+
sig_alg = identity.ssh_signature_type
|
187
|
+
else
|
188
|
+
signed = info[:key].ssh_do_sign(data.to_s, sig_alg)
|
189
|
+
end
|
190
|
+
return Net::SSH::Buffer.from(:string, sig_alg,
|
191
|
+
:mstring, signed).to_s
|
150
192
|
end
|
151
193
|
|
152
194
|
if info[:from] == :agent
|
153
195
|
raise KeyManagerError, "the agent is no longer available" unless agent
|
154
|
-
|
196
|
+
|
197
|
+
case sig_alg
|
198
|
+
when "rsa-sha2-512"
|
199
|
+
return agent.sign(info[:identity], data.to_s, Net::SSH::Authentication::Agent::SSH_AGENT_RSA_SHA2_512)
|
200
|
+
when "rsa-sha2-256"
|
201
|
+
return agent.sign(info[:identity], data.to_s, Net::SSH::Authentication::Agent::SSH_AGENT_RSA_SHA2_256)
|
202
|
+
else
|
203
|
+
return agent.sign(info[:identity], data.to_s)
|
204
|
+
end
|
155
205
|
end
|
156
206
|
|
157
207
|
raise KeyManagerError, "[BUG] can't determine identity origin (#{info.inspect})"
|
@@ -175,81 +225,102 @@ module Net
|
|
175
225
|
# or if the agent is otherwise not available.
|
176
226
|
def agent
|
177
227
|
return unless use_agent?
|
178
|
-
|
228
|
+
|
229
|
+
@agent ||= Agent.connect(logger, options[:agent_socket_factory], options[:identity_agent])
|
179
230
|
rescue AgentNotAvailable
|
180
231
|
@use_agent = false
|
181
232
|
nil
|
182
233
|
end
|
183
234
|
|
235
|
+
def no_keys?
|
236
|
+
key_files.empty? && key_data.empty?
|
237
|
+
end
|
238
|
+
|
184
239
|
private
|
185
240
|
|
241
|
+
# Load keycerts from files and data.
|
242
|
+
def keycerts
|
243
|
+
keycert_files.map { |keycert_file| KeyFactory.load_public_key(keycert_file) } +
|
244
|
+
keycert_data.map { |data| KeyFactory.load_data_public_key(data) }
|
245
|
+
end
|
246
|
+
|
186
247
|
# Prepares identities from user key_files for loading, preserving their order and sources.
|
187
248
|
def prepare_identities_from_files
|
188
249
|
key_files.map do |file|
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
250
|
+
if readable_file?(file)
|
251
|
+
identity = {}
|
252
|
+
cert_file = file + "-cert.pub"
|
253
|
+
public_key_file = file + ".pub"
|
254
|
+
if readable_file?(cert_file)
|
255
|
+
identity[:load_from] = :pubkey_file
|
256
|
+
identity[:pubkey_file] = cert_file
|
257
|
+
elsif readable_file?(public_key_file)
|
258
|
+
identity[:load_from] = :pubkey_file
|
259
|
+
identity[:pubkey_file] = public_key_file
|
260
|
+
else
|
261
|
+
identity[:load_from] = :privkey_file
|
262
|
+
end
|
263
|
+
identity.merge(privkey_file: file)
|
194
264
|
end
|
195
265
|
end.compact
|
196
266
|
end
|
197
267
|
|
268
|
+
def readable_file?(path)
|
269
|
+
File.file?(path) && File.readable?(path)
|
270
|
+
end
|
271
|
+
|
198
272
|
# Prepared identities from user key_data, preserving their order and sources.
|
199
273
|
def prepare_identities_from_data
|
200
274
|
key_data.map do |data|
|
201
|
-
{ :
|
275
|
+
{ load_from: :data, data: data }
|
202
276
|
end
|
203
277
|
end
|
204
278
|
|
205
|
-
# Load prepared identities. Private key decryption errors ignored if
|
206
|
-
def load_identities(identities, ask_passphrase)
|
279
|
+
# Load prepared identities. Private key decryption errors ignored if ignore_decryption_errors
|
280
|
+
def load_identities(identities, ask_passphrase, ignore_decryption_errors)
|
207
281
|
identities.map do |identity|
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
end
|
232
|
-
rescue ArgumentError => e
|
233
|
-
process_identity_loading_error(identity, e)
|
234
|
-
nil
|
235
|
-
rescue Exception => e
|
282
|
+
case identity[:load_from]
|
283
|
+
when :pubkey_file
|
284
|
+
key = KeyFactory.load_public_key(identity[:pubkey_file])
|
285
|
+
{ public_key: key, from: :file, file: identity[:privkey_file] }
|
286
|
+
when :privkey_file
|
287
|
+
private_key = KeyFactory.load_private_key(
|
288
|
+
identity[:privkey_file], options[:passphrase], ask_passphrase, options[:password_prompt]
|
289
|
+
)
|
290
|
+
key = private_key.send(:public_key)
|
291
|
+
{ public_key: key, from: :file, file: identity[:privkey_file], key: private_key }
|
292
|
+
when :data
|
293
|
+
private_key = KeyFactory.load_data_private_key(
|
294
|
+
identity[:data], options[:passphrase], ask_passphrase, "<key in memory>", options[:password_prompt]
|
295
|
+
)
|
296
|
+
key = private_key.send(:public_key)
|
297
|
+
{ public_key: key, from: :key_data, data: identity[:data], key: private_key }
|
298
|
+
else
|
299
|
+
identity
|
300
|
+
end
|
301
|
+
rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError, OpenSSL::PKey::ECError, OpenSSL::PKey::PKeyError, ArgumentError => e
|
302
|
+
if ignore_decryption_errors
|
303
|
+
identity
|
304
|
+
else
|
236
305
|
process_identity_loading_error(identity, e)
|
237
306
|
nil
|
238
307
|
end
|
308
|
+
rescue Exception => e
|
309
|
+
process_identity_loading_error(identity, e)
|
310
|
+
nil
|
239
311
|
end.compact
|
240
312
|
end
|
241
313
|
|
242
314
|
def process_identity_loading_error(identity, e)
|
243
315
|
case identity[:load_from]
|
244
316
|
when :pubkey_file
|
245
|
-
error { "could not load public key file `#{identity[:
|
317
|
+
error { "could not load public key file `#{identity[:pubkey_file]}': #{e.class} (#{e.message})" }
|
246
318
|
when :privkey_file
|
247
|
-
error { "could not load private key file `#{identity[:
|
319
|
+
error { "could not load private key file `#{identity[:privkey_file]}': #{e.class} (#{e.message})" }
|
248
320
|
else
|
249
321
|
raise e
|
250
322
|
end
|
251
323
|
end
|
252
|
-
|
253
324
|
end
|
254
325
|
end
|
255
326
|
end
|
@@ -3,58 +3,77 @@ require 'net/ssh/errors'
|
|
3
3
|
require 'net/ssh/loggable'
|
4
4
|
require 'net/ssh/authentication/constants'
|
5
5
|
|
6
|
-
module Net
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
# The key manager object. Not all authentication methods will require
|
17
|
-
# this.
|
18
|
-
attr_reader :key_manager
|
19
|
-
|
20
|
-
# Instantiates a new authentication method.
|
21
|
-
def initialize(session, options={})
|
22
|
-
@session = session
|
23
|
-
@key_manager = options[:key_manager]
|
24
|
-
@options = options
|
25
|
-
self.logger = session.logger
|
26
|
-
end
|
6
|
+
module Net
|
7
|
+
module SSH
|
8
|
+
module Authentication
|
9
|
+
module Methods
|
10
|
+
# The base class of all user authentication methods. It provides a few
|
11
|
+
# bits of common functionality.
|
12
|
+
class Abstract
|
13
|
+
include Loggable
|
14
|
+
include Constants
|
27
15
|
|
28
|
-
|
29
|
-
|
30
|
-
def session_id
|
31
|
-
session.transport.algorithms.session_id
|
32
|
-
end
|
16
|
+
# The authentication session object
|
17
|
+
attr_reader :session
|
33
18
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
19
|
+
# The key manager object. Not all authentication methods will require
|
20
|
+
# this.
|
21
|
+
attr_reader :key_manager
|
22
|
+
|
23
|
+
# So far only affects algorithms used for rsa keys, but can be
|
24
|
+
# extended to other keys, e.g after reading of
|
25
|
+
# PubkeyAcceptedAlgorithms option from ssh_config file is implemented.
|
26
|
+
attr_reader :pubkey_algorithms
|
27
|
+
|
28
|
+
# Instantiates a new authentication method.
|
29
|
+
def initialize(session, options = {})
|
30
|
+
@session = session
|
31
|
+
@key_manager = options[:key_manager]
|
32
|
+
@options = options
|
33
|
+
@prompt = options[:password_prompt]
|
34
|
+
@pubkey_algorithms = options[: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]
|
39
|
+
self.logger = session.logger
|
40
|
+
end
|
41
|
+
|
42
|
+
# Returns the session-id, as generated during the first key exchange of
|
43
|
+
# an SSH connection.
|
44
|
+
def session_id
|
45
|
+
session.transport.algorithms.session_id
|
46
|
+
end
|
47
|
+
|
48
|
+
# Sends a message via the underlying transport layer abstraction. This
|
49
|
+
# will block until the message is completely sent.
|
50
|
+
def send_message(msg)
|
51
|
+
session.transport.send_message(msg)
|
52
|
+
end
|
39
53
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
54
|
+
# Creates a new USERAUTH_REQUEST packet. The extra arguments on the end
|
55
|
+
# must be either boolean values or strings, and are tacked onto the end
|
56
|
+
# of the packet. The new packet is returned, ready for sending.
|
57
|
+
def userauth_request(username, next_service, auth_method, *others)
|
58
|
+
buffer = Net::SSH::Buffer.from(:byte, USERAUTH_REQUEST,
|
59
|
+
:string, username, :string, next_service, :string, auth_method)
|
60
|
+
|
61
|
+
others.each do |value|
|
62
|
+
case value
|
63
|
+
when true, false then buffer.write_bool(value)
|
64
|
+
when String then buffer.write_string(value)
|
65
|
+
else raise ArgumentError, "don't know how to write #{value.inspect}"
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
buffer
|
70
|
+
end
|
71
|
+
|
72
|
+
private
|
73
|
+
|
74
|
+
attr_reader :prompt
|
52
75
|
end
|
53
76
|
end
|
54
|
-
|
55
|
-
buffer
|
56
77
|
end
|
57
|
-
|
58
78
|
end
|
59
|
-
|
60
|
-
end; end; end; end
|
79
|
+
end
|