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 =
{
  "ssh-rsa" => "rsa",
  "ssh-dss" => "dsa",
  "ssh-ed25519" => "ed25519",
  "ecdsa-sha2-nistp256" => "ecdsa",
  "ecdsa-sha2-nistp384" => "ecdsa",
  "ecdsa-sha2-nistp521" => "ecdsa",
}
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.9.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



210
211
212
213
214
215
216
217
218
219
220
221
# File 'lib/sshkey.rb', line 210

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.



199
200
201
# File 'lib/sshkey.rb', line 199

def comment
  @comment
end

#directivesObject

Returns the value of attribute directives.



350
351
352
# File 'lib/sshkey.rb', line 350

def directives
  @directives
end

#key_objectObject (readonly)

Returns the value of attribute key_object.



198
199
200
# File 'lib/sshkey.rb', line 198

def key_object
  @key_object
end

#passphraseObject

Returns the value of attribute passphrase.



199
200
201
# File 'lib/sshkey.rb', line 199

def passphrase
  @passphrase
end

#typeObject (readonly)

Returns the value of attribute type.



198
199
200
# File 'lib/sshkey.rb', line 198

def type
  @type
end

Class Method Details

.fingerprintObject

Fingerprints

Accepts either a public or private key

MD5 fingerprint for the given SSH key



98
99
100
101
102
103
104
# File 'lib/sshkey.rb', line 98

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



34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
# File 'lib/sshkey.rb', line 34

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



91
92
93
94
95
96
97
# File 'lib/sshkey.rb', line 91

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



101
102
103
104
105
106
107
# File 'lib/sshkey.rb', line 101

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



110
111
112
113
114
115
116
# File 'lib/sshkey.rb', line 110

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.…”



82
83
84
# File 'lib/sshkey.rb', line 82

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:



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
# File 'lib/sshkey.rb', line 124

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)


58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
# File 'lib/sshkey.rb', line 58

def valid_ssh_public_key?(ssh_public_key)
  ssh_type, encoded_key = parse_ssh_public_key(ssh_public_key)
  sections = unpacked_byte_array(ssh_type, encoded_key)
  case ssh_type
    when "ssh-rsa", "ssh-dss"
      sections.size == SSH_CONVERSION[SSH_TYPES[ssh_type]].size
    when "ssh-ed25519"
      sections.size == 1 && sections[0].num_bytes == 32 # https://tools.ietf.org/id/draft-bjh21-ssh-ed25519-00.html#rfc.section.4
    when "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521"
      sections.size == 2                                # https://tools.ietf.org/html/rfc5656#section-3.1
    else
      false
  end
rescue
  false
end

Instance Method Details

#bitsObject

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



286
287
288
# File 'lib/sshkey.rb', line 286

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



235
236
237
238
# File 'lib/sshkey.rb', line 235

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



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

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



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

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



243
244
245
# File 'lib/sshkey.rb', line 243

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. | -----------------



307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
# File 'lib/sshkey.rb', line 307

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



276
277
278
# File 'lib/sshkey.rb', line 276

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



281
282
283
# File 'lib/sshkey.rb', line 281

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’



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

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



250
251
252
# File 'lib/sshkey.rb', line 250

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