Class: SshTresor::Agent

Inherits:
Object
  • Object
show all
Defined in:
lib/ssh_tresor/agent.rb

Constant Summary collapse

SSH_AGENT_FAILURE =
5
SSH_AGENTC_REQUEST_IDENTITIES =
11
SSH_AGENT_IDENTITIES_ANSWER =
12
SSH_AGENTC_SIGN_REQUEST =
13
SSH_AGENT_SIGN_RESPONSE =
14
SSH_AGENT_SIGN_REQUEST_RSA_SHA2_256 =
2

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(socket) ⇒ Agent

Returns a new instance of Agent.



96
97
98
# File 'lib/ssh_tresor/agent.rb', line 96

def initialize(socket)
  @socket = socket
end

Class Method Details

.bit_length(bytes) ⇒ Object



89
90
91
92
93
94
# File 'lib/ssh_tresor/agent.rb', line 89

def self.bit_length(bytes)
  trimmed = bytes.b.sub(/\A\x00+/n, "")
  return 0 if trimmed.empty?

  ((trimmed.bytesize - 1) * 8) + trimmed.getbyte(0).bit_length
end

.connectObject



55
56
57
58
59
60
61
62
# File 'lib/ssh_tresor/agent.rb', line 55

def self.connect
  socket_path = ENV["SSH_AUTH_SOCK"]
  raise AgentError, "SSH agent not available\nHint: Is SSH_AUTH_SOCK set? Try running: eval $(ssh-agent) && ssh-add" if socket_path.nil? || socket_path.empty?

  new(UNIXSocket.new(socket_path))
rescue SystemCallError => e
  raise AgentError, "Failed to connect to SSH agent: #{e.message}"
end

.format_key_type(blob) ⇒ Object



64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# File 'lib/ssh_tresor/agent.rb', line 64

def self.format_key_type(blob)
  reader = SSHEncoding::Reader.new(blob)
  type = reader.string

  case type
  when "ssh-ed25519"
    "ED25519"
  when "ssh-rsa"
    reader.string
    n = reader.string
    "RSA-#{bit_length(n)}"
  when /\Aecdsa-sha2-/
    curve = reader.string
    "ECDSA-#{curve.delete_prefix("nistp")}"
  when "sk-ssh-ed25519@openssh.com"
    "SK-ED25519"
  when "sk-ecdsa-sha2-nistp256@openssh.com"
    "SK-ECDSA-256"
  else
    type.upcase
  end
rescue Error
  "UNKNOWN"
end

Instance Method Details

#find_key(fingerprint) ⇒ Object



120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/ssh_tresor/agent.rb', line 120

def find_key(fingerprint)
  matches = list_keys.select { |key| key.matches_fingerprint?(fingerprint) }

  case matches.length
  when 0
    raise KeyNotFound, "Key not found: #{fingerprint}\nHint: Use 'ssh-tresor list-keys' to see available keys"
  when 1
    matches.first
  else
    raise KeyNotFound, "Key not found: #{fingerprint} (ambiguous: #{matches.length} keys match this prefix, please be more specific)"
  end
end

#find_key_by_fingerprint_bytes(fingerprint_bytes) ⇒ Object



133
134
135
136
# File 'lib/ssh_tresor/agent.rb', line 133

def find_key_by_fingerprint_bytes(fingerprint_bytes)
  list_keys.find { |key| key.fingerprint_bytes == fingerprint_bytes } ||
    raise(KeyNotFound, "Key not found: SHA256:#{Base64.strict_encode64(fingerprint_bytes).delete("=")}")
end

#first_keyObject



116
117
118
# File 'lib/ssh_tresor/agent.rb', line 116

def first_key
  list_keys.first || raise(KeyNotFound, "No keys available in SSH agent\nHint: Try running: ssh-add")
end

#list_keysObject

Raises:



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/ssh_tresor/agent.rb', line 100

def list_keys
  response = request(SSHEncoding.byte(SSH_AGENTC_REQUEST_IDENTITIES))
  reader = SSHEncoding::Reader.new(response)
  type = reader.byte
  raise AgentError, "SSH agent refused identity request" if type == SSH_AGENT_FAILURE
  raise AgentError, "Unexpected SSH agent response type #{type}" unless type == SSH_AGENT_IDENTITIES_ANSWER

  count = reader.uint32
  Array.new(count) do
    blob = reader.string
    comment = reader.string.force_encoding(Encoding::UTF_8)
    comment = comment.valid_encoding? ? comment : comment.b.inspect
    AgentKey.new(blob: blob, comment: comment)
  end
end

#sign(key, data) ⇒ Object

Raises:



138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
# File 'lib/ssh_tresor/agent.rb', line 138

def sign(key, data)
  flags = key.ssh_type == "ssh-rsa" ? SSH_AGENT_SIGN_REQUEST_RSA_SHA2_256 : 0
  payload = SSHEncoding.byte(SSH_AGENTC_SIGN_REQUEST) +
            SSHEncoding.string(key.blob) +
            SSHEncoding.string(data) +
            SSHEncoding.uint32(flags)

  response = request(payload)
  reader = SSHEncoding::Reader.new(response)
  type = reader.byte
  raise AgentError, "SSH agent refused signing request" if type == SSH_AGENT_FAILURE
  raise AgentError, "Unexpected SSH agent response type #{type}" unless type == SSH_AGENT_SIGN_RESPONSE

  signature_blob = reader.string
  signature_reader = SSHEncoding::Reader.new(signature_blob)
  signature_reader.string
  signature_reader.string
end