Class: SSHKey

Inherits:
Object
  • Object
show all
Defined in:
lib/sshkey.rb,
lib/sshkey/version.rb,
lib/sshkey/exception.rb

Defined Under Namespace

Classes: PublicKeyError

Constant Summary collapse

SSH_TYPES =
{"rsa" => "ssh-rsa", "dsa" => "ssh-dss"}
SSH_CONVERSION =
{"rsa" => ["e", "n"], "dsa" => ["p", "q", "g", "pub_key"]}
SSH2_LINE_LENGTH =

+1 (for line wrap ‘/’ character) must be <= 72

70
VERSION =
"1.8.0"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(private_key, options = {}) ⇒ SSHKey

Create a new SSHKey object

Parameters

  • private_key - Existing RSA or DSA private key

  • options<~Hash>

    • :comment<~String> - Comment to use for the public key, defaults to “”

    • :passphrase<~String> - If the key is encrypted, supply the passphrase

    • :directives<~Array> - Options prefixed to the public key



193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/sshkey.rb', line 193

def initialize(private_key, options = {})
  @passphrase = options[:passphrase]
  @comment    = options[:comment] || ""
  self.directives = options[:directives] || []
  begin
    @key_object = OpenSSL::PKey::RSA.new(private_key, passphrase)
    @type = "rsa"
  rescue
    @key_object = OpenSSL::PKey::DSA.new(private_key, passphrase)
    @type = "dsa"
  end
end

Instance Attribute Details

#commentObject

Returns the value of attribute comment.



182
183
184
# File 'lib/sshkey.rb', line 182

def comment
  @comment
end

#directivesObject

Returns the value of attribute directives.



333
334
335
# File 'lib/sshkey.rb', line 333

def directives
  @directives
end

#key_objectObject (readonly)

Returns the value of attribute key_object.



181
182
183
# File 'lib/sshkey.rb', line 181

def key_object
  @key_object
end

#passphraseObject

Returns the value of attribute passphrase.



182
183
184
# File 'lib/sshkey.rb', line 182

def passphrase
  @passphrase
end

#typeObject (readonly)

Returns the value of attribute type.



181
182
183
# File 'lib/sshkey.rb', line 181

def type
  @type
end

Class Method Details

.fingerprintObject

Fingerprints

Accepts either a public or private key

MD5 fingerprint for the given SSH key



81
82
83
84
85
86
87
# File 'lib/sshkey.rb', line 81

def md5_fingerprint(key)
  if key.match(/PRIVATE/)
    new(key).md5_fingerprint
  else
    Digest::MD5.hexdigest(decoded_key(key)).gsub(fingerprint_regex, '\1:\2')
  end
end

.generate(options = {}) ⇒ Object

Generate a new keypair and return an SSHKey object

The default behavior when providing no options will generate a 2048-bit RSA keypair.

Parameters

  • options<~Hash>:

    • :type<~String> - “rsa” or “dsa”, “rsa” by default

    • :bits<~Integer> - Bit length

    • :comment<~String> - Comment to use for the public key, defaults to “”

    • :passphrase<~String> - Encrypt the key with this passphrase



27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
# File 'lib/sshkey.rb', line 27

def generate(options = {})
  type   = options[:type] || "rsa"

  # JRuby modulus size must range from 512 to 1024
  default_bits = type == "rsa" ? 2048 : 1024

  bits   = options[:bits] || default_bits
  cipher = OpenSSL::Cipher::Cipher.new("AES-128-CBC") if options[:passphrase]

  case type.downcase
  when "rsa" then new(OpenSSL::PKey::RSA.generate(bits).to_pem(cipher, options[:passphrase]), options)
  when "dsa" then new(OpenSSL::PKey::DSA.generate(bits).to_pem(cipher, options[:passphrase]), options)
  else
    raise "Unknown key type: #{type}"
  end
end

.md5_fingerprint(key) ⇒ Object

Fingerprints

Accepts either a public or private key

MD5 fingerprint for the given SSH key



74
75
76
77
78
79
80
# File 'lib/sshkey.rb', line 74

def md5_fingerprint(key)
  if key.match(/PRIVATE/)
    new(key).md5_fingerprint
  else
    Digest::MD5.hexdigest(decoded_key(key)).gsub(fingerprint_regex, '\1:\2')
  end
end

.sha1_fingerprint(key) ⇒ Object

SHA1 fingerprint for the given SSH key



84
85
86
87
88
89
90
# File 'lib/sshkey.rb', line 84

def sha1_fingerprint(key)
  if key.match(/PRIVATE/)
    new(key).sha1_fingerprint
  else
    Digest::SHA1.hexdigest(decoded_key(key)).gsub(fingerprint_regex, '\1:\2')
  end
end

.sha256_fingerprint(key) ⇒ Object

SHA256 fingerprint for the given SSH key



93
94
95
96
97
98
99
# File 'lib/sshkey.rb', line 93

def sha256_fingerprint(key)
  if key.match(/PRIVATE/)
    new(key).sha256_fingerprint
  else
    Base64.encode64(Digest::SHA256.digest(decoded_key(key))).gsub("\n", "")
  end
end

.ssh_public_key_bits(ssh_public_key) ⇒ Object

Bits

Returns ssh public key bits or false depending on the validity of the public key provided

Parameters

  • ssh_public_key<~String> - “ssh-rsa AAAAB3NzaC1yc2EA.…”



65
66
67
# File 'lib/sshkey.rb', line 65

def ssh_public_key_bits(ssh_public_key)
  unpacked_byte_array( *parse_ssh_public_key(ssh_public_key) ).last.num_bytes * 8
end

.ssh_public_key_to_ssh2_public_key(ssh_public_key, headers = nil) ⇒ Object

Convert an existing SSH public key to SSH2 (RFC4716) public key

Parameters

  • ssh_public_key<~String> - “ssh-rsa AAAAB3NzaC1yc2EA.…”

  • headers<~Hash> - The Key will be used as the header-tag and the value as the header-value

Raises:



107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
# File 'lib/sshkey.rb', line 107

def ssh_public_key_to_ssh2_public_key(ssh_public_key, headers = nil)
  raise PublicKeyError, "invalid ssh public key" unless SSHKey.valid_ssh_public_key?(ssh_public_key)

  source_format, source_key = parse_ssh_public_key(ssh_public_key)

  # Add a 'Comment' Header Field unless others are explicitly passed in
  if source_comment = ssh_public_key.split(source_key)[1]
    headers = {'Comment' => source_comment.strip} if headers.nil? && !source_comment.empty?
  end
  header_fields = build_ssh2_headers(headers)

  ssh2_key = "---- BEGIN SSH2 PUBLIC KEY ----\n"
  ssh2_key << header_fields unless header_fields.nil?
  ssh2_key << source_key.scan(/.{1,#{SSH2_LINE_LENGTH}}/).join("\n")
  ssh2_key << "\n---- END SSH2 PUBLIC KEY ----"
end

.valid_ssh_public_key?(ssh_public_key) ⇒ Boolean

Validate an existing SSH public key

Returns true or false depending on the validity of the public key provided

Parameters

  • ssh_public_key<~String> - “ssh-rsa AAAAB3NzaC1yc2EA.…”

Returns:

  • (Boolean)


51
52
53
54
55
56
# File 'lib/sshkey.rb', line 51

def valid_ssh_public_key?(ssh_public_key)
  ssh_type, encoded_key = parse_ssh_public_key(ssh_public_key)
  SSH_CONVERSION[SSH_TYPES.invert[ssh_type]].size == unpacked_byte_array(ssh_type, encoded_key).size
rescue
  false
end

Instance Method Details

#bitsObject

Determine the length (bits) of the key as an integer



269
270
271
# File 'lib/sshkey.rb', line 269

def bits
  self.class.ssh_public_key_bits(ssh_public_key)
end

#encrypted_private_keyObject

Fetch the encrypted RSA/DSA private key using the passphrase provided

If no passphrase is set, returns the unencrypted private key



218
219
220
221
# File 'lib/sshkey.rb', line 218

def encrypted_private_key
  return private_key unless passphrase
  key_object.to_pem(OpenSSL::Cipher::Cipher.new("AES-128-CBC"), passphrase)
end

#md5_fingerprintObject Also known as: fingerprint

Fingerprints

MD5 fingerprint for the given SSH public key



253
254
255
# File 'lib/sshkey.rb', line 253

def md5_fingerprint
  Digest::MD5.hexdigest(ssh_public_key_conversion).gsub(/(.{2})(?=.)/, '\1:\2')
end

#private_keyObject Also known as: rsa_private_key, dsa_private_key

Fetch the RSA/DSA private key

rsa_private_key and dsa_private_key are aliased for backward compatibility



209
210
211
# File 'lib/sshkey.rb', line 209

def private_key
  key_object.to_pem
end

#public_keyObject Also known as: rsa_public_key, dsa_public_key

Fetch the RSA/DSA public key

rsa_public_key and dsa_public_key are aliased for backward compatibility



226
227
228
# File 'lib/sshkey.rb', line 226

def public_key
  key_object.public_key.to_pem
end

#randomartObject

Randomart

Generate OpenSSH compatible ASCII art fingerprints See www.opensource.apple.com/source/OpenSSH/OpenSSH-175/openssh/key.c (key_fingerprint_randomart function)

Example: –[ RSA 2048]—- |o+ o.. | |..+.o | | ooo | |.++. o | |o + S | |.. + o . | | . + . | | . . | | Eo. | -----------------



290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
# File 'lib/sshkey.rb', line 290

def randomart
  fieldsize_x = 17
  fieldsize_y = 9
  x = fieldsize_x / 2
  y = fieldsize_y / 2
  raw_digest = Digest::MD5.digest(ssh_public_key_conversion)
  num_bytes = raw_digest.bytesize

  field = Array.new(fieldsize_x) { Array.new(fieldsize_y) {0} }

  raw_digest.bytes.each do |byte|
    4.times do
      x += (byte & 0x1 != 0) ? 1 : -1
      y += (byte & 0x2 != 0) ? 1 : -1

      x = [[x, 0].max, fieldsize_x - 1].min
      y = [[y, 0].max, fieldsize_y - 1].min

      field[x][y] += 1 if (field[x][y] < num_bytes - 2)

      byte >>= 2
    end
  end

  field[fieldsize_x / 2][fieldsize_y / 2] = num_bytes - 1
  field[x][y] = num_bytes
  augmentation_string = " .o+=*BOX@%&#/^SE"
  output = "+--#{sprintf("[%4s %4u]", type.upcase, bits)}----+\n"
  fieldsize_y.times do |y|
    output << "|"
    fieldsize_x.times do |x|
      output << augmentation_string[[field[x][y], num_bytes].min]
    end
    output << "|"
    output << "\n"
  end
  output << "+#{"-" * fieldsize_x}+"
  output
end

#sha1_fingerprintObject

SHA1 fingerprint for the given SSH public key



259
260
261
# File 'lib/sshkey.rb', line 259

def sha1_fingerprint
  Digest::SHA1.hexdigest(ssh_public_key_conversion).gsub(/(.{2})(?=.)/, '\1:\2')
end

#sha256_fingerprintObject

SHA256 fingerprint for the given SSH public key



264
265
266
# File 'lib/sshkey.rb', line 264

def sha256_fingerprint
  Base64.encode64(Digest::SHA256.digest(ssh_public_key_conversion)).gsub("\n", "")
end

#ssh2_public_key(headers = nil) ⇒ Object

SSH2 public key (RFC4716)

Parameters

  • headers<~Hash> - Keys will be used as header-tags and values as header-values.

Examples

=> ‘2048-bit RSA created by user@example’ => ‘Private Use Value’



246
247
248
# File 'lib/sshkey.rb', line 246

def ssh2_public_key(headers = nil)
  self.class.ssh_public_key_to_ssh2_public_key(ssh_public_key, headers)
end

#ssh_public_keyObject

SSH public key



233
234
235
# File 'lib/sshkey.rb', line 233

def ssh_public_key
  [directives.join(",").strip, SSH_TYPES[type], Base64.encode64(ssh_public_key_conversion).gsub("\n", ""), comment].join(" ").strip
end