Class: SSHKey

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

Constant Summary collapse

SSH_TYPES =
{"rsa" => "ssh-rsa", "dsa" => "ssh-dss"}
SSH_CONVERSION =
{"rsa" => ["e", "n"], "dsa" => ["p", "q", "g", "pub_key"]}
VERSION =
"1.4.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



135
136
137
138
139
140
141
142
143
144
145
# File 'lib/sshkey.rb', line 135

def initialize(private_key, options = {})
  @passphrase = options[:passphrase]
  @comment    = options[:comment] || ""
  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.



11
12
13
# File 'lib/sshkey.rb', line 11

def comment
  @comment
end

#key_objectObject (readonly)

Returns the value of attribute key_object.



10
11
12
# File 'lib/sshkey.rb', line 10

def key_object
  @key_object
end

#passphraseObject

Returns the value of attribute passphrase.



11
12
13
# File 'lib/sshkey.rb', line 11

def passphrase
  @passphrase
end

#typeObject (readonly)

Returns the value of attribute type.



10
11
12
# File 'lib/sshkey.rb', line 10

def type
  @type
end

Class Method Details

.fingerprintObject

Fingerprints

Accepts either a public or private key

MD5 fingerprint for the given SSH key



89
90
91
92
93
94
95
# File 'lib/sshkey.rb', line 89

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



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

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



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

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



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

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

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


50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
# File 'lib/sshkey.rb', line 50

def valid_ssh_public_key?(ssh_public_key)
  ssh_type, encoded_key = parse_ssh_public_key(ssh_public_key)

  type = SSH_TYPES.invert[ssh_type]
  prefix = [0,0,0,7].pack("C*")
  decoded = Base64.decode64(encoded_key)

  # Base64 decoding is too permissive, so we should validate if encoding is correct
  return false unless Base64.encode64(decoded).gsub("\n", "") == encoded_key
  return false unless decoded.sub!(/^#{prefix}#{ssh_type}/, "")

  unpacked = decoded.unpack("C*")
  data = []
  index = 0
  until unpacked[index].nil?
    datum_size = from_byte_array unpacked[index..index+4-1], 4
    index = index + 4
    datum = from_byte_array unpacked[index..index+datum_size-1], datum_size
    data << datum
    index = index + datum_size
  end

  SSH_CONVERSION[type].size == data.size
rescue
  false
end

Instance Method Details

#bitsObject

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



192
193
194
# File 'lib/sshkey.rb', line 192

def bits
  key_object.to_text.match(/Private-Key:\s\((\d*)\sbit\)/)[1].to_i
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



159
160
161
162
# File 'lib/sshkey.rb', line 159

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



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

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



150
151
152
# File 'lib/sshkey.rb', line 150

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



167
168
169
# File 'lib/sshkey.rb', line 167

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



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
# File 'lib/sshkey.rb', line 213

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



187
188
189
# File 'lib/sshkey.rb', line 187

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

#ssh_public_keyObject

SSH public key



174
175
176
# File 'lib/sshkey.rb', line 174

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