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



184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/sshkey.rb', line 184

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.



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

def comment
  @comment
end

#directivesObject

Returns the value of attribute directives.



319
320
321
# File 'lib/sshkey.rb', line 319

def directives
  @directives
end

#key_objectObject (readonly)

Returns the value of attribute key_object.



172
173
174
# File 'lib/sshkey.rb', line 172

def key_object
  @key_object
end

#passphraseObject

Returns the value of attribute passphrase.



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

def passphrase
  @passphrase
end

#typeObject (readonly)

Returns the value of attribute type.



172
173
174
# File 'lib/sshkey.rb', line 172

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

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



98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
# File 'lib/sshkey.rb', line 98

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



255
256
257
# File 'lib/sshkey.rb', line 255

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



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

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



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

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



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

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



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

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



276
277
278
279
280
281
282
283
284
285
286
287
288
289
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
# File 'lib/sshkey.rb', line 276

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



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

def sha1_fingerprint
  Digest::SHA1.hexdigest(ssh_public_key_conversion).gsub(/(.{2})(?=.)/, '\1:\2')
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’



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

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



224
225
226
# File 'lib/sshkey.rb', line 224

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