net-ssh-backports 6.3.0.backports

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.
Files changed (111) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/ci.yml +93 -0
  3. data/.gitignore +13 -0
  4. data/.rubocop.yml +21 -0
  5. data/.rubocop_todo.yml +1074 -0
  6. data/.travis.yml +51 -0
  7. data/CHANGES.txt +698 -0
  8. data/Gemfile +13 -0
  9. data/Gemfile.noed25519 +12 -0
  10. data/ISSUE_TEMPLATE.md +30 -0
  11. data/LICENSE.txt +19 -0
  12. data/Manifest +132 -0
  13. data/README.md +287 -0
  14. data/Rakefile +105 -0
  15. data/THANKS.txt +110 -0
  16. data/appveyor.yml +58 -0
  17. data/lib/net/ssh/authentication/agent.rb +284 -0
  18. data/lib/net/ssh/authentication/certificate.rb +183 -0
  19. data/lib/net/ssh/authentication/constants.rb +20 -0
  20. data/lib/net/ssh/authentication/ed25519.rb +185 -0
  21. data/lib/net/ssh/authentication/ed25519_loader.rb +31 -0
  22. data/lib/net/ssh/authentication/key_manager.rb +297 -0
  23. data/lib/net/ssh/authentication/methods/abstract.rb +69 -0
  24. data/lib/net/ssh/authentication/methods/hostbased.rb +72 -0
  25. data/lib/net/ssh/authentication/methods/keyboard_interactive.rb +77 -0
  26. data/lib/net/ssh/authentication/methods/none.rb +34 -0
  27. data/lib/net/ssh/authentication/methods/password.rb +80 -0
  28. data/lib/net/ssh/authentication/methods/publickey.rb +95 -0
  29. data/lib/net/ssh/authentication/pageant.rb +497 -0
  30. data/lib/net/ssh/authentication/pub_key_fingerprint.rb +43 -0
  31. data/lib/net/ssh/authentication/session.rb +163 -0
  32. data/lib/net/ssh/buffer.rb +434 -0
  33. data/lib/net/ssh/buffered_io.rb +202 -0
  34. data/lib/net/ssh/config.rb +406 -0
  35. data/lib/net/ssh/connection/channel.rb +695 -0
  36. data/lib/net/ssh/connection/constants.rb +33 -0
  37. data/lib/net/ssh/connection/event_loop.rb +123 -0
  38. data/lib/net/ssh/connection/keepalive.rb +59 -0
  39. data/lib/net/ssh/connection/session.rb +712 -0
  40. data/lib/net/ssh/connection/term.rb +180 -0
  41. data/lib/net/ssh/errors.rb +106 -0
  42. data/lib/net/ssh/key_factory.rb +218 -0
  43. data/lib/net/ssh/known_hosts.rb +264 -0
  44. data/lib/net/ssh/loggable.rb +62 -0
  45. data/lib/net/ssh/packet.rb +106 -0
  46. data/lib/net/ssh/prompt.rb +62 -0
  47. data/lib/net/ssh/proxy/command.rb +123 -0
  48. data/lib/net/ssh/proxy/errors.rb +16 -0
  49. data/lib/net/ssh/proxy/http.rb +98 -0
  50. data/lib/net/ssh/proxy/https.rb +50 -0
  51. data/lib/net/ssh/proxy/jump.rb +54 -0
  52. data/lib/net/ssh/proxy/socks4.rb +67 -0
  53. data/lib/net/ssh/proxy/socks5.rb +140 -0
  54. data/lib/net/ssh/service/forward.rb +426 -0
  55. data/lib/net/ssh/test/channel.rb +147 -0
  56. data/lib/net/ssh/test/extensions.rb +173 -0
  57. data/lib/net/ssh/test/kex.rb +46 -0
  58. data/lib/net/ssh/test/local_packet.rb +53 -0
  59. data/lib/net/ssh/test/packet.rb +101 -0
  60. data/lib/net/ssh/test/remote_packet.rb +40 -0
  61. data/lib/net/ssh/test/script.rb +180 -0
  62. data/lib/net/ssh/test/socket.rb +65 -0
  63. data/lib/net/ssh/test.rb +94 -0
  64. data/lib/net/ssh/transport/algorithms.rb +502 -0
  65. data/lib/net/ssh/transport/cipher_factory.rb +103 -0
  66. data/lib/net/ssh/transport/constants.rb +40 -0
  67. data/lib/net/ssh/transport/ctr.rb +115 -0
  68. data/lib/net/ssh/transport/hmac/abstract.rb +97 -0
  69. data/lib/net/ssh/transport/hmac/md5.rb +10 -0
  70. data/lib/net/ssh/transport/hmac/md5_96.rb +9 -0
  71. data/lib/net/ssh/transport/hmac/none.rb +13 -0
  72. data/lib/net/ssh/transport/hmac/ripemd160.rb +11 -0
  73. data/lib/net/ssh/transport/hmac/sha1.rb +11 -0
  74. data/lib/net/ssh/transport/hmac/sha1_96.rb +9 -0
  75. data/lib/net/ssh/transport/hmac/sha2_256.rb +11 -0
  76. data/lib/net/ssh/transport/hmac/sha2_256_96.rb +9 -0
  77. data/lib/net/ssh/transport/hmac/sha2_256_etm.rb +12 -0
  78. data/lib/net/ssh/transport/hmac/sha2_512.rb +11 -0
  79. data/lib/net/ssh/transport/hmac/sha2_512_96.rb +9 -0
  80. data/lib/net/ssh/transport/hmac/sha2_512_etm.rb +12 -0
  81. data/lib/net/ssh/transport/hmac.rb +47 -0
  82. data/lib/net/ssh/transport/identity_cipher.rb +57 -0
  83. data/lib/net/ssh/transport/kex/abstract.rb +130 -0
  84. data/lib/net/ssh/transport/kex/abstract5656.rb +72 -0
  85. data/lib/net/ssh/transport/kex/curve25519_sha256.rb +39 -0
  86. data/lib/net/ssh/transport/kex/curve25519_sha256_loader.rb +30 -0
  87. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha1.rb +37 -0
  88. data/lib/net/ssh/transport/kex/diffie_hellman_group14_sha256.rb +11 -0
  89. data/lib/net/ssh/transport/kex/diffie_hellman_group1_sha1.rb +122 -0
  90. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha1.rb +72 -0
  91. data/lib/net/ssh/transport/kex/diffie_hellman_group_exchange_sha256.rb +11 -0
  92. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp256.rb +39 -0
  93. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp384.rb +21 -0
  94. data/lib/net/ssh/transport/kex/ecdh_sha2_nistp521.rb +21 -0
  95. data/lib/net/ssh/transport/kex.rb +31 -0
  96. data/lib/net/ssh/transport/key_expander.rb +30 -0
  97. data/lib/net/ssh/transport/openssl.rb +253 -0
  98. data/lib/net/ssh/transport/packet_stream.rb +280 -0
  99. data/lib/net/ssh/transport/server_version.rb +77 -0
  100. data/lib/net/ssh/transport/session.rb +354 -0
  101. data/lib/net/ssh/transport/state.rb +208 -0
  102. data/lib/net/ssh/verifiers/accept_new.rb +33 -0
  103. data/lib/net/ssh/verifiers/accept_new_or_local_tunnel.rb +33 -0
  104. data/lib/net/ssh/verifiers/always.rb +58 -0
  105. data/lib/net/ssh/verifiers/never.rb +19 -0
  106. data/lib/net/ssh/version.rb +68 -0
  107. data/lib/net/ssh.rb +330 -0
  108. data/net-ssh-public_cert.pem +20 -0
  109. data/net-ssh.gemspec +44 -0
  110. data/support/ssh_tunnel_bug.rb +65 -0
  111. metadata +271 -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)
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,297 @@
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)
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
+ return Net::SSH::Buffer.from(:string, identity.ssh_signature_type,
174
+ :mstring, info[:key].ssh_do_sign(data.to_s)).to_s
175
+ end
176
+
177
+ if info[:from] == :agent
178
+ raise KeyManagerError, "the agent is no longer available" unless agent
179
+
180
+ return agent.sign(info[:identity], data.to_s)
181
+ end
182
+
183
+ raise KeyManagerError, "[BUG] can't determine identity origin (#{info.inspect})"
184
+ end
185
+
186
+ # Identifies whether the ssh-agent will be used or not.
187
+ def use_agent?
188
+ @use_agent
189
+ end
190
+
191
+ # Toggles whether the ssh-agent will be used or not. If true, an
192
+ # attempt will be made to use the ssh-agent. If false, any existing
193
+ # connection to an agent is closed and the agent will not be used.
194
+ def use_agent=(use_agent)
195
+ finish if !use_agent
196
+ @use_agent = use_agent
197
+ end
198
+
199
+ # Returns an Agent instance to use for communicating with an SSH
200
+ # agent process. Returns nil if use of an SSH agent has been disabled,
201
+ # or if the agent is otherwise not available.
202
+ def agent
203
+ return unless use_agent?
204
+
205
+ @agent ||= Agent.connect(logger, options[:agent_socket_factory], options[:identity_agent])
206
+ rescue AgentNotAvailable
207
+ @use_agent = false
208
+ nil
209
+ end
210
+
211
+ def no_keys?
212
+ key_files.empty? && key_data.empty?
213
+ end
214
+
215
+ private
216
+
217
+ # Prepares identities from user key_files for loading, preserving their order and sources.
218
+ def prepare_identities_from_files
219
+ key_files.map do |file|
220
+ if readable_file?(file)
221
+ identity = {}
222
+ cert_file = file + "-cert.pub"
223
+ public_key_file = file + ".pub"
224
+ if readable_file?(cert_file)
225
+ identity[:load_from] = :pubkey_file
226
+ identity[:pubkey_file] = cert_file
227
+ elsif readable_file?(public_key_file)
228
+ identity[:load_from] = :pubkey_file
229
+ identity[:pubkey_file] = public_key_file
230
+ else
231
+ identity[:load_from] = :privkey_file
232
+ end
233
+ identity.merge(privkey_file: file)
234
+ end
235
+ end.compact
236
+ end
237
+
238
+ def readable_file?(path)
239
+ File.file?(path) && File.readable?(path)
240
+ end
241
+
242
+ # Prepared identities from user key_data, preserving their order and sources.
243
+ def prepare_identities_from_data
244
+ key_data.map do |data|
245
+ { load_from: :data, data: data }
246
+ end
247
+ end
248
+
249
+ # Load prepared identities. Private key decryption errors ignored if ignore_decryption_errors
250
+ def load_identities(identities, ask_passphrase, ignore_decryption_errors)
251
+ identities.map do |identity|
252
+ case identity[:load_from]
253
+ when :pubkey_file
254
+ key = KeyFactory.load_public_key(identity[:pubkey_file])
255
+ { public_key: key, from: :file, file: identity[:privkey_file] }
256
+ when :privkey_file
257
+ private_key = KeyFactory.load_private_key(
258
+ identity[:privkey_file], options[:passphrase], ask_passphrase, options[:password_prompt]
259
+ )
260
+ key = private_key.send(:public_key)
261
+ { public_key: key, from: :file, file: identity[:privkey_file], key: private_key }
262
+ when :data
263
+ private_key = KeyFactory.load_data_private_key(
264
+ identity[:data], options[:passphrase], ask_passphrase, "<key in memory>", options[:password_prompt]
265
+ )
266
+ key = private_key.send(:public_key)
267
+ { public_key: key, from: :key_data, data: identity[:data], key: private_key }
268
+ else
269
+ identity
270
+ end
271
+ rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError, OpenSSL::PKey::ECError, OpenSSL::PKey::PKeyError, ArgumentError => e
272
+ if ignore_decryption_errors
273
+ identity
274
+ else
275
+ process_identity_loading_error(identity, e)
276
+ nil
277
+ end
278
+ rescue Exception => e
279
+ process_identity_loading_error(identity, e)
280
+ nil
281
+ end.compact
282
+ end
283
+
284
+ def process_identity_loading_error(identity, e)
285
+ case identity[:load_from]
286
+ when :pubkey_file
287
+ error { "could not load public key file `#{identity[:pubkey_file]}': #{e.class} (#{e.message})" }
288
+ when :privkey_file
289
+ error { "could not load private key file `#{identity[:privkey_file]}': #{e.class} (#{e.message})" }
290
+ else
291
+ raise e
292
+ end
293
+ end
294
+ end
295
+ end
296
+ end
297
+ end
@@ -0,0 +1,69 @@
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
+ # Instantiates a new authentication method.
24
+ def initialize(session, options={})
25
+ @session = session
26
+ @key_manager = options[:key_manager]
27
+ @options = options
28
+ @prompt = options[:password_prompt]
29
+ self.logger = session.logger
30
+ end
31
+
32
+ # Returns the session-id, as generated during the first key exchange of
33
+ # an SSH connection.
34
+ def session_id
35
+ session.transport.algorithms.session_id
36
+ end
37
+
38
+ # Sends a message via the underlying transport layer abstraction. This
39
+ # will block until the message is completely sent.
40
+ def send_message(msg)
41
+ session.transport.send_message(msg)
42
+ end
43
+
44
+ # Creates a new USERAUTH_REQUEST packet. The extra arguments on the end
45
+ # must be either boolean values or strings, and are tacked onto the end
46
+ # of the packet. The new packet is returned, ready for sending.
47
+ def userauth_request(username, next_service, auth_method, *others)
48
+ buffer = Net::SSH::Buffer.from(:byte, USERAUTH_REQUEST,
49
+ :string, username, :string, next_service, :string, auth_method)
50
+
51
+ others.each do |value|
52
+ case value
53
+ when true, false then buffer.write_bool(value)
54
+ when String then buffer.write_string(value)
55
+ else raise ArgumentError, "don't know how to write #{value.inspect}"
56
+ end
57
+ end
58
+
59
+ buffer
60
+ end
61
+
62
+ private
63
+
64
+ attr_reader :prompt
65
+ end
66
+ end
67
+ end
68
+ end
69
+ 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