Class: Net::SSH::Authentication::KeyManager

Inherits:
Object
  • Object
show all
Includes:
Loggable
Defined in:
lib/net/ssh/authentication/key_manager.rb

Overview

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.

Instance Attribute Summary collapse

Attributes included from Loggable

#logger

Instance Method Summary collapse

Methods included from Loggable

#debug, #error, #fatal, #info, #lwarn

Constructor Details

#initialize(logger, options = {}) ⇒ KeyManager

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.



44
45
46
47
48
49
50
51
52
53
# 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

Instance Attribute Details

#key_dataObject (readonly)

The list of user key data that will be examined



30
31
32
# File 'lib/net/ssh/authentication/key_manager.rb', line 30

def key_data
  @key_data
end

#key_filesObject (readonly)

The list of user key files that will be examined



27
28
29
# File 'lib/net/ssh/authentication/key_manager.rb', line 27

def key_files
  @key_files
end

#keycert_filesObject (readonly)

The list of user key certificate files that will be examined



33
34
35
# File 'lib/net/ssh/authentication/key_manager.rb', line 33

def keycert_files
  @keycert_files
end

#known_identitiesObject (readonly)

The map of loaded identities



36
37
38
# File 'lib/net/ssh/authentication/key_manager.rb', line 36

def known_identities
  @known_identities
end

#optionsObject (readonly)

The map of options that were passed to the key-manager



39
40
41
# File 'lib/net/ssh/authentication/key_manager.rb', line 39

def options
  @options
end

Instance Method Details

#add(key_file) ⇒ Object

Add the given key_file to the list of key files that will be used.



67
68
69
70
# 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_key_data(key_data_) ⇒ Object

Add the given key_file to the list of keys that will be used.



79
80
81
82
# 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_keycert(keycert_file) ⇒ Object

Add the given keycert_file to the list of keycert files that will be used.



73
74
75
76
# 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

#agentObject

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.



215
216
217
218
219
220
221
222
# 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!Object

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.



59
60
61
62
63
64
# File 'lib/net/ssh/authentication/key_manager.rb', line 59

def clear!
  key_files.clear
  key_data.clear
  known_identities.clear
  self
end

#each_identityObject

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.



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
# 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

#finishObject

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.



93
94
95
96
# File 'lib/net/ssh/authentication/key_manager.rb', line 93

def finish
  @agent.close if @agent
  @agent = nil
end

#no_keys?Boolean

Returns:

  • (Boolean)


224
225
226
# File 'lib/net/ssh/authentication/key_manager.rb', line 224

def no_keys?
  key_files.empty? && key_data.empty?
end

#sign(identity, data, sig_alg = nil) ⇒ Object

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.

Raises:



161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
# 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

#use_agent=(use_agent) ⇒ Object

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.



207
208
209
210
# File 'lib/net/ssh/authentication/key_manager.rb', line 207

def use_agent=(use_agent)
  finish if !use_agent
  @use_agent = use_agent
end

#use_agent?Boolean

Identifies whether the ssh-agent will be used or not.

Returns:

  • (Boolean)


200
201
202
# File 'lib/net/ssh/authentication/key_manager.rb', line 200

def use_agent?
  @use_agent
end