Class: Net::SSH::Authentication::Agent

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

Overview

This class implements a simple client for the ssh-agent protocol. It does not implement any specific protocol, but instead copies the behavior of the ssh-agent functions in the OpenSSH library (3.8).

This means that although it behaves like a SSH1 client, it also has some SSH2 functionality (like signing data).

Defined Under Namespace

Modules: Comment

Constant Summary collapse

SSH2_AGENT_REQUEST_VERSION =
1
SSH2_AGENT_REQUEST_IDENTITIES =
11
SSH2_AGENT_IDENTITIES_ANSWER =
12
SSH2_AGENT_SIGN_REQUEST =
13
SSH2_AGENT_SIGN_RESPONSE =
14
SSH2_AGENT_ADD_IDENTITY =
17
SSH2_AGENT_REMOVE_IDENTITY =
18
SSH2_AGENT_REMOVE_ALL_IDENTITIES =
19
SSH2_AGENT_LOCK =
22
SSH2_AGENT_UNLOCK =
23
SSH2_AGENT_ADD_ID_CONSTRAINED =
25
SSH2_AGENT_FAILURE =
30
SSH2_AGENT_VERSION_RESPONSE =
103
SSH_COM_AGENT2_FAILURE =
102
SSH_AGENT_REQUEST_RSA_IDENTITIES =
1
SSH_AGENT_RSA_IDENTITIES_ANSWER1 =
2
SSH_AGENT_RSA_IDENTITIES_ANSWER2 =
5
SSH_AGENT_FAILURE =
5
SSH_AGENT_SUCCESS =
6
SSH_AGENT_CONSTRAIN_LIFETIME =
1
SSH_AGENT_CONSTRAIN_CONFIRM =
2
SSH_AGENT_RSA_SHA2_256 =
0x02
SSH_AGENT_RSA_SHA2_512 =
0x04

Instance Attribute Summary collapse

Attributes included from Loggable

#logger

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Loggable

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

Constructor Details

#initialize(logger = nil) ⇒ Agent

Creates a new Agent object, using the optional logger instance to report status.



77
78
79
# File 'lib/net/ssh/authentication/agent.rb', line 77

def initialize(logger = nil)
  self.logger = logger
end

Instance Attribute Details

#socketObject (readonly)

The underlying socket being used to communicate with the SSH agent.



64
65
66
# File 'lib/net/ssh/authentication/agent.rb', line 64

def socket
  @socket
end

Class Method Details

.connect(logger = nil, agent_socket_factory = nil, identity_agent = nil) ⇒ Object

Instantiates a new agent object, connects to a running SSH agent, negotiates the agent protocol version, and returns the agent object.



68
69
70
71
72
73
# File 'lib/net/ssh/authentication/agent.rb', line 68

def self.connect(logger = nil, agent_socket_factory = nil, identity_agent = nil)
  agent = new(logger)
  agent.connect!(agent_socket_factory, identity_agent)
  agent.negotiate!
  agent
end

Instance Method Details

#add_identity(priv_key, comment, lifetime: nil, confirm: false) ⇒ Object

Adds the private key with comment to the agent. If lifetime is given, the key will automatically be removed after lifetime seconds. If confirm is true, confirmation will be required for each agent signing operation.

Raises:



170
171
172
173
174
175
176
177
178
179
180
181
182
# File 'lib/net/ssh/authentication/agent.rb', line 170

def add_identity(priv_key, comment, lifetime: nil, confirm: false)
  constraints = Buffer.new
  if lifetime
    constraints.write_byte(SSH_AGENT_CONSTRAIN_LIFETIME)
    constraints.write_long(lifetime)
  end
  constraints.write_byte(SSH_AGENT_CONSTRAIN_CONFIRM) if confirm

  req_type = constraints.empty? ? SSH2_AGENT_ADD_IDENTITY : SSH2_AGENT_ADD_ID_CONSTRAINED
  type, = send_and_wait(req_type, :string, priv_key.ssh_type, :raw, blob_for_add(priv_key),
                        :string, comment, :raw, constraints)
  raise AgentError, "could not add identity to agent" if type != SSH_AGENT_SUCCESS
end

#closeObject

Closes this socket. This agent reference is no longer able to query the agent.



150
151
152
# File 'lib/net/ssh/authentication/agent.rb', line 150

def close
  @socket.close
end

#connect!(agent_socket_factory = nil, identity_agent = nil) ⇒ Object

Connect to the agent process using the socket factory and socket name given by the attribute writers. If the agent on the other end of the socket reports that it is an SSH2-compatible agent, this will fail (it only supports the ssh-agent distributed by OpenSSH).



85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
# File 'lib/net/ssh/authentication/agent.rb', line 85

def connect!(agent_socket_factory = nil, identity_agent = nil)
  debug { "connecting to ssh-agent" }
  @socket =
    if agent_socket_factory
      agent_socket_factory.call
    elsif identity_agent
      unix_socket_class.open(File.expand_path(identity_agent))
    elsif ENV['SSH_AUTH_SOCK'] && unix_socket_class
      unix_socket_class.open(File.expand_path(ENV['SSH_AUTH_SOCK']))
    elsif Gem.win_platform? && RUBY_ENGINE != "jruby"
      Pageant::Socket.open
    else
      raise AgentNotAvailable, "Agent not configured"
    end
rescue StandardError => e
  error { "could not connect to ssh-agent: #{e.message}" }
  raise AgentNotAvailable, $!.message
end

#identitiesObject

Return an array of all identities (public keys) known to the agent. Each key returned is augmented with a comment property which is set to the comment returned by the agent for that key.

Raises:



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
# File 'lib/net/ssh/authentication/agent.rb', line 122

def identities
  type, body = send_and_wait(SSH2_AGENT_REQUEST_IDENTITIES)
  raise AgentError, "could not get identity count" if agent_failed(type)
  raise AgentError, "bad authentication reply: #{type}" if type != SSH2_AGENT_IDENTITIES_ANSWER

  identities = []
  body.read_long.times do
    key_str = body.read_string
    comment_str = body.read_string
    begin
      key = Buffer.new(key_str).read_key
      if key.nil?
        error { "ignoring invalid key: #{comment_str}" }
        next
      end
      key.extend(Comment)
      key.comment = comment_str
      identities.push key
    rescue NotImplementedError => e
      error { "ignoring unimplemented key:#{e.message} #{comment_str}" }
    end
  end

  return identities
end

#lock(password) ⇒ Object

lock the ssh agent with password

Raises:



197
198
199
200
# File 'lib/net/ssh/authentication/agent.rb', line 197

def lock(password)
  type, = send_and_wait(SSH2_AGENT_LOCK, :string, password)
  raise AgentError, "could not lock agent" if type != SSH_AGENT_SUCCESS
end

#negotiate!Object

Attempts to negotiate the SSH agent protocol version. Raises an error if the version could not be negotiated successfully.

Raises:



106
107
108
109
110
111
112
113
114
115
116
117
# File 'lib/net/ssh/authentication/agent.rb', line 106

def negotiate!
  # determine what type of agent we're communicating with
  type, body = send_and_wait(SSH2_AGENT_REQUEST_VERSION, :string, Transport::ServerVersion::PROTO_VERSION)

  raise AgentNotAvailable, "SSH2 agents are not yet supported" if type == SSH2_AGENT_VERSION_RESPONSE

  if type == SSH2_AGENT_FAILURE
    debug { "Unexpected response type==#{type}, this will be ignored" }
  elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2
    raise AgentNotAvailable, "unknown response from agent: #{type}, #{body.to_s.inspect}"
  end
end

#remove_all_identitiesObject

Removes all identities from the agent.

Raises:



191
192
193
194
# File 'lib/net/ssh/authentication/agent.rb', line 191

def remove_all_identities
  type, = send_and_wait(SSH2_AGENT_REMOVE_ALL_IDENTITIES)
  raise AgentError, "could not remove all identity from agent" if type != SSH_AGENT_SUCCESS
end

#remove_identity(key) ⇒ Object

Removes key from the agent.

Raises:



185
186
187
188
# File 'lib/net/ssh/authentication/agent.rb', line 185

def remove_identity(key)
  type, = send_and_wait(SSH2_AGENT_REMOVE_IDENTITY, :string, key.to_blob)
  raise AgentError, "could not remove identity from agent" if type != SSH_AGENT_SUCCESS
end

#sign(key, data, flags = 0) ⇒ Object

Using the agent and the given public key, sign the given data. The signature is returned in SSH2 format.

Raises:



156
157
158
159
160
161
162
163
# File 'lib/net/ssh/authentication/agent.rb', line 156

def sign(key, data, flags = 0)
  type, reply = send_and_wait(SSH2_AGENT_SIGN_REQUEST, :string, Buffer.from(:key, key), :string, data, :long, flags)

  raise AgentError, "agent could not sign data with requested identity" if agent_failed(type)
  raise AgentError, "bad authentication response #{type}" if type != SSH2_AGENT_SIGN_RESPONSE

  return reply.read_string
end

#unlock(password) ⇒ Object

unlock the ssh agent with password

Raises:



203
204
205
206
# File 'lib/net/ssh/authentication/agent.rb', line 203

def unlock(password)
  type, = send_and_wait(SSH2_AGENT_UNLOCK, :string, password)
  raise AgentError, "could not unlock agent" if type != SSH_AGENT_SUCCESS
end