eximius-net-ssh 6.3.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (117) hide show
  1. checksums.yaml +7 -0
  2. data/.dockerignore +6 -0
  3. data/.github/config/rubocop_linter_action.yml +4 -0
  4. data/.github/workflows/ci-with-docker.yml +44 -0
  5. data/.github/workflows/ci.yml +87 -0
  6. data/.github/workflows/rubocop.yml +13 -0
  7. data/.gitignore +13 -0
  8. data/.rubocop.yml +22 -0
  9. data/.rubocop_todo.yml +1072 -0
  10. data/CHANGES.txt +698 -0
  11. data/Dockerfile +27 -0
  12. data/Dockerfile.openssl3 +17 -0
  13. data/Gemfile +13 -0
  14. data/Gemfile.noed25519 +12 -0
  15. data/ISSUE_TEMPLATE.md +30 -0
  16. data/LICENSE.txt +19 -0
  17. data/Manifest +132 -0
  18. data/README.md +293 -0
  19. data/Rakefile +105 -0
  20. data/THANKS.txt +110 -0
  21. data/appveyor.yml +58 -0
  22. data/docker-compose.yml +23 -0
  23. data/lib/net/ssh/authentication/agent.rb +284 -0
  24. data/lib/net/ssh/authentication/certificate.rb +183 -0
  25. data/lib/net/ssh/authentication/constants.rb +20 -0
  26. data/lib/net/ssh/authentication/ed25519.rb +185 -0
  27. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  28. data/lib/net/ssh/authentication/key_manager.rb +310 -0
  29. data/lib/net/ssh/authentication/methods/abstract.rb +79 -0
  30. data/lib/net/ssh/authentication/methods/hostbased.rb +72 -0
  31. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +77 -0
  32. data/lib/net/ssh/authentication/methods/none.rb +34 -0
  33. data/lib/net/ssh/authentication/methods/password.rb +80 -0
  34. data/lib/net/ssh/authentication/methods/publickey.rb +137 -0
  35. data/lib/net/ssh/authentication/pageant.rb +497 -0
  36. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  37. data/lib/net/ssh/authentication/session.rb +165 -0
  38. data/lib/net/ssh/buffer.rb +440 -0
  39. data/lib/net/ssh/buffered_io.rb +202 -0
  40. data/lib/net/ssh/config.rb +406 -0
  41. data/lib/net/ssh/connection/channel.rb +695 -0
  42. data/lib/net/ssh/connection/constants.rb +33 -0
  43. data/lib/net/ssh/connection/event_loop.rb +123 -0
  44. data/lib/net/ssh/connection/keepalive.rb +59 -0
  45. data/lib/net/ssh/connection/session.rb +712 -0
  46. data/lib/net/ssh/connection/term.rb +180 -0
  47. data/lib/net/ssh/errors.rb +106 -0
  48. data/lib/net/ssh/key_factory.rb +218 -0
  49. data/lib/net/ssh/known_hosts.rb +265 -0
  50. data/lib/net/ssh/loggable.rb +62 -0
  51. data/lib/net/ssh/packet.rb +106 -0
  52. data/lib/net/ssh/prompt.rb +62 -0
  53. data/lib/net/ssh/proxy/command.rb +123 -0
  54. data/lib/net/ssh/proxy/errors.rb +16 -0
  55. data/lib/net/ssh/proxy/http.rb +98 -0
  56. data/lib/net/ssh/proxy/https.rb +50 -0
  57. data/lib/net/ssh/proxy/jump.rb +54 -0
  58. data/lib/net/ssh/proxy/socks4.rb +67 -0
  59. data/lib/net/ssh/proxy/socks5.rb +140 -0
  60. data/lib/net/ssh/service/forward.rb +426 -0
  61. data/lib/net/ssh/test/channel.rb +147 -0
  62. data/lib/net/ssh/test/extensions.rb +173 -0
  63. data/lib/net/ssh/test/kex.rb +46 -0
  64. data/lib/net/ssh/test/local_packet.rb +53 -0
  65. data/lib/net/ssh/test/packet.rb +101 -0
  66. data/lib/net/ssh/test/remote_packet.rb +40 -0
  67. data/lib/net/ssh/test/script.rb +180 -0
  68. data/lib/net/ssh/test/socket.rb +65 -0
  69. data/lib/net/ssh/test.rb +94 -0
  70. data/lib/net/ssh/transport/algorithms.rb +502 -0
  71. data/lib/net/ssh/transport/cipher_factory.rb +103 -0
  72. data/lib/net/ssh/transport/constants.rb +40 -0
  73. data/lib/net/ssh/transport/ctr.rb +115 -0
  74. data/lib/net/ssh/transport/hmac/abstract.rb +97 -0
  75. data/lib/net/ssh/transport/hmac/md5.rb +10 -0
  76. data/lib/net/ssh/transport/hmac/md5_96.rb +9 -0
  77. data/lib/net/ssh/transport/hmac/none.rb +13 -0
  78. data/lib/net/ssh/transport/hmac/ripemd160.rb +11 -0
  79. data/lib/net/ssh/transport/hmac/sha1.rb +11 -0
  80. data/lib/net/ssh/transport/hmac/sha1_96.rb +9 -0
  81. data/lib/net/ssh/transport/hmac/sha2_256.rb +11 -0
  82. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +9 -0
  83. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  84. data/lib/net/ssh/transport/hmac/sha2_512.rb +11 -0
  85. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +9 -0
  86. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  87. data/lib/net/ssh/transport/hmac.rb +47 -0
  88. data/lib/net/ssh/transport/identity_cipher.rb +57 -0
  89. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  90. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  91. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  92. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  93. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +37 -0
  94. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  95. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +122 -0
  96. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +72 -0
  97. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +11 -0
  98. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +39 -0
  99. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +21 -0
  100. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +21 -0
  101. data/lib/net/ssh/transport/kex.rb +31 -0
  102. data/lib/net/ssh/transport/key_expander.rb +30 -0
  103. data/lib/net/ssh/transport/openssl.rb +262 -0
  104. data/lib/net/ssh/transport/packet_stream.rb +280 -0
  105. data/lib/net/ssh/transport/server_version.rb +77 -0
  106. data/lib/net/ssh/transport/session.rb +354 -0
  107. data/lib/net/ssh/transport/state.rb +208 -0
  108. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  109. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  110. data/lib/net/ssh/verifiers/always.rb +58 -0
  111. data/lib/net/ssh/verifiers/never.rb +19 -0
  112. data/lib/net/ssh/version.rb +70 -0
  113. data/lib/net/ssh.rb +330 -0
  114. data/net-ssh-public_cert.pem +20 -0
  115. data/net-ssh.gemspec +44 -0
  116. data/support/ssh_tunnel_bug.rb +65 -0
  117. metadata +277 -0
@@ -0,0 +1,185 @@
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
+ else
81
+ key = '\x00' * (keylen + ivlen)
82
+ end
83
+
84
+ cipher = CipherFactory.get(ciphername, key: key[0...keylen], iv: key[keylen...keylen + ivlen], decrypt: true)
85
+
86
+ decoded = cipher.update(buffer.remainder_as_buffer.to_s)
87
+ decoded << cipher.final
88
+
89
+ decoded = Net::SSH::Buffer.new(decoded)
90
+ check1 = decoded.read_long
91
+ check2 = decoded.read_long
92
+
93
+ raise DecryptError.new("Decrypt failed on private key", encrypted_key: kdfname == 'bcrypt') if (check1 != check2)
94
+
95
+ type_name = decoded.read_string
96
+ case type_name
97
+ when "ssh-ed25519"
98
+ PrivKey.new(decoded)
99
+ else
100
+ decoded.read_private_keyblob(type_name)
101
+ end
102
+ end
103
+ end
104
+
105
+ class PubKey
106
+ include Net::SSH::Authentication::PubKeyFingerprint
107
+
108
+ attr_reader :verify_key
109
+
110
+ def initialize(data)
111
+ @verify_key = ::Ed25519::VerifyKey.new(data)
112
+ end
113
+
114
+ def self.read_keyblob(buffer)
115
+ PubKey.new(buffer.read_string)
116
+ end
117
+
118
+ def to_blob
119
+ Net::SSH::Buffer.from(:mstring, "ssh-ed25519".dup, :string, @verify_key.to_bytes).to_s
120
+ end
121
+
122
+ def ssh_type
123
+ "ssh-ed25519"
124
+ end
125
+
126
+ def ssh_signature_type
127
+ ssh_type
128
+ end
129
+
130
+ def ssh_do_verify(sig, data, options = {})
131
+ @verify_key.verify(sig, data)
132
+ end
133
+
134
+ def to_pem
135
+ # TODO this is not pem
136
+ ssh_type + Base64.encode64(@verify_key.to_bytes)
137
+ end
138
+ end
139
+
140
+ class PrivKey
141
+ CipherFactory = Net::SSH::Transport::CipherFactory
142
+
143
+ MBEGIN = "-----BEGIN OPENSSH PRIVATE KEY-----\n"
144
+ MEND = "-----END OPENSSH PRIVATE KEY-----\n"
145
+ MAGIC = "openssh-key-v1"
146
+
147
+ attr_reader :sign_key
148
+
149
+ def initialize(buffer)
150
+ pk = buffer.read_string
151
+ sk = buffer.read_string
152
+ _comment = buffer.read_string
153
+
154
+ @pk = pk
155
+ @sign_key = SigningKeyFromFile.new(pk, sk)
156
+ end
157
+
158
+ def to_blob
159
+ public_key.to_blob
160
+ end
161
+
162
+ def ssh_type
163
+ "ssh-ed25519"
164
+ end
165
+
166
+ def ssh_signature_type
167
+ ssh_type
168
+ end
169
+
170
+ def public_key
171
+ PubKey.new(@pk)
172
+ end
173
+
174
+ def ssh_do_sign(data, sig_alg = nil)
175
+ @sign_key.sign(data)
176
+ end
177
+
178
+ def self.read(data, password)
179
+ OpenSSHPrivateKeyLoader.read(data, password)
180
+ end
181
+ end
182
+ end
183
+ end
184
+ end
185
+ 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
@@ -0,0 +1,310 @@
1
+ require 'net/ssh/errors'
2
+ require 'net/ssh/key_factory'
3
+ require 'net/ssh/loggable'
4
+ require 'net/ssh/authentication/agent'
5
+
6
+ module Net
7
+ module SSH
8
+ module Authentication
9
+ # A trivial exception class used to report errors in the key manager.
10
+ class KeyManagerError < Net::SSH::Exception; end
11
+
12
+ # This class encapsulates all operations done by clients on a user's
13
+ # private keys. In practice, the client should never need a reference
14
+ # to a private key; instead, they grab a list of "identities" (public
15
+ # keys) that are available from the KeyManager, and then use
16
+ # the KeyManager to do various private key operations using those
17
+ # identities.
18
+ #
19
+ # The KeyManager also uses the Agent class to encapsulate the
20
+ # ssh-agent. Thus, from a client's perspective it is completely
21
+ # hidden whether an identity comes from the ssh-agent or from a file
22
+ # on disk.
23
+ class KeyManager
24
+ include Loggable
25
+
26
+ # The list of user key files that will be examined
27
+ attr_reader :key_files
28
+
29
+ # The list of user key data that will be examined
30
+ attr_reader :key_data
31
+
32
+ # The list of user key certificate files that will be examined
33
+ attr_reader :keycert_files
34
+
35
+ # The map of loaded identities
36
+ attr_reader :known_identities
37
+
38
+ # The map of options that were passed to the key-manager
39
+ attr_reader :options
40
+
41
+ # Create a new KeyManager. By default, the manager will
42
+ # use the ssh-agent if it is running and the `:use_agent` option
43
+ # is not false.
44
+ def initialize(logger, options = {})
45
+ self.logger = logger
46
+ @key_files = []
47
+ @key_data = []
48
+ @keycert_files = []
49
+ @use_agent = options[:use_agent] != false
50
+ @known_identities = {}
51
+ @agent = nil
52
+ @options = options
53
+ end
54
+
55
+ # Clear all knowledge of any loaded user keys. This also clears the list
56
+ # of default identity files that are to be loaded, thus making it
57
+ # appropriate to use if a client wishes to NOT use the default identity
58
+ # files.
59
+ def clear!
60
+ key_files.clear
61
+ key_data.clear
62
+ known_identities.clear
63
+ self
64
+ end
65
+
66
+ # Add the given key_file to the list of key files that will be used.
67
+ def add(key_file)
68
+ key_files.push(File.expand_path(key_file)).uniq!
69
+ self
70
+ end
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
+
78
+ # Add the given key_file to the list of keys that will be used.
79
+ def add_key_data(key_data_)
80
+ key_data.push(key_data_).uniq!
81
+ self
82
+ end
83
+
84
+ # This is used as a hint to the KeyManager indicating that the agent
85
+ # connection is no longer needed. Any other open resources may be closed
86
+ # at this time.
87
+ #
88
+ # Calling this does NOT indicate that the KeyManager will no longer
89
+ # be used. Identities may still be requested and operations done on
90
+ # loaded identities, in which case, the agent will be automatically
91
+ # reconnected. This method simply allows the client connection to be
92
+ # closed when it will not be used in the immediate future.
93
+ def finish
94
+ @agent.close if @agent
95
+ @agent = nil
96
+ end
97
+
98
+ # Iterates over all available identities (public keys) known to this
99
+ # manager. As it finds one, it will then yield it to the caller.
100
+ # The origin of the identities may be from files on disk or from an
101
+ # ssh-agent. Note that identities from an ssh-agent are always listed
102
+ # first in the array, with other identities coming after.
103
+ #
104
+ # If key manager was created with :keys_only option, any identity
105
+ # from ssh-agent will be ignored unless it present in key_files or
106
+ # key_data.
107
+ def each_identity
108
+ prepared_identities = prepare_identities_from_files + prepare_identities_from_data
109
+
110
+ user_identities = load_identities(prepared_identities, false, true)
111
+
112
+ if agent
113
+ agent.identities.each do |key|
114
+ corresponding_user_identity = user_identities.detect { |identity|
115
+ identity[:public_key] && identity[:public_key].to_pem == key.to_pem
116
+ }
117
+ user_identities.delete(corresponding_user_identity) if corresponding_user_identity
118
+
119
+ if !options[:keys_only] || corresponding_user_identity
120
+ known_identities[key] = { from: :agent, identity: key }
121
+ yield key
122
+ end
123
+ end
124
+ end
125
+
126
+ user_identities = load_identities(user_identities, !options[:non_interactive], false)
127
+
128
+ user_identities.each do |identity|
129
+ key = identity.delete(:public_key)
130
+ known_identities[key] = identity
131
+ yield key
132
+ end
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
+
149
+ self
150
+ end
151
+
152
+ # Sign the given data, using the corresponding private key of the given
153
+ # identity. If the identity was originally obtained from an ssh-agent,
154
+ # then the ssh-agent will be used to sign the data, otherwise the
155
+ # private key for the identity will be loaded from disk (if it hasn't
156
+ # been loaded already) and will then be used to sign the data.
157
+ #
158
+ # Regardless of the identity's origin or who does the signing, this
159
+ # will always return the signature in an SSH2-specified "signature
160
+ # blob" format.
161
+ def sign(identity, data, sig_alg = nil)
162
+ info = known_identities[identity] or raise KeyManagerError, "the given identity is unknown to the key manager"
163
+
164
+ if info[:key].nil? && info[:from] == :file
165
+ begin
166
+ info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase], !options[:non_interactive], options[:password_prompt])
167
+ rescue OpenSSL::OpenSSLError, Exception => e
168
+ raise KeyManagerError, "the given identity is known, but the private key could not be loaded: #{e.class} (#{e.message})"
169
+ end
170
+ end
171
+
172
+ if info[:key]
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
181
+ end
182
+
183
+ if info[:from] == :agent
184
+ raise KeyManagerError, "the agent is no longer available" unless agent
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
194
+ end
195
+
196
+ raise KeyManagerError, "[BUG] can't determine identity origin (#{info.inspect})"
197
+ end
198
+
199
+ # Identifies whether the ssh-agent will be used or not.
200
+ def use_agent?
201
+ @use_agent
202
+ end
203
+
204
+ # Toggles whether the ssh-agent will be used or not. If true, an
205
+ # attempt will be made to use the ssh-agent. If false, any existing
206
+ # connection to an agent is closed and the agent will not be used.
207
+ def use_agent=(use_agent)
208
+ finish if !use_agent
209
+ @use_agent = use_agent
210
+ end
211
+
212
+ # Returns an Agent instance to use for communicating with an SSH
213
+ # agent process. Returns nil if use of an SSH agent has been disabled,
214
+ # or if the agent is otherwise not available.
215
+ def agent
216
+ return unless use_agent?
217
+
218
+ @agent ||= Agent.connect(logger, options[:agent_socket_factory], options[:identity_agent])
219
+ rescue AgentNotAvailable
220
+ @use_agent = false
221
+ nil
222
+ end
223
+
224
+ def no_keys?
225
+ key_files.empty? && key_data.empty?
226
+ end
227
+
228
+ private
229
+
230
+ # Prepares identities from user key_files for loading, preserving their order and sources.
231
+ def prepare_identities_from_files
232
+ key_files.map do |file|
233
+ if readable_file?(file)
234
+ identity = {}
235
+ cert_file = file + "-cert.pub"
236
+ public_key_file = file + ".pub"
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)
241
+ identity[:load_from] = :pubkey_file
242
+ identity[:pubkey_file] = public_key_file
243
+ else
244
+ identity[:load_from] = :privkey_file
245
+ end
246
+ identity.merge(privkey_file: file)
247
+ end
248
+ end.compact
249
+ end
250
+
251
+ def readable_file?(path)
252
+ File.file?(path) && File.readable?(path)
253
+ end
254
+
255
+ # Prepared identities from user key_data, preserving their order and sources.
256
+ def prepare_identities_from_data
257
+ key_data.map do |data|
258
+ { load_from: :data, data: data }
259
+ end
260
+ end
261
+
262
+ # Load prepared identities. Private key decryption errors ignored if ignore_decryption_errors
263
+ def load_identities(identities, ask_passphrase, ignore_decryption_errors)
264
+ identities.map do |identity|
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
288
+ process_identity_loading_error(identity, e)
289
+ nil
290
+ end
291
+ rescue Exception => e
292
+ process_identity_loading_error(identity, e)
293
+ nil
294
+ end.compact
295
+ end
296
+
297
+ def process_identity_loading_error(identity, e)
298
+ case identity[:load_from]
299
+ when :pubkey_file
300
+ error { "could not load public key file `#{identity[:pubkey_file]}': #{e.class} (#{e.message})" }
301
+ when :privkey_file
302
+ error { "could not load private key file `#{identity[:privkey_file]}': #{e.class} (#{e.message})" }
303
+ else
304
+ raise e
305
+ end
306
+ end
307
+ end
308
+ end
309
+ end
310
+ end
@@ -0,0 +1,79 @@
1
+ require 'net/ssh/buffer'
2
+ require 'net/ssh/errors'
3
+ require 'net/ssh/loggable'
4
+ require 'net/ssh/authentication/constants'
5
+
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
15
+
16
+ # The authentication session object
17
+ attr_reader :session
18
+
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
53
+
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
75
+ end
76
+ end
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,72 @@
1
+ require 'net/ssh/authentication/methods/abstract'
2
+
3
+ module Net
4
+ module SSH
5
+ module Authentication
6
+ module Methods
7
+ # Implements the host-based SSH authentication method.
8
+ class Hostbased < Abstract
9
+ include Constants
10
+
11
+ # Attempts to perform host-based authorization of the user by trying
12
+ # all known keys.
13
+ def authenticate(next_service, username, password = nil)
14
+ return false unless key_manager
15
+
16
+ key_manager.each_identity do |identity|
17
+ return true if authenticate_with(identity, next_service,
18
+ username, key_manager)
19
+ end
20
+
21
+ return false
22
+ end
23
+
24
+ private
25
+
26
+ # Returns the hostname as reported by the underlying socket.
27
+ def hostname
28
+ session.transport.socket.client_name
29
+ end
30
+
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
36
+
37
+ req = build_request(identity, next_service, username, "#{hostname}.", client_username)
38
+ sig_data = Buffer.from(:string, session_id, :raw, req)
39
+
40
+ sig = key_manager.sign(identity, sig_data.to_s)
41
+
42
+ message = Buffer.from(:raw, req, :string, sig)
43
+
44
+ send_message(message)
45
+ message = session.next_message
46
+
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})" }
53
+
54
+ raise Net::SSH::Authentication::DisallowedMethod unless
55
+ message[:authentications].split(/,/).include? 'hostbased'
56
+
57
+ return false
58
+ else
59
+ raise Net::SSH::Exception, "unexpected server response to USERAUTH_REQUEST: #{message.type} (#{message.inspect})"
60
+ end
61
+ end
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
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end