Class: Orthrus::SSH::Agent

Inherits:
Object
  • Object
show all
Defined in:
lib/orthrus/ssh/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).

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_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
ID =
"SSH-2.0-Ruby/Orthrus #{RUBY_PLATFORM}"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initializeAgent

Creates a new Agent object.



54
55
56
# File 'lib/orthrus/ssh/agent.rb', line 54

def initialize
  @socket = nil
end

Instance Attribute Details

#socketObject (readonly)

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



38
39
40
# File 'lib/orthrus/ssh/agent.rb', line 38

def socket
  @socket
end

Class Method Details

.available?Boolean

Returns:

  • (Boolean)


49
50
51
# File 'lib/orthrus/ssh/agent.rb', line 49

def self.available?
  ENV.key?("SSH_AUTH_SOCK") && File.exists?(ENV['SSH_AUTH_SOCK'])
end

.connectObject

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



42
43
44
45
46
47
# File 'lib/orthrus/ssh/agent.rb', line 42

def self.connect
  agent = new
  agent.connect!
  agent.negotiate!
  agent
end

Instance Method Details

#closeObject

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



114
115
116
# File 'lib/orthrus/ssh/agent.rb', line 114

def close
  @socket.close
end

#connect!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).



62
63
64
65
66
67
68
# File 'lib/orthrus/ssh/agent.rb', line 62

def connect!
  begin
    @socket = UNIXSocket.open(ENV['SSH_AUTH_SOCK'])
  rescue
    raise AgentNotAvailable, $!.message
  end
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:



88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/orthrus/ssh/agent.rb', line 88

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 = Buffer.new(body.read_string).read_key
    case key
    when OpenSSL::PKey::RSA
      key = RSAPublicKey.new key
    when OpenSSL::PKey::DSA
      key = DSAPublicKey.new key
    else
      raise AgentError, "Unknown key type - #{key.class}"
    end

    key.comment = body.read_string
    identities.push key
  end

  return identities
end

#negotiate!Object

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



74
75
76
77
78
79
80
81
82
83
# File 'lib/orthrus/ssh/agent.rb', line 74

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

  if type == SSH2_AGENT_VERSION_RESPONSE
    raise NotImplementedError, "SSH2 agents are not yet supported"
  elsif type != SSH_AGENT_RSA_IDENTITIES_ANSWER1 && type != SSH_AGENT_RSA_IDENTITIES_ANSWER2
    raise AgentError, "unknown response from agent: #{type}, #{body.to_s.inspect}"
  end
end

#sign(key, data, b64armor = false) ⇒ Object

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



120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/orthrus/ssh/agent.rb', line 120

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

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

  b = Buffer.new reply.read_string
  type = b.read_string
  sign = b.read_string

  sign = Utils.encode64 sign if b64armor

  [type, sign]
end