net-ssh 3.2.0 → 7.2.0.rc1
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 +5 -5
- 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 +93 -0
- data/.github/workflows/rubocop.yml +16 -0
- data/.gitignore +13 -0
- data/.rubocop.yml +22 -0
- data/.rubocop_todo.yml +1081 -0
- data/CHANGES.txt +237 -7
- data/DEVELOPMENT.md +23 -0
- data/Dockerfile +27 -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 +298 -0
- data/Rakefile +125 -74
- data/SECURITY.md +4 -0
- data/appveyor.yml +58 -0
- data/docker-compose.yml +23 -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 +186 -0
- data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
- data/lib/net/ssh/authentication/key_manager.rb +86 -39
- 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 +13 -13
- data/lib/net/ssh/authentication/methods/none.rb +16 -19
- data/lib/net/ssh/authentication/methods/password.rb +27 -17
- data/lib/net/ssh/authentication/methods/publickey.rb +96 -55
- data/lib/net/ssh/authentication/pageant.rb +471 -367
- data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
- data/lib/net/ssh/authentication/session.rb +131 -121
- data/lib/net/ssh/buffer.rb +399 -300
- data/lib/net/ssh/buffered_io.rb +154 -150
- data/lib/net/ssh/config.rb +308 -185
- data/lib/net/ssh/connection/channel.rb +635 -613
- 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 +55 -51
- data/lib/net/ssh/connection/session.rb +620 -551
- data/lib/net/ssh/connection/term.rb +125 -123
- data/lib/net/ssh/errors.rb +101 -99
- data/lib/net/ssh/key_factory.rb +197 -105
- data/lib/net/ssh/known_hosts.rb +214 -127
- 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 +105 -90
- data/lib/net/ssh/proxy/errors.rb +12 -10
- data/lib/net/ssh/proxy/http.rb +82 -79
- 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 +2 -6
- data/lib/net/ssh/proxy/socks5.rb +14 -17
- data/lib/net/ssh/service/forward.rb +370 -317
- 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/algorithms.rb +462 -359
- 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 +122 -99
- data/lib/net/ssh/transport/constants.rb +32 -24
- data/lib/net/ssh/transport/ctr.rb +42 -22
- data/lib/net/ssh/transport/hmac/abstract.rb +81 -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 -185
- data/lib/net/ssh/transport/server_version.rb +55 -56
- data/lib/net/ssh/transport/session.rb +306 -255
- 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 +55 -53
- data/lib/net/ssh.rb +111 -47
- data/net-ssh-public_cert.pem +18 -18
- data/net-ssh.gemspec +38 -205
- data/support/ssh_tunnel_bug.rb +5 -5
- data.tar.gz.sig +0 -0
- metadata +173 -118
- metadata.gz.sig +0 -0
- data/.travis.yml +0 -18
- data/README.rdoc +0 -182
- data/lib/net/ssh/authentication/agent/java_pageant.rb +0 -85
- data/lib/net/ssh/authentication/agent/socket.rb +0 -178
- data/lib/net/ssh/ruby_compat.rb +0 -46
- 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 -52
- 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 -18
- 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 -121
- data/test/authentication/methods/test_none.rb +0 -41
- data/test/authentication/methods/test_password.rb +0 -95
- data/test/authentication/methods/test_publickey.rb +0 -148
- data/test/authentication/test_agent.rb +0 -232
- data/test/authentication/test_key_manager.rb +0 -240
- data/test/authentication/test_session.rb +0 -107
- data/test/common.rb +0 -125
- data/test/configs/auth_off +0 -5
- data/test/configs/auth_on +0 -4
- data/test/configs/empty +0 -0
- 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/negative_match +0 -6
- data/test/configs/nohost +0 -19
- data/test/configs/numeric_host +0 -4
- data/test/configs/proxy_remote_user +0 -2
- 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 -487
- data/test/connection/test_session.rb +0 -564
- data/test/integration/README.txt +0 -17
- data/test/integration/Vagrantfile +0 -12
- data/test/integration/common.rb +0 -63
- data/test/integration/playbook.yml +0 -56
- data/test/integration/test_forward.rb +0 -637
- data/test/integration/test_id_rsa_keys.rb +0 -96
- data/test/integration/test_proxy.rb +0 -93
- data/test/known_hosts/github +0 -1
- data/test/known_hosts/github_hash +0 -1
- data/test/manual/test_pageant.rb +0 -37
- data/test/start/test_connection.rb +0 -53
- data/test/start/test_options.rb +0 -57
- data/test/start/test_transport.rb +0 -28
- data/test/start/test_user_nil.rb +0 -27
- data/test/test_all.rb +0 -12
- data/test/test_buffer.rb +0 -433
- data/test/test_buffered_io.rb +0 -63
- data/test/test_config.rb +0 -268
- data/test/test_key_factory.rb +0 -191
- data/test/test_known_hosts.rb +0 -66
- 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 -150
- data/test/transport/kex/test_diffie_hellman_group_exchange_sha1.rb +0 -96
- data/test/transport/kex/test_diffie_hellman_group_exchange_sha256.rb +0 -19
- 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 -328
- 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 -1762
- data/test/transport/test_server_version.rb +0 -74
- data/test/transport/test_session.rb +0 -331
- data/test/transport/test_state.rb +0 -181
- data/test/verifiers/test_secure.rb +0 -40
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
gem 'ed25519', '~> 1.2'
|
|
2
|
+
gem 'bcrypt_pbkdf', '~> 1.0' unless RUBY_PLATFORM == "java"
|
|
3
|
+
|
|
4
|
+
require 'ed25519'
|
|
5
|
+
|
|
6
|
+
require 'base64'
|
|
7
|
+
|
|
8
|
+
require 'net/ssh/transport/cipher_factory'
|
|
9
|
+
require 'net/ssh/authentication/pub_key_fingerprint'
|
|
10
|
+
require 'bcrypt_pbkdf' unless RUBY_PLATFORM == "java"
|
|
11
|
+
|
|
12
|
+
module Net
|
|
13
|
+
module SSH
|
|
14
|
+
module Authentication
|
|
15
|
+
module ED25519
|
|
16
|
+
class SigningKeyFromFile < SimpleDelegator
|
|
17
|
+
def initialize(pk, sk)
|
|
18
|
+
key = ::Ed25519::SigningKey.from_keypair(sk)
|
|
19
|
+
raise ArgumentError, "pk does not match sk" unless pk == key.verify_key.to_bytes
|
|
20
|
+
|
|
21
|
+
super(key)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class OpenSSHPrivateKeyLoader
|
|
26
|
+
CipherFactory = Net::SSH::Transport::CipherFactory
|
|
27
|
+
|
|
28
|
+
MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
|
|
29
|
+
MEND = "-----END OPENSSH PRIVATE KEY-----"
|
|
30
|
+
MAGIC = "openssh-key-v1"
|
|
31
|
+
|
|
32
|
+
class DecryptError < ArgumentError
|
|
33
|
+
def initialize(message, encrypted_key: false)
|
|
34
|
+
super(message)
|
|
35
|
+
@encrypted_key = encrypted_key
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def encrypted_key?
|
|
39
|
+
return @encrypted_key
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def self.read(datafull, password)
|
|
44
|
+
datafull = datafull.strip
|
|
45
|
+
raise ArgumentError.new("Expected #{MBEGIN} at start of private key") unless datafull.start_with?(MBEGIN)
|
|
46
|
+
raise ArgumentError.new("Expected #{MEND} at end of private key") unless datafull.end_with?(MEND)
|
|
47
|
+
|
|
48
|
+
datab64 = datafull[MBEGIN.size...-MEND.size]
|
|
49
|
+
data = Base64.decode64(datab64)
|
|
50
|
+
raise ArgumentError.new("Expected #{MAGIC} at start of decoded private key") unless data.start_with?(MAGIC)
|
|
51
|
+
|
|
52
|
+
buffer = Net::SSH::Buffer.new(data[MAGIC.size + 1..-1])
|
|
53
|
+
|
|
54
|
+
ciphername = buffer.read_string
|
|
55
|
+
raise ArgumentError.new("#{ciphername} in private key is not supported") unless
|
|
56
|
+
CipherFactory.supported?(ciphername)
|
|
57
|
+
|
|
58
|
+
kdfname = buffer.read_string
|
|
59
|
+
raise ArgumentError.new("Expected #{kdfname} to be or none or bcrypt") unless %w[none bcrypt].include?(kdfname)
|
|
60
|
+
|
|
61
|
+
kdfopts = Net::SSH::Buffer.new(buffer.read_string)
|
|
62
|
+
num_keys = buffer.read_long
|
|
63
|
+
raise ArgumentError.new("Only 1 key is supported in ssh keys #{num_keys} was in private key") unless num_keys == 1
|
|
64
|
+
|
|
65
|
+
_pubkey = buffer.read_string
|
|
66
|
+
|
|
67
|
+
len = buffer.read_long
|
|
68
|
+
|
|
69
|
+
keylen, blocksize, ivlen = CipherFactory.get_lengths(ciphername, iv_len: true)
|
|
70
|
+
raise ArgumentError.new("Private key len:#{len} is not a multiple of #{blocksize}") if
|
|
71
|
+
((len < blocksize) || ((blocksize > 0) && (len % blocksize) != 0))
|
|
72
|
+
|
|
73
|
+
if kdfname == 'bcrypt'
|
|
74
|
+
salt = kdfopts.read_string
|
|
75
|
+
rounds = kdfopts.read_long
|
|
76
|
+
|
|
77
|
+
raise "BCryptPbkdf is not implemented for jruby" if RUBY_PLATFORM == "java"
|
|
78
|
+
|
|
79
|
+
key = BCryptPbkdf::key(password, salt, keylen + ivlen, rounds)
|
|
80
|
+
raise DecryptError.new("BCyryptPbkdf failed", encrypted_key: true) unless key
|
|
81
|
+
else
|
|
82
|
+
key = '\x00' * (keylen + ivlen)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
cipher = CipherFactory.get(ciphername, key: key[0...keylen], iv: key[keylen...keylen + ivlen], decrypt: true)
|
|
86
|
+
|
|
87
|
+
decoded = cipher.update(buffer.remainder_as_buffer.to_s)
|
|
88
|
+
decoded << cipher.final
|
|
89
|
+
|
|
90
|
+
decoded = Net::SSH::Buffer.new(decoded)
|
|
91
|
+
check1 = decoded.read_long
|
|
92
|
+
check2 = decoded.read_long
|
|
93
|
+
|
|
94
|
+
raise DecryptError.new("Decrypt failed on private key", encrypted_key: kdfname == 'bcrypt') if (check1 != check2)
|
|
95
|
+
|
|
96
|
+
type_name = decoded.read_string
|
|
97
|
+
case type_name
|
|
98
|
+
when "ssh-ed25519"
|
|
99
|
+
PrivKey.new(decoded)
|
|
100
|
+
else
|
|
101
|
+
decoded.read_private_keyblob(type_name)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
class PubKey
|
|
107
|
+
include Net::SSH::Authentication::PubKeyFingerprint
|
|
108
|
+
|
|
109
|
+
attr_reader :verify_key
|
|
110
|
+
|
|
111
|
+
def initialize(data)
|
|
112
|
+
@verify_key = ::Ed25519::VerifyKey.new(data)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def self.read_keyblob(buffer)
|
|
116
|
+
PubKey.new(buffer.read_string)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
def to_blob
|
|
120
|
+
Net::SSH::Buffer.from(:mstring, "ssh-ed25519".dup, :string, @verify_key.to_bytes).to_s
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def ssh_type
|
|
124
|
+
"ssh-ed25519"
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def ssh_signature_type
|
|
128
|
+
ssh_type
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def ssh_do_verify(sig, data, options = {})
|
|
132
|
+
@verify_key.verify(sig, data)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def to_pem
|
|
136
|
+
# TODO this is not pem
|
|
137
|
+
ssh_type + Base64.encode64(@verify_key.to_bytes)
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class PrivKey
|
|
142
|
+
CipherFactory = Net::SSH::Transport::CipherFactory
|
|
143
|
+
|
|
144
|
+
MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
|
|
145
|
+
MEND = "-----END OPENSSH PRIVATE KEY-----\n"
|
|
146
|
+
MAGIC = "openssh-key-v1"
|
|
147
|
+
|
|
148
|
+
attr_reader :sign_key
|
|
149
|
+
|
|
150
|
+
def initialize(buffer)
|
|
151
|
+
pk = buffer.read_string
|
|
152
|
+
sk = buffer.read_string
|
|
153
|
+
_comment = buffer.read_string
|
|
154
|
+
|
|
155
|
+
@pk = pk
|
|
156
|
+
@sign_key = SigningKeyFromFile.new(pk, sk)
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def to_blob
|
|
160
|
+
public_key.to_blob
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def ssh_type
|
|
164
|
+
"ssh-ed25519"
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def ssh_signature_type
|
|
168
|
+
ssh_type
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def public_key
|
|
172
|
+
PubKey.new(@pk)
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def ssh_do_sign(data, sig_alg = nil)
|
|
176
|
+
@sign_key.sign(data)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def self.read(data, password)
|
|
180
|
+
OpenSSHPrivateKeyLoader.read(data, password)
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
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,7 +6,6 @@ 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
|
|
|
@@ -30,6 +29,9 @@ 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
|
+
|
|
33
35
|
# The map of loaded identities
|
|
34
36
|
attr_reader :known_identities
|
|
35
37
|
|
|
@@ -39,11 +41,12 @@ module Net
|
|
|
39
41
|
# Create a new KeyManager. By default, the manager will
|
|
40
42
|
# use the ssh-agent if it is running and the `:use_agent` option
|
|
41
43
|
# is not false.
|
|
42
|
-
def initialize(logger, options={})
|
|
44
|
+
def initialize(logger, options = {})
|
|
43
45
|
self.logger = logger
|
|
44
46
|
@key_files = []
|
|
45
47
|
@key_data = []
|
|
46
|
-
@
|
|
48
|
+
@keycert_files = []
|
|
49
|
+
@use_agent = options[:use_agent] != false
|
|
47
50
|
@known_identities = {}
|
|
48
51
|
@agent = nil
|
|
49
52
|
@options = options
|
|
@@ -66,6 +69,12 @@ module Net
|
|
|
66
69
|
self
|
|
67
70
|
end
|
|
68
71
|
|
|
72
|
+
# Add the given keycert_file to the list of keycert files that will be used.
|
|
73
|
+
def add_keycert(keycert_file)
|
|
74
|
+
keycert_files.push(File.expand_path(keycert_file)).uniq!
|
|
75
|
+
self
|
|
76
|
+
end
|
|
77
|
+
|
|
69
78
|
# Add the given key_file to the list of keys that will be used.
|
|
70
79
|
def add_key_data(key_data_)
|
|
71
80
|
key_data.push(key_data_).uniq!
|
|
@@ -108,7 +117,7 @@ module Net
|
|
|
108
117
|
user_identities.delete(corresponding_user_identity) if corresponding_user_identity
|
|
109
118
|
|
|
110
119
|
if !options[:keys_only] || corresponding_user_identity
|
|
111
|
-
known_identities[key] = { :
|
|
120
|
+
known_identities[key] = { from: :agent, identity: key }
|
|
112
121
|
yield key
|
|
113
122
|
end
|
|
114
123
|
end
|
|
@@ -122,6 +131,21 @@ module Net
|
|
|
122
131
|
yield key
|
|
123
132
|
end
|
|
124
133
|
|
|
134
|
+
known_identity_blobs = known_identities.keys.map(&:to_blob)
|
|
135
|
+
keycert_files.each do |keycert_file|
|
|
136
|
+
keycert = KeyFactory.load_public_key(keycert_file)
|
|
137
|
+
next if known_identity_blobs.include?(keycert.to_blob)
|
|
138
|
+
|
|
139
|
+
(_, corresponding_identity) = known_identities.detect { |public_key, _|
|
|
140
|
+
public_key.to_pem == keycert.to_pem
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if corresponding_identity
|
|
144
|
+
known_identities[keycert] = corresponding_identity
|
|
145
|
+
yield keycert
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
125
149
|
self
|
|
126
150
|
end
|
|
127
151
|
|
|
@@ -134,25 +158,39 @@ module Net
|
|
|
134
158
|
# Regardless of the identity's origin or who does the signing, this
|
|
135
159
|
# will always return the signature in an SSH2-specified "signature
|
|
136
160
|
# blob" format.
|
|
137
|
-
def sign(identity, data)
|
|
161
|
+
def sign(identity, data, sig_alg = nil)
|
|
138
162
|
info = known_identities[identity] or raise KeyManagerError, "the given identity is unknown to the key manager"
|
|
139
163
|
|
|
140
164
|
if info[:key].nil? && info[:from] == :file
|
|
141
165
|
begin
|
|
142
|
-
info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase], !options[:non_interactive])
|
|
143
|
-
rescue
|
|
166
|
+
info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase], !options[:non_interactive], options[:password_prompt])
|
|
167
|
+
rescue OpenSSL::OpenSSLError, Exception => e
|
|
144
168
|
raise KeyManagerError, "the given identity is known, but the private key could not be loaded: #{e.class} (#{e.message})"
|
|
145
169
|
end
|
|
146
170
|
end
|
|
147
171
|
|
|
148
172
|
if info[:key]
|
|
149
|
-
|
|
150
|
-
|
|
173
|
+
if sig_alg.nil?
|
|
174
|
+
signed = info[:key].ssh_do_sign(data.to_s)
|
|
175
|
+
sig_alg = identity.ssh_signature_type
|
|
176
|
+
else
|
|
177
|
+
signed = info[:key].ssh_do_sign(data.to_s, sig_alg)
|
|
178
|
+
end
|
|
179
|
+
return Net::SSH::Buffer.from(:string, sig_alg,
|
|
180
|
+
:mstring, signed).to_s
|
|
151
181
|
end
|
|
152
182
|
|
|
153
183
|
if info[:from] == :agent
|
|
154
184
|
raise KeyManagerError, "the agent is no longer available" unless agent
|
|
155
|
-
|
|
185
|
+
|
|
186
|
+
case sig_alg
|
|
187
|
+
when "rsa-sha2-512"
|
|
188
|
+
return agent.sign(info[:identity], data.to_s, Net::SSH::Authentication::Agent::SSH_AGENT_RSA_SHA2_512)
|
|
189
|
+
when "rsa-sha2-256"
|
|
190
|
+
return agent.sign(info[:identity], data.to_s, Net::SSH::Authentication::Agent::SSH_AGENT_RSA_SHA2_256)
|
|
191
|
+
else
|
|
192
|
+
return agent.sign(info[:identity], data.to_s)
|
|
193
|
+
end
|
|
156
194
|
end
|
|
157
195
|
|
|
158
196
|
raise KeyManagerError, "[BUG] can't determine identity origin (#{info.inspect})"
|
|
@@ -176,12 +214,17 @@ module Net
|
|
|
176
214
|
# or if the agent is otherwise not available.
|
|
177
215
|
def agent
|
|
178
216
|
return unless use_agent?
|
|
179
|
-
|
|
217
|
+
|
|
218
|
+
@agent ||= Agent.connect(logger, options[:agent_socket_factory], options[:identity_agent])
|
|
180
219
|
rescue AgentNotAvailable
|
|
181
220
|
@use_agent = false
|
|
182
221
|
nil
|
|
183
222
|
end
|
|
184
223
|
|
|
224
|
+
def no_keys?
|
|
225
|
+
key_files.empty? && key_data.empty?
|
|
226
|
+
end
|
|
227
|
+
|
|
185
228
|
private
|
|
186
229
|
|
|
187
230
|
# Prepares identities from user key_files for loading, preserving their order and sources.
|
|
@@ -189,8 +232,12 @@ module Net
|
|
|
189
232
|
key_files.map do |file|
|
|
190
233
|
if readable_file?(file)
|
|
191
234
|
identity = {}
|
|
235
|
+
cert_file = file + "-cert.pub"
|
|
192
236
|
public_key_file = file + ".pub"
|
|
193
|
-
if readable_file?(
|
|
237
|
+
if readable_file?(cert_file)
|
|
238
|
+
identity[:load_from] = :pubkey_file
|
|
239
|
+
identity[:pubkey_file] = cert_file
|
|
240
|
+
elsif readable_file?(public_key_file)
|
|
194
241
|
identity[:load_from] = :pubkey_file
|
|
195
242
|
identity[:pubkey_file] = public_key_file
|
|
196
243
|
else
|
|
@@ -208,41 +255,42 @@ module Net
|
|
|
208
255
|
# Prepared identities from user key_data, preserving their order and sources.
|
|
209
256
|
def prepare_identities_from_data
|
|
210
257
|
key_data.map do |data|
|
|
211
|
-
{ :
|
|
258
|
+
{ load_from: :data, data: data }
|
|
212
259
|
end
|
|
213
260
|
end
|
|
214
261
|
|
|
215
262
|
# Load prepared identities. Private key decryption errors ignored if ignore_decryption_errors
|
|
216
263
|
def load_identities(identities, ask_passphrase, ignore_decryption_errors)
|
|
217
264
|
identities.map do |identity|
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
end
|
|
242
|
-
rescue Exception => e
|
|
265
|
+
case identity[:load_from]
|
|
266
|
+
when :pubkey_file
|
|
267
|
+
key = KeyFactory.load_public_key(identity[:pubkey_file])
|
|
268
|
+
{ public_key: key, from: :file, file: identity[:privkey_file] }
|
|
269
|
+
when :privkey_file
|
|
270
|
+
private_key = KeyFactory.load_private_key(
|
|
271
|
+
identity[:privkey_file], options[:passphrase], ask_passphrase, options[:password_prompt]
|
|
272
|
+
)
|
|
273
|
+
key = private_key.send(:public_key)
|
|
274
|
+
{ public_key: key, from: :file, file: identity[:privkey_file], key: private_key }
|
|
275
|
+
when :data
|
|
276
|
+
private_key = KeyFactory.load_data_private_key(
|
|
277
|
+
identity[:data], options[:passphrase], ask_passphrase, "<key in memory>", options[:password_prompt]
|
|
278
|
+
)
|
|
279
|
+
key = private_key.send(:public_key)
|
|
280
|
+
{ public_key: key, from: :key_data, data: identity[:data], key: private_key }
|
|
281
|
+
else
|
|
282
|
+
identity
|
|
283
|
+
end
|
|
284
|
+
rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError, OpenSSL::PKey::ECError, OpenSSL::PKey::PKeyError, ArgumentError => e
|
|
285
|
+
if ignore_decryption_errors
|
|
286
|
+
identity
|
|
287
|
+
else
|
|
243
288
|
process_identity_loading_error(identity, e)
|
|
244
289
|
nil
|
|
245
290
|
end
|
|
291
|
+
rescue Exception => e
|
|
292
|
+
process_identity_loading_error(identity, e)
|
|
293
|
+
nil
|
|
246
294
|
end.compact
|
|
247
295
|
end
|
|
248
296
|
|
|
@@ -256,7 +304,6 @@ module Net
|
|
|
256
304
|
raise e
|
|
257
305
|
end
|
|
258
306
|
end
|
|
259
|
-
|
|
260
307
|
end
|
|
261
308
|
end
|
|
262
309
|
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
|
|
@@ -4,19 +4,18 @@ module Net
|
|
|
4
4
|
module SSH
|
|
5
5
|
module Authentication
|
|
6
6
|
module Methods
|
|
7
|
-
|
|
8
7
|
# Implements the host-based SSH authentication method.
|
|
9
8
|
class Hostbased < Abstract
|
|
10
9
|
include Constants
|
|
11
10
|
|
|
12
11
|
# Attempts to perform host-based authorization of the user by trying
|
|
13
12
|
# all known keys.
|
|
14
|
-
def authenticate(next_service, username, password=nil)
|
|
13
|
+
def authenticate(next_service, username, password = nil)
|
|
15
14
|
return false unless key_manager
|
|
16
15
|
|
|
17
16
|
key_manager.each_identity do |identity|
|
|
18
17
|
return true if authenticate_with(identity, next_service,
|
|
19
|
-
|
|
18
|
+
username, key_manager)
|
|
20
19
|
end
|
|
21
20
|
|
|
22
21
|
return false
|
|
@@ -24,51 +23,49 @@ module Net
|
|
|
24
23
|
|
|
25
24
|
private
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
# Attempts to perform host-based authentication of the user, using
|
|
33
|
-
# the given host identity (key).
|
|
34
|
-
def authenticate_with(identity, next_service, username, key_manager)
|
|
35
|
-
debug { "trying hostbased (#{identity.fingerprint})" }
|
|
36
|
-
client_username = ENV['USER'] || username
|
|
26
|
+
# Returns the hostname as reported by the underlying socket.
|
|
27
|
+
def hostname
|
|
28
|
+
session.transport.socket.client_name
|
|
29
|
+
end
|
|
37
30
|
|
|
38
|
-
|
|
39
|
-
|
|
31
|
+
# Attempts to perform host-based authentication of the user, using
|
|
32
|
+
# the given host identity (key).
|
|
33
|
+
def authenticate_with(identity, next_service, username, key_manager)
|
|
34
|
+
debug { "trying hostbased (#{identity.fingerprint})" }
|
|
35
|
+
client_username = ENV['USER'] || username
|
|
40
36
|
|
|
41
|
-
|
|
37
|
+
req = build_request(identity, next_service, username, "#{hostname}.", client_username)
|
|
38
|
+
sig_data = Buffer.from(:string, session_id, :raw, req)
|
|
42
39
|
|
|
43
|
-
|
|
40
|
+
sig = key_manager.sign(identity, sig_data.to_s)
|
|
44
41
|
|
|
45
|
-
|
|
46
|
-
message = session.next_message
|
|
42
|
+
message = Buffer.from(:raw, req, :string, sig)
|
|
47
43
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
info { "hostbased succeeded (#{identity.fingerprint})" }
|
|
51
|
-
return true
|
|
52
|
-
when USERAUTH_FAILURE
|
|
53
|
-
info { "hostbased failed (#{identity.fingerprint})" }
|
|
44
|
+
send_message(message)
|
|
45
|
+
message = session.next_message
|
|
54
46
|
|
|
55
|
-
|
|
56
|
-
|
|
47
|
+
case message.type
|
|
48
|
+
when USERAUTH_SUCCESS
|
|
49
|
+
info { "hostbased succeeded (#{identity.fingerprint})" }
|
|
50
|
+
return true
|
|
51
|
+
when USERAUTH_FAILURE
|
|
52
|
+
info { "hostbased failed (#{identity.fingerprint})" }
|
|
57
53
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
raise Net::SSH::Exception, "unexpected server response to USERAUTH_REQUEST: #{message.type} (#{message.inspect})"
|
|
61
|
-
end
|
|
62
|
-
end
|
|
54
|
+
raise Net::SSH::Authentication::DisallowedMethod unless
|
|
55
|
+
message[:authentications].split(/,/).include? 'hostbased'
|
|
63
56
|
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Buffer.from(:key, identity).to_s, hostname, client_username).to_s
|
|
57
|
+
return false
|
|
58
|
+
else
|
|
59
|
+
raise Net::SSH::Exception, "unexpected server response to USERAUTH_REQUEST: #{message.type} (#{message.inspect})"
|
|
68
60
|
end
|
|
61
|
+
end
|
|
69
62
|
|
|
63
|
+
# Build the "core" hostbased request string.
|
|
64
|
+
def build_request(identity, next_service, username, hostname, client_username)
|
|
65
|
+
userauth_request(username, next_service, "hostbased", identity.ssh_type,
|
|
66
|
+
Buffer.from(:key, identity).to_s, hostname, client_username).to_s
|
|
67
|
+
end
|
|
70
68
|
end
|
|
71
|
-
|
|
72
69
|
end
|
|
73
70
|
end
|
|
74
71
|
end
|