class Net::SSH::Authentication::KeyManager
This class encapsulates all operations done by clients on a user’s private keys. In practice, the client should never need a reference to a private key; instead, they grab a list of “identities” (public keys) that are available from the KeyManager
, and then use the KeyManager
to do various private key operations using those identities.
The KeyManager
also uses the Agent
class to encapsulate the ssh-agent. Thus, from a client’s perspective it is completely hidden whether an identity comes from the ssh-agent or from a file on disk.
Attributes
The list of user key data that will be examined
The list of user key files that will be examined
The list of user key certificate files that will be examined
The map of loaded identities
The map of options that were passed to the key-manager
Public Class Methods
Create a new KeyManager
. By default, the manager will use the ssh-agent if it is running and the ‘:use_agent` option is not false.
# File lib/net/ssh/authentication/key_manager.rb, line 44 def initialize(logger, options = {}) self.logger = logger @key_files = [] @key_data = [] @keycert_files = [] @use_agent = options[:use_agent] != false @known_identities = {} @agent = nil @options = options end
Public Instance Methods
Add the given key_file to the list of key files that will be used.
# File lib/net/ssh/authentication/key_manager.rb, line 67 def add(key_file) key_files.push(File.expand_path(key_file)).uniq! self end
Add the given key_file to the list of keys that will be used.
# File lib/net/ssh/authentication/key_manager.rb, line 79 def add_key_data(key_data_) key_data.push(key_data_).uniq! self end
Add the given keycert_file to the list of keycert files that will be used.
# File lib/net/ssh/authentication/key_manager.rb, line 73 def add_keycert(keycert_file) keycert_files.push(File.expand_path(keycert_file)).uniq! self end
Returns an Agent
instance to use for communicating with an SSH
agent process. Returns nil if use of an SSH
agent has been disabled, or if the agent is otherwise not available.
# File lib/net/ssh/authentication/key_manager.rb, line 215 def agent return unless use_agent? @agent ||= Agent.connect(logger, options[:agent_socket_factory], options[:identity_agent]) rescue AgentNotAvailable @use_agent = false nil end
Clear all knowledge of any loaded user keys. This also clears the list of default identity files that are to be loaded, thus making it appropriate to use if a client wishes to NOT use the default identity files.
# File lib/net/ssh/authentication/key_manager.rb, line 59 def clear! key_files.clear key_data.clear known_identities.clear self end
Iterates over all available identities (public keys) known to this manager. As it finds one, it will then yield it to the caller. The origin of the identities may be from files on disk or from an ssh-agent. Note that identities from an ssh-agent are always listed first in the array, with other identities coming after.
If key manager was created with :keys_only option, any identity from ssh-agent will be ignored unless it present in key_files
or key_data.
# File lib/net/ssh/authentication/key_manager.rb, line 107 def each_identity prepared_identities = prepare_identities_from_files + prepare_identities_from_data user_identities = load_identities(prepared_identities, false, true) if agent agent.identities.each do |key| corresponding_user_identity = user_identities.detect { |identity| identity[:public_key] && identity[:public_key].to_pem == key.to_pem } user_identities.delete(corresponding_user_identity) if corresponding_user_identity if !options[:keys_only] || corresponding_user_identity known_identities[key] = { from: :agent, identity: key } yield key end end end user_identities = load_identities(user_identities, !options[:non_interactive], false) user_identities.each do |identity| key = identity.delete(:public_key) known_identities[key] = identity yield key end known_identity_blobs = known_identities.keys.map(&:to_blob) keycert_files.each do |keycert_file| keycert = KeyFactory.load_public_key(keycert_file) next if known_identity_blobs.include?(keycert.to_blob) (_, corresponding_identity) = known_identities.detect { |public_key, _| public_key.to_pem == keycert.to_pem } if corresponding_identity known_identities[keycert] = corresponding_identity yield keycert end end self end
This is used as a hint to the KeyManager
indicating that the agent connection is no longer needed. Any other open resources may be closed at this time.
Calling this does NOT indicate that the KeyManager
will no longer be used. Identities may still be requested and operations done on loaded identities, in which case, the agent will be automatically reconnected. This method simply allows the client connection to be closed when it will not be used in the immediate future.
# File lib/net/ssh/authentication/key_manager.rb, line 93 def finish @agent.close if @agent @agent = nil end
# File lib/net/ssh/authentication/key_manager.rb, line 224 def no_keys? key_files.empty? && key_data.empty? end
Sign the given data, using the corresponding private key of the given identity. If the identity was originally obtained from an ssh-agent, then the ssh-agent will be used to sign the data, otherwise the private key for the identity will be loaded from disk (if it hasn’t been loaded already) and will then be used to sign the data.
Regardless of the identity’s origin or who does the signing, this will always return the signature in an SSH2-specified “signature blob” format.
# File lib/net/ssh/authentication/key_manager.rb, line 161 def sign(identity, data, sig_alg = nil) info = known_identities[identity] or raise KeyManagerError, "the given identity is unknown to the key manager" if info[:key].nil? && info[:from] == :file begin info[:key] = KeyFactory.load_private_key(info[:file], options[:passphrase], !options[:non_interactive], options[:password_prompt]) rescue OpenSSL::OpenSSLError, Exception => e raise KeyManagerError, "the given identity is known, but the private key could not be loaded: #{e.class} (#{e.message})" end end if info[:key] if sig_alg.nil? signed = info[:key].ssh_do_sign(data.to_s) sig_alg = identity.ssh_signature_type else signed = info[:key].ssh_do_sign(data.to_s, sig_alg) end return Net::SSH::Buffer.from(:string, sig_alg, :mstring, signed).to_s end if info[:from] == :agent raise KeyManagerError, "the agent is no longer available" unless agent case sig_alg when "rsa-sha2-512" return agent.sign(info[:identity], data.to_s, Net::SSH::Authentication::Agent::SSH_AGENT_RSA_SHA2_512) when "rsa-sha2-256" return agent.sign(info[:identity], data.to_s, Net::SSH::Authentication::Agent::SSH_AGENT_RSA_SHA2_256) else return agent.sign(info[:identity], data.to_s) end end raise KeyManagerError, "[BUG] can't determine identity origin (#{info.inspect})" end
Toggles whether the ssh-agent will be used or not. If true, an attempt will be made to use the ssh-agent. If false, any existing connection to an agent is closed and the agent will not be used.
# File lib/net/ssh/authentication/key_manager.rb, line 207 def use_agent=(use_agent) finish if !use_agent @use_agent = use_agent end
Identifies whether the ssh-agent will be used or not.
# File lib/net/ssh/authentication/key_manager.rb, line 200 def use_agent? @use_agent end
Private Instance Methods
Load prepared identities. Private key decryption errors ignored if ignore_decryption_errors
# File lib/net/ssh/authentication/key_manager.rb, line 263 def load_identities(identities, ask_passphrase, ignore_decryption_errors) identities.map do |identity| case identity[:load_from] when :pubkey_file key = KeyFactory.load_public_key(identity[:pubkey_file]) { public_key: key, from: :file, file: identity[:privkey_file] } when :privkey_file private_key = KeyFactory.load_private_key( identity[:privkey_file], options[:passphrase], ask_passphrase, options[:password_prompt] ) key = private_key.send(:public_key) { public_key: key, from: :file, file: identity[:privkey_file], key: private_key } when :data private_key = KeyFactory.load_data_private_key( identity[:data], options[:passphrase], ask_passphrase, "<key in memory>", options[:password_prompt] ) key = private_key.send(:public_key) { public_key: key, from: :key_data, data: identity[:data], key: private_key } else identity end rescue OpenSSL::PKey::RSAError, OpenSSL::PKey::DSAError, OpenSSL::PKey::ECError, OpenSSL::PKey::PKeyError, ArgumentError => e if ignore_decryption_errors identity else process_identity_loading_error(identity, e) nil end rescue Exception => e process_identity_loading_error(identity, e) nil end.compact end
Prepared identities from user key_data
, preserving their order and sources.
# File lib/net/ssh/authentication/key_manager.rb, line 256 def prepare_identities_from_data key_data.map do |data| { load_from: :data, data: data } end end
Prepares identities from user key_files
for loading, preserving their order and sources.
# File lib/net/ssh/authentication/key_manager.rb, line 231 def prepare_identities_from_files key_files.map do |file| if readable_file?(file) identity = {} cert_file = file + "-cert.pub" public_key_file = file + ".pub" if readable_file?(cert_file) identity[:load_from] = :pubkey_file identity[:pubkey_file] = cert_file elsif readable_file?(public_key_file) identity[:load_from] = :pubkey_file identity[:pubkey_file] = public_key_file else identity[:load_from] = :privkey_file end identity.merge(privkey_file: file) end end.compact end
# File lib/net/ssh/authentication/key_manager.rb, line 297 def process_identity_loading_error(identity, e) case identity[:load_from] when :pubkey_file error { "could not load public key file `#{identity[:pubkey_file]}': #{e.class} (#{e.message})" } when :privkey_file error { "could not load private key file `#{identity[:privkey_file]}': #{e.class} (#{e.message})" } else raise e end end
# File lib/net/ssh/authentication/key_manager.rb, line 251 def readable_file?(path) File.file?(path) && File.readable?(path) end