sshkey-3.0.0/0000755000004100000410000000000014570752160013057 5ustar www-datawww-datasshkey-3.0.0/.gitignore0000644000004100000410000000004114570752160015042 0ustar www-datawww-data*.gem .bundle Gemfile.lock pkg/* sshkey-3.0.0/sshkey.gemspec0000644000004100000410000000217514570752160015737 0ustar www-datawww-data# -*- encoding: utf-8 -*- $:.push File.expand_path("../lib", __FILE__) require "sshkey/version" Gem::Specification.new do |s| s.name = "sshkey" s.version = SSHKey::VERSION s.platform = Gem::Platform::RUBY s.authors = ["James Miller"] s.email = ["bensie@gmail.com"] s.homepage = "https://github.com/bensie/sshkey" s.summary = %q{SSH private/public key generator in Ruby} s.description = %q{Generate private/public SSH keypairs using pure Ruby} s.licenses = ["MIT"] # ECDSA requires OpenSSL::PKey::EC::Point#to_octet_string # to_octet string was added in Ruby/OpenSSL 2.1.0 https://github.com/ruby/openssl/blob/master/History.md#version-210 # Ruby 2.5 Updated Ruby/OpenSSL from to 2.1.0 https://github.com/ruby/ruby/blob/v2_5_0/NEWS s.required_ruby_version = '>= 2.5' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } s.require_paths = ["lib"] s.add_development_dependency("rake") s.add_development_dependency("test-unit") end sshkey-3.0.0/.github/0000755000004100000410000000000014570752160014417 5ustar www-datawww-datasshkey-3.0.0/.github/workflows/0000755000004100000410000000000014570752160016454 5ustar www-datawww-datasshkey-3.0.0/.github/workflows/ci.yml0000644000004100000410000000073114570752160017573 0ustar www-datawww-dataname: CI on: push: branches: [main] pull_request: branches: [main] permissions: contents: read jobs: test: runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: ["2.5", "2.6", "2.7", "3.0", "3.1", "3.2", "3.3", jruby-9.3, jruby-9.4] steps: - uses: actions/checkout@v3 - uses: ruby/setup-ruby@v1 with: bundler-cache: true ruby-version: ${{ matrix.ruby }} - run: bundle exec rake sshkey-3.0.0/lib/0000755000004100000410000000000014570752160013625 5ustar www-datawww-datasshkey-3.0.0/lib/sshkey.rb0000644000004100000410000005450114570752160015465 0ustar www-datawww-datarequire 'openssl' require 'base64' require 'digest/md5' require 'digest/sha1' require 'digest/sha2' def jruby_not_implemented(msg) raise NotImplementedError.new "jruby-openssl #{JOpenSSL::VERSION}: #{msg}" if RUBY_PLATFORM == "java" end # Monkey patch OpenSSL::PKey::EC to provide convenience methods usable in this gem class OpenSSL::PKey::EC def identifier # NOTE: Unable to find these constants within OpenSSL, so hardcode them here. # Analogous to net-ssh OpenSSL::PKey::EC::CurveNameAliasInv # https://github.com/net-ssh/net-ssh/blob/master/lib/net/ssh/transport/openssl.rb#L147-L151 case group.curve_name when "prime256v1" then "nistp256" # https://stackoverflow.com/a/41953717 when "secp256r1" then "nistp256" # JRuby when "secp384r1" then "nistp384" when "secp521r1" then "nistp521" else raise "Unknown curve name: #{public_key.group.curve_name}" end end def q # jruby-openssl does not currently support to_octet_string # https://github.com/jruby/jruby-openssl/issues/226 jruby_not_implemented("to_octet_string is not implemented") public_key.to_octet_string(group.point_conversion_form) end end class SSHKey SSH_TYPES = { "ssh-rsa" => "rsa", "ssh-dss" => "dsa", "ssh-ed25519" => "ed25519", "ecdsa-sha2-nistp256" => "ecdsa", "ecdsa-sha2-nistp384" => "ecdsa", "ecdsa-sha2-nistp521" => "ecdsa", } SSHFP_TYPES = { "rsa" => 1, "dsa" => 2, "ecdsa" => 3, "ed25519" => 4, } ECDSA_CURVES = { 256 => "prime256v1", # https://stackoverflow.com/a/41953717 384 => "secp384r1", 521 => "secp521r1", } VALID_BITS = { "ecdsa" => ECDSA_CURVES.keys, } # Accessor methods are defined in: # - RSA: https://github.com/ruby/openssl/blob/master/ext/openssl/ossl_pkey_rsa.c # - DSA: https://github.com/ruby/openssl/blob/master/ext/openssl/ossl_pkey_dsa.c # - ECDSA: monkey patch OpenSSL::PKey::EC above SSH_CONVERSION = {"rsa" => ["e", "n"], "dsa" => ["p", "q", "g", "pub_key"], "ecdsa" => ["identifier", "q"]} SSH2_LINE_LENGTH = 70 # +1 (for line wrap '/' character) must be <= 72 class << self # 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 # def generate(options = {}) type = options[:type] || "rsa" # JRuby modulus size must range from 512 to 1024 case type when "rsa" then default_bits = 2048 when "ecdsa" then default_bits = 256 else default_bits = 1024 end bits = options[:bits] || default_bits cipher = OpenSSL::Cipher.new("AES-128-CBC") if options[:passphrase] raise "Bits must either: #{VALID_BITS[type.downcase].join(', ')}" unless VALID_BITS[type.downcase].nil? || VALID_BITS[type.downcase].include?(bits) case type.downcase when "rsa" key_object = OpenSSL::PKey::RSA.generate(bits) when "dsa" key_object = OpenSSL::PKey::DSA.generate(bits) when "ecdsa" # jruby-openssl OpenSSL::PKey::EC support isn't complete # https://github.com/jruby/jruby-openssl/issues/189 jruby_not_implemented("OpenSSL::PKey::EC is not fully implemented") if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000 # https://github.com/ruby/openssl/pull/480 key_object = OpenSSL::PKey::EC.generate(ECDSA_CURVES[bits]) else key_pkey = OpenSSL::PKey::EC.new(ECDSA_CURVES[bits]) key_object = key_pkey.generate_key end else raise "Unknown key type: #{type}" end key_pem = key_object.to_pem(cipher, options[:passphrase]) new(key_pem, options) end # 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...." # 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 # 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 # 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...." # * ssh_public_key<~String> - "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTY...." # def ssh_public_key_bits(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", "ssh-ed25519" sections.last.num_bytes * 8 when "ecdsa-sha2-nistp256", "ecdsa-sha2-nistp384", "ecdsa-sha2-nistp521" raise PublicKeyError, "invalid ECDSA key" unless sections.count == 2 # https://tools.ietf.org/html/rfc5656#section-3.1 identifier = sections[0].to_s(2) q = sections[1].to_s(2) ecdsa_bits(ssh_type, identifier, q) else raise PublicKeyError, "unsupported key type #{ssh_type}" end end # Fingerprints # # Accepts either a public or private key # # MD5 fingerprint for the given SSH key 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 alias_method :fingerprint, :md5_fingerprint # SHA1 fingerprint for the given SSH key 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 for the given SSH key 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 # SSHFP records for the given SSH key def sshfp(hostname, key) if key.match(/PRIVATE/) new(key).sshfp hostname else type, encoded_key = parse_ssh_public_key(key) format_sshfp_record(hostname, SSH_TYPES[type], Base64.decode64(encoded_key)) end end # 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 # 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 def format_sshfp_record(hostname, type, key) [[Digest::SHA1, 1], [Digest::SHA256, 2]].map { |f, num| fpr = f.hexdigest(key) "#{hostname} IN SSHFP #{SSHFP_TYPES[type]} #{num} #{fpr}" }.join("\n") end private def unpacked_byte_array(ssh_type, encoded_key) prefix = [ssh_type.length].pack("N") + ssh_type decoded = Base64.decode64(encoded_key) # Base64 decoding is too permissive, so we should validate if encoding is correct unless Base64.encode64(decoded).gsub("\n", "") == encoded_key && decoded.slice!(0, prefix.length) == prefix raise PublicKeyError, "validation error" end byte_count = 0 data = [] until decoded.empty? front = decoded.slice!(0,4) size = front.unpack("N").first segment = decoded.slice!(0, size) byte_count += segment.length unless front.length == 4 && segment.length == size raise PublicKeyError, "byte array too short" end data << OpenSSL::BN.new(segment, 2) end if ssh_type == "ssh-ed25519" unless byte_count == 32 raise PublicKeyError, "validation error, ed25519 key length not OK" end end return data end def ecdsa_bits(ssh_type, identifier, q) raise PublicKeyError, "invalid ssh type" unless ssh_type == "ecdsa-sha2-#{identifier}" len_q = q.length compression_octet = q.slice(0, 1) if compression_octet == "\x04" # Point compression is off # Summary from https://www.secg.org/sec1-v2.pdf "2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion" # - the leftmost octet indicates that point compression is off # (first octet 0x04 as specified in "3.3. Output M = 04 base 16 ‖ X ‖ Y.") # - the remainder of the octet string contains the x-coordinate followed by the y-coordinate. len_x = (len_q - 1) / 2 else # Point compression is on # Summary from https://www.secg.org/sec1-v2.pdf "2.3.3 Elliptic-Curve-Point-to-Octet-String Conversion" # - the compressed y-coordinate is recovered from the leftmost octet # - the x-coordinate is recovered from the remainder of the octet string raise PublicKeyError, "invalid compression octet" unless compression_octet == "\x02" || compression_octet == "\x03" len_x = len_q - 1 end # https://www.secg.org/sec2-v2.pdf "2.1 Properties of Elliptic Curve Domain Parameters over Fp" defines # five discrete bit lengths: 192, 224, 256, 384, 521 # These bit lengths can be ascertained from the length of the packed x-coordinate. # Alternatively, these bit lengths can be derived from their associated prime constants using Math.log2(prime).ceil # against the prime constants defined in https://www.secg.org/sec2-v2.pdf case len_x when 24 then bits = 192 when 28 then bits = 224 when 32 then bits = 256 when 48 then bits = 384 when 66 then bits = 521 else raise PublicKeyError, "invalid x-coordinate length #{len_x}" end raise PublicKeyError, "invalid identifier #{identifier}" unless identifier =~ /#{bits}/ return bits end def decoded_key(key) Base64.decode64(parse_ssh_public_key(key).last) end def fingerprint_regex /(.{2})(?=.)/ end def parse_ssh_public_key(public_key) # lines starting with a '#' and empty lines are ignored as comments (as in ssh AuthorizedKeysFile) public_key = public_key.gsub(/^#.*$/, '') public_key = public_key.strip # leading and trailing whitespaces wiped out raise PublicKeyError, "newlines are not permitted between key data" if public_key =~ /\n(?!$)/ parsed = public_key.split(" ") parsed.each_with_index do |el, index| return parsed[index..(index+1)] if SSH_TYPES[el] end raise PublicKeyError, "cannot determine key type" end def build_ssh2_headers(headers = {}) return nil if headers.nil? || headers.empty? headers.keys.sort.collect do |header_tag| # header-tag must be us-ascii & <= 64 bytes and header-data must be UTF-8 & <= 1024 bytes raise PublicKeyError, "SSH2 header-tag '#{header_tag}' must be US-ASCII" unless header_tag.each_byte.all? {|b| b < 128 } raise PublicKeyError, "SSH2 header-tag '#{header_tag}' must be <= 64 bytes" unless header_tag.size <= 64 raise PublicKeyError, "SSH2 header-value for '#{header_tag}' must be <= 1024 bytes" unless headers[header_tag].size <= 1024 header_field = "#{header_tag}: #{headers[header_tag]}" header_field.scan(/.{1,#{SSH2_LINE_LENGTH}}/).join("\\\n") end.join("\n") << "\n" end end attr_reader :key_object, :type, :typestr attr_accessor :passphrase, :comment # Create a new SSHKey object # # ==== Parameters # * private_key - Existing RSA or DSA or ECDSA 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 # 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" @typestr = "ssh-rsa" rescue OpenSSL::PKey::RSAError @type = nil end return if @type begin @key_object = OpenSSL::PKey::DSA.new(private_key, passphrase) @type = "dsa" @typestr = "ssh-dss" rescue OpenSSL::PKey::DSAError @type = nil end return if @type @key_object = OpenSSL::PKey::EC.new(private_key, passphrase) @type = "ecdsa" bits = ECDSA_CURVES.invert[@key_object.group.curve_name] @typestr = "ecdsa-sha2-nistp#{bits}" end # Fetch the private key (PEM format) # # rsa_private_key and dsa_private_key are aliased for backward compatibility def private_key # jruby-openssl OpenSSL::PKey::EC support isn't complete # https://github.com/jruby/jruby-openssl/issues/189 jruby_not_implemented("OpenSSL::PKey::EC is not fully implemented") if type == "ecdsa" key_object.to_pem end alias_method :rsa_private_key, :private_key alias_method :dsa_private_key, :private_key # Fetch the encrypted RSA/DSA private key using the passphrase provided # # If no passphrase is set, returns the unencrypted private key def encrypted_private_key return private_key unless passphrase key_object.to_pem(OpenSSL::Cipher.new("AES-128-CBC"), passphrase) end # Fetch the public key (PEM format) # # rsa_public_key and dsa_public_key are aliased for backward compatibility def public_key public_key_object.to_pem end alias_method :rsa_public_key, :public_key alias_method :dsa_public_key, :public_key def public_key_object if type == "ecdsa" return nil unless key_object return nil unless key_object.group if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x30000000 && RUBY_PLATFORM != "java" # jruby-openssl does not currently support point_conversion_form # (futureproofing for if/when JRuby requires this technique to determine public key) jruby_not_implemented("point_conversion_form is not implemented") # Avoid "OpenSSL::PKey::PKeyError: pkeys are immutable on OpenSSL 3.0" # https://github.com/ruby/openssl/blob/master/History.md#version-300 # https://github.com/ruby/openssl/issues/498 # https://github.com/net-ssh/net-ssh/commit/4de6831dea4e922bf3052192eec143af015a3486 # https://github.com/ClearlyClaire/cose-ruby/commit/28ee497fa7d9d49e72d5a5e97a567c0b58fdd822 curve_name = key_object.group.curve_name return nil unless curve_name # Map to different curve_name for JRuby # (futureproofing for if/when JRuby requires this technique to determine public key) # https://github.com/jwt/ruby-jwt/issues/362#issuecomment-722938409 curve_name = "prime256v1" if curve_name == "secp256r1" && RUBY_PLATFORM == "java" # Construct public key OpenSSL::PKey::EC from OpenSSL::PKey::EC::Point public_key_point = key_object.public_key # => OpenSSL::PKey::EC::Point return nil unless public_key_point asn1 = OpenSSL::ASN1::Sequence( [ OpenSSL::ASN1::Sequence( [ OpenSSL::ASN1::ObjectId("id-ecPublicKey"), OpenSSL::ASN1::ObjectId(curve_name) ] ), OpenSSL::ASN1::BitString(public_key_point.to_octet_string(key_object.group.point_conversion_form)) ] ) pub = OpenSSL::PKey::EC.new(asn1.to_der) pub else pub = OpenSSL::PKey::EC.new(key_object.group) pub.public_key = key_object.public_key pub end else key_object.public_key end end # SSH public key def ssh_public_key [directives.join(",").strip, typestr, Base64.encode64(ssh_public_key_conversion).gsub("\n", ""), comment].join(" ").strip end # SSH2 public key (RFC4716) # # ==== Parameters # * headers<~Hash> - Keys will be used as header-tags and values as header-values. # # ==== Examples # {'Comment' => '2048-bit RSA created by user@example'} # {'x-private-use-tag' => 'Private Use Value'} # def ssh2_public_key(headers = nil) self.class.ssh_public_key_to_ssh2_public_key(ssh_public_key, headers) end # Fingerprints # # MD5 fingerprint for the given SSH public key def md5_fingerprint Digest::MD5.hexdigest(ssh_public_key_conversion).gsub(/(.{2})(?=.)/, '\1:\2') end alias_method :fingerprint, :md5_fingerprint # SHA1 fingerprint for the given SSH public key def sha1_fingerprint Digest::SHA1.hexdigest(ssh_public_key_conversion).gsub(/(.{2})(?=.)/, '\1:\2') end # SHA256 fingerprint for the given SSH public key def sha256_fingerprint Base64.encode64(Digest::SHA256.digest(ssh_public_key_conversion)).gsub("\n", "") end # Determine the length (bits) of the key as an integer def bits self.class.ssh_public_key_bits(ssh_public_key) end # Randomart # # Generate OpenSSH compatible ASCII art fingerprints # See http://www.opensource.apple.com/source/OpenSSH/OpenSSH-175/openssh/key.c (key_fingerprint_randomart function) # or https://mirrors.mit.edu/pub/OpenBSD/OpenSSH/ (sshkey.c fingerprint_randomart function) # # Example: # +--[ RSA 2048]----+ # |o+ o.. | # |..+.o | # | ooo | # |.++. o | # |+o+ + S | # |.. + o . | # | . + . | # | . . | # | Eo. | # +-----------------+ def randomart(dgst_alg = "MD5") fieldsize_x = 17 fieldsize_y = 9 x = fieldsize_x / 2 y = fieldsize_y / 2 case dgst_alg when "MD5" then raw_digest = Digest::MD5.digest(ssh_public_key_conversion) when "SHA256" then raw_digest = Digest::SHA2.new(256).digest(ssh_public_key_conversion) when "SHA384" then raw_digest = Digest::SHA2.new(384).digest(ssh_public_key_conversion) when "SHA512" then raw_digest = Digest::SHA2.new(512).digest(ssh_public_key_conversion) else raise "Unknown digest algorithm: #{digest}" end augmentation_string = " .o+=*BOX@%&#/^SE" len = augmentation_string.length - 1 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] < len - 2) byte >>= 2 end end fieldsize_x_halved = fieldsize_x / 2 fieldsize_y_halved = fieldsize_y / 2 field[fieldsize_x_halved][fieldsize_y_halved] = len - 1 field[x][y] = len type_name_length_max = 4 # Note: this will need to be extended to accomodate ed25519 bits_number_length_max = (bits < 1000 ? 3 : 4) formatstr = "[%#{type_name_length_max}s %#{bits_number_length_max}u]" output = "+--#{sprintf(formatstr, type.upcase, bits)}----+\n" fieldsize_y.times do |y| output << "|" fieldsize_x.times do |x| output << augmentation_string[[field[x][y], len].min] end output << "|" output << "\n" end output << "+#{"-" * fieldsize_x}+" output end def sshfp(hostname) self.class.format_sshfp_record(hostname, @type, ssh_public_key_conversion) end def directives=(directives) @directives = Array[directives].flatten.compact end attr_reader :directives private def self.ssh_public_key_data_dsarsa(val) # Get byte-representation of absolute value of val data = val.to_s(2) first_byte = data[0,1].unpack("c").first if val < 0 # For negative values, highest bit must be set data[0] = [0x80 & first_byte].pack("c") elsif first_byte < 0 # For positive values where highest bit would be set, prefix with \0 data = "\0" + data end data end def self.ssh_public_key_data_ecdsa(val) val end # SSH Public Key Conversion # # All data type encoding is defined in the section #5 of RFC #4251. # String and mpint (multiple precision integer) types are encoded this way: # 4-bytes word: data length (unsigned big-endian 32 bits integer) # n bytes: binary representation of the data # For instance, the "ssh-rsa" string is encoded as the following byte array # [0, 0, 0, 7, 's', 's', 'h', '-', 'r', 's', 'a'] def ssh_public_key_conversion methods = SSH_CONVERSION[type] methods.inject([typestr.length].pack("N") + typestr) do |pubkeystr, m| # Given public_key_object.class == OpenSSL::BN, public_key_object.to_s(0) # returns an MPI formatted string (length prefixed bytes). This is not # supported by JRuby, so we still have to deal with length and data separately. val = public_key_object.send(m) case type when "dsa","rsa" then data = self.class.ssh_public_key_data_dsarsa(val) when "ecdsa" then data = self.class.ssh_public_key_data_ecdsa(val) else raise "Unknown key type: #{type}" end pubkeystr + [data.length].pack("N") + data end end class PublicKeyError < StandardError; end end sshkey-3.0.0/lib/sshkey/0000755000004100000410000000000014570752160015133 5ustar www-datawww-datasshkey-3.0.0/lib/sshkey/version.rb0000644000004100000410000000004514570752160017144 0ustar www-datawww-dataclass SSHKey VERSION = "3.0.0" end sshkey-3.0.0/test/0000755000004100000410000000000014570752160014036 5ustar www-datawww-datasshkey-3.0.0/test/sshkey_test.rb0000644000004100000410000011757214570752160016745 0ustar www-datawww-datarequire 'test/unit' require 'sshkey' class SSHKeyTest < Test::Unit::TestCase # https://github.com/jruby/jruby-openssl/issues/189 # https://github.com/jruby/jruby-openssl/issues/226 def ecdsa_supported? RUBY_PLATFORM != "java" end SSH_PRIVATE_KEY1 = <<-EOF -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEArfTA/lKVR84IMc9ZzXOCHr8DVtR8hzWuEVHF6KElavRHlk14 g0SZu3m908Ejm/XF3EfNHjX9wN+62IMA0QBxkBMFCuLF+U/oeUs0NoDdAEKxjj4n 6lq6Ss8aLct+anMy7D1jwvOLbcwV54w1d5JDdlZVdZ6AvHm9otwJq6rNpDgdmXY4 HgC2nM9csFpuy0cDpL6fdJx9lcNL2RnkRC4+RMsIB+PxDw0j3vDi04dYLBXMGYjy eGH+mIFpL3PTPXGXwL2XDYXZ2H4SQX6bOoKmazTXq6QXuEB665njh1GxXldoIMcS shoJL0hrk3WrTOG22N2CQA+IfHgrXJ+A+QUzKQIBIwKCAQBAnLy2O+5N3s/X/I8R y9E+nrgY79Z7XRTEmrc5JelTnI+eOggwwbVxhP1dR7zE5kItPz2O4NqYGJXbY9u7 V++qiri65oQMJP6Tc7ROwiYzS/jObtugMFPSpLHzwJyrMho6fTOuz3zuRH0qHiJ8 3o4WAs9I8brJqY+UQxmI56t3gfHcX4nRhueyUvmEdDG+4Mob21wED1GD5ENh9ebX UiuYkeROqd+lfBUkWoxUXi2fjRMSRt7n3bq59pyZQCwKiShIVaonciV8xAAlNvhI RBzYvXbQ47YgsTmcW4Srlv0j/Oij2/RaDhkJtaXyPkqw9k4B8oCaX3C2x4sdhcwa iLU7AoGBANb4Rmz1w4wfZSnu/HlW4G0Us+AWVEX+6zePoOartP5Pe5t3XhHW7vpi YoB4ecqhz4Y1LoYZL07cSsQZHfntUV4eh/apuo/5slrhDkk0ewJkUh6SKLOFNv6Q 7iJnmtzzRovW1MQPa0NeInsUrZYe4B4iGZmK4yEr9+c7IQCPFQvVAoGBAM8ofVgb gzDYY2uX1lvU9bGAHqA/qNJHcYZBu5AZr7bkZC1GlSKh93ppczdQhiZmj2FQr09R Z5GgKIlSWk8MYC+kYq7l5r2O42g3Unp+i1Zc5KCYUWYpyeE/jfl5IFJFQJFVtdB1 JlsFxruQIF/HuTzY6D+zF8GzK/T5ZQwigBgFAoGAGJFnImU663FNY+DMZaOHXOxs VB/PHfE/dBBqKP2uSPMkEcR/x4ZHMo7mr5i9dj5g3CNVxi7Dk/vrSZx4dFWi5i9f /u7TfisqU4dvWNLMOsmi/C32BeNWvgHvVGOcq4mEZ8DH2+SBSYcZ4i4/uWKdRUW5 yGek7dkjpWXX4s6GD/sCgYEAiCHr+BIUYe1Ipcotx1FuQXFzNhs0bO0et0/EZgJA RPx8WERTX+bHMy9aV4yv7VlW6C21CDzPB+zncC7NoakMAgzwZE3vZp+6AqgDAAoD ywnYEcMuLTFnaCJzPYocjdW8t0bz0iEZNIAjgpHpY4M/Np0q6Af5qyyZOpVCZw9b fX8CgYEAqFpBwetp78UfwvWyKkfN56cY8EaC7gMkwE4gnXsByrqW0f/Shf5ofpO1 kCMav5GhplRYcF3mUO9xiAPx1FxWj/MjeevkmmugIrcYi5OpGu70KoaBmCmb5uV6 zJLsX4h3i0JFdIOaECZEOXhPA7btQT8Vvznj8cHFeeronqdFWf0= -----END RSA PRIVATE KEY----- EOF SSH_PRIVATE_KEY2 = <<-EOF -----BEGIN RSA PRIVATE KEY----- MIIEogIBAAKCAQEAxl6TpN7uFiY/JZ8qDnD7UrxDP+ABeh2PVg8Du1LEgXNk0+YW CeP5S6oHklqaWeDlbmAs1oHsBwCMAVpMa5tgONOLvz4JgwgkiqQEbKR8ofWJ+LAD UElvqRVGmGiNEMLI6GJWeneL4sjmbb8d6U+M53c6iWG0si9XE5m7teBQSsCl0Tk3 qMIkQGw5zpJeCXjZ8KpJhIJRYgexFkGgPlYRV+UYIhxpUW90t0Ra5i6JOFYwq98k 5S/6SJIZQ/A9F4JNzwLw3eVxZj0yVHWxkGz1+TyELNY1kOyMxnZaqSfGzSQJTrnI XpdweVHuYh1LtOgedRQhCyiELeSMGwio1vRPKwIBIwKCAQEAiAZWnPCjQmNegDKg fu5jMWsmzLbccP5TqLnWrFYDFvAKn+46/3fA421HBUVxJ7AoS6+pt6mL54tYsHhu 6rOvsfAlT/AGhbxw1Biyk6P9sOLiRCDsVE+dBjp5jRR9/N1WkLh10FH5hZETCW0b 0y88DG8DkWeR2UUIgngLr+pFr5jV/e4nvA5QpvbNscOwoiR7sFsMGLcMgM2fT4Hj ZZovcGQMrDr6AG+y0/Vdf9wX22j+XKj7huIqM3GZvyqGPqJnP9sOKkPcuTck8Wx3 55BX675RVdoW9OTcHbUh3qHcCND4d9WZqHarW/a7XBdIiuRmC2kBX5WBmVXnm/RF bvxoCwKBgQDqyVNWwm98gIw7LS8dR6Ec7t3kOeLNo/rtNTszo631yZ4hqdwgYR3Q q6rhjufsVVRLVzfTDXucbhZ5h+UB9wXAM49ZPxKNw+ddHsRbhCuIWUl/iO8E/Aub H3eZupo73N9JGa4STFw056ejOQrTTCMf0M316V4wgFAXOZeHEErxSQKBgQDYSuqR nr3Hdw1n/iXfKrfd9fJI++nm14uQ4mkA+9HrtQpj/RTxr66/QSj7p3r6GF4dDYY4 XaqK+iCfhUKMr8+3CP7NoS/saZAUqvMnL+RCvX14sV55xRMwplaaNIwqDhQAhkmL UeOBq40kmBsunjfp06JedmWhWKHYc1eR2iPw0wKBgA1qlwxFn/h8X8jeArE3Swj3 tOh4VhphJEgRq5yNAqBUqfNLiOvoSti5WjjGVmVGtFwTnMo7SOReD+mv/nUkDvUK QrSkhLeky2RoKHpCER279ZJCVs0Vt4U0/4UgmxldFBLORHYS/fRlAkPXX7RNflmW 5zKfnvt1C+QR62bNuyO7AoGBAI4imiUtzStOPAKCcKiYajLGMYBrB2vPePjPTFEa gqI1JBXSMlWt9n2uegR1X3El9LQBkrdTfrMZZeUrr2PD/Ybop3EvaKKrxRTlXfUu GagzYRTMVAbgl5T/l/7vVMst0qFCTZYRPbucnpRj9Jr6QgAOuygh6wOgpN6yMjtG NOAVAoGACIdfR5oZ4tvNIeqjtLF83HmUJARv86eMmjqgiQTFcZS3A8vk5y05STxX HU3kTCfT6sypRi9zDQafIIyqYFgaOezr2eRRFRojQZqzHjtuFUeKLrKf7R9bzwwx DPlNgYq8p4FOY5ZOL/ZOxUHW4vKRewURJttnxzw+LEy0T1FyAE0= -----END RSA PRIVATE KEY----- EOF SSH_PRIVATE_KEY3 = <<-EOF -----BEGIN DSA PRIVATE KEY----- MIIBvAIBAAKBgQC8lcuXcFcIC9wsV87L6PAwYefKgK0CwTSD1v3/aabZsu4w+UF8 zsPtdsNP8+JWfOp3KFbrUTH+ODgAXF/aL4UZfpbsQe446ZFV8v6dmWqj23sk0FLX U5l2tsuJ9OdyXetVXjBvoiz+/r4k/iG/esvWlVGEHwq5eYXgQ1GfXABY3QIVAMVe c7skmkUrCR6iivgZYYe3PQPZAoGBAKnpdEVATtDGOW9w2evSf5kc1InzdTurcJOH q9qYdCaa8rlMGaIS6XFWcKqBlpj0Mv2R5ldW90bU/RllGvh1KinTIRVTsf4qtZIV Xy4vN8IYzDL1493nKndMsxsRh50rI1Snn2tssAix64eJ5VFSGlyOYEKYDMlWzHK6 Jg3tVmc6AoGBAIwTRPAEcroqOzaebiVspFcmsXxDQ4wXQZQdho1ExW6FKS8s7/6p ItmZYXTvJDwLXgq2/iK1fRRcKk2PJEaSuJR7WeNGsJKfWmQ2UbOhqA3wWLDazIZt cMKjFzD0hM4E8qgjHjMvKDE6WgT6SFP+tqx3nnh7pJWwsbGjSMQexpyRAhQLhz0l GzM8qwTcXd06uIZAJdTHIQ== -----END DSA PRIVATE KEY----- EOF SSH_PRIVATE_KEY4 = <<-EOF -----BEGIN EC PRIVATE KEY----- MHcCAQEEIByjVCRawGxEd/L/VblGjnJTJeOgk6vGFYnolYWHg+JkoAoGCCqGSM49 AwEHoUQDQgAEQOAmNzXT3XN5DQdHBYCgflosVlHd6MUB1n9n6CCijvVJCQGJAA0p 6+3o91ccyA0zHXuUno2eMzBUDghfNZYnHg== -----END EC PRIVATE KEY----- EOF PUBLIC_KEY1 = <<-EOF -----BEGIN PUBLIC KEY----- MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEArfTA/lKVR84IMc9ZzXOC Hr8DVtR8hzWuEVHF6KElavRHlk14g0SZu3m908Ejm/XF3EfNHjX9wN+62IMA0QBx kBMFCuLF+U/oeUs0NoDdAEKxjj4n6lq6Ss8aLct+anMy7D1jwvOLbcwV54w1d5JD dlZVdZ6AvHm9otwJq6rNpDgdmXY4HgC2nM9csFpuy0cDpL6fdJx9lcNL2RnkRC4+ RMsIB+PxDw0j3vDi04dYLBXMGYjyeGH+mIFpL3PTPXGXwL2XDYXZ2H4SQX6bOoKm azTXq6QXuEB665njh1GxXldoIMcSshoJL0hrk3WrTOG22N2CQA+IfHgrXJ+A+QUz KQIBIw== -----END PUBLIC KEY----- EOF PUBLIC_KEY2 = <<-EOF -----BEGIN PUBLIC KEY----- MIIBIDANBgkqhkiG9w0BAQEFAAOCAQ0AMIIBCAKCAQEAxl6TpN7uFiY/JZ8qDnD7 UrxDP+ABeh2PVg8Du1LEgXNk0+YWCeP5S6oHklqaWeDlbmAs1oHsBwCMAVpMa5tg ONOLvz4JgwgkiqQEbKR8ofWJ+LADUElvqRVGmGiNEMLI6GJWeneL4sjmbb8d6U+M 53c6iWG0si9XE5m7teBQSsCl0Tk3qMIkQGw5zpJeCXjZ8KpJhIJRYgexFkGgPlYR V+UYIhxpUW90t0Ra5i6JOFYwq98k5S/6SJIZQ/A9F4JNzwLw3eVxZj0yVHWxkGz1 +TyELNY1kOyMxnZaqSfGzSQJTrnIXpdweVHuYh1LtOgedRQhCyiELeSMGwio1vRP KwIBIw== -----END PUBLIC KEY----- EOF PUBLIC_KEY3 = <<-EOF -----BEGIN PUBLIC KEY----- MIIBuDCCASwGByqGSM44BAEwggEfAoGBALyVy5dwVwgL3CxXzsvo8DBh58qArQLB NIPW/f9pptmy7jD5QXzOw+12w0/z4lZ86ncoVutRMf44OABcX9ovhRl+luxB7jjp kVXy/p2ZaqPbeyTQUtdTmXa2y4n053Jd61VeMG+iLP7+viT+Ib96y9aVUYQfCrl5 heBDUZ9cAFjdAhUAxV5zuySaRSsJHqKK+Blhh7c9A9kCgYEAqel0RUBO0MY5b3DZ 69J/mRzUifN1O6twk4er2ph0JpryuUwZohLpcVZwqoGWmPQy/ZHmV1b3RtT9GWUa +HUqKdMhFVOx/iq1khVfLi83whjMMvXj3ecqd0yzGxGHnSsjVKefa2ywCLHrh4nl UVIaXI5gQpgMyVbMcromDe1WZzoDgYUAAoGBAIwTRPAEcroqOzaebiVspFcmsXxD Q4wXQZQdho1ExW6FKS8s7/6pItmZYXTvJDwLXgq2/iK1fRRcKk2PJEaSuJR7WeNG sJKfWmQ2UbOhqA3wWLDazIZtcMKjFzD0hM4E8qgjHjMvKDE6WgT6SFP+tqx3nnh7 pJWwsbGjSMQexpyR -----END PUBLIC KEY----- EOF PUBLIC_KEY4 = <<-EOF -----BEGIN PUBLIC KEY----- MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEQOAmNzXT3XN5DQdHBYCgflosVlHd 6MUB1n9n6CCijvVJCQGJAA0p6+3o91ccyA0zHXuUno2eMzBUDghfNZYnHg== -----END PUBLIC KEY----- EOF SSH_PUBLIC_KEY1 = 'AAAAB3NzaC1yc2EAAAABIwAAAQEArfTA/lKVR84IMc9ZzXOCHr8DVtR8hzWuEVHF6KElavRHlk14g0SZu3m908Ejm/XF3EfNHjX9wN+62IMA0QBxkBMFCuLF+U/oeUs0NoDdAEKxjj4n6lq6Ss8aLct+anMy7D1jwvOLbcwV54w1d5JDdlZVdZ6AvHm9otwJq6rNpDgdmXY4HgC2nM9csFpuy0cDpL6fdJx9lcNL2RnkRC4+RMsIB+PxDw0j3vDi04dYLBXMGYjyeGH+mIFpL3PTPXGXwL2XDYXZ2H4SQX6bOoKmazTXq6QXuEB665njh1GxXldoIMcSshoJL0hrk3WrTOG22N2CQA+IfHgrXJ+A+QUzKQ==' SSH_PUBLIC_KEY2 = 'AAAAB3NzaC1yc2EAAAABIwAAAQEAxl6TpN7uFiY/JZ8qDnD7UrxDP+ABeh2PVg8Du1LEgXNk0+YWCeP5S6oHklqaWeDlbmAs1oHsBwCMAVpMa5tgONOLvz4JgwgkiqQEbKR8ofWJ+LADUElvqRVGmGiNEMLI6GJWeneL4sjmbb8d6U+M53c6iWG0si9XE5m7teBQSsCl0Tk3qMIkQGw5zpJeCXjZ8KpJhIJRYgexFkGgPlYRV+UYIhxpUW90t0Ra5i6JOFYwq98k5S/6SJIZQ/A9F4JNzwLw3eVxZj0yVHWxkGz1+TyELNY1kOyMxnZaqSfGzSQJTrnIXpdweVHuYh1LtOgedRQhCyiELeSMGwio1vRPKw==' SSH_PUBLIC_KEY3 = 'AAAAB3NzaC1kc3MAAACBALyVy5dwVwgL3CxXzsvo8DBh58qArQLBNIPW/f9pptmy7jD5QXzOw+12w0/z4lZ86ncoVutRMf44OABcX9ovhRl+luxB7jjpkVXy/p2ZaqPbeyTQUtdTmXa2y4n053Jd61VeMG+iLP7+viT+Ib96y9aVUYQfCrl5heBDUZ9cAFjdAAAAFQDFXnO7JJpFKwkeoor4GWGHtz0D2QAAAIEAqel0RUBO0MY5b3DZ69J/mRzUifN1O6twk4er2ph0JpryuUwZohLpcVZwqoGWmPQy/ZHmV1b3RtT9GWUa+HUqKdMhFVOx/iq1khVfLi83whjMMvXj3ecqd0yzGxGHnSsjVKefa2ywCLHrh4nlUVIaXI5gQpgMyVbMcromDe1WZzoAAACBAIwTRPAEcroqOzaebiVspFcmsXxDQ4wXQZQdho1ExW6FKS8s7/6pItmZYXTvJDwLXgq2/iK1fRRcKk2PJEaSuJR7WeNGsJKfWmQ2UbOhqA3wWLDazIZtcMKjFzD0hM4E8qgjHjMvKDE6WgT6SFP+tqx3nnh7pJWwsbGjSMQexpyR' SSH_PUBLIC_KEY4 = 'AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEDgJjc1091zeQ0HRwWAoH5aLFZR3ejFAdZ/Z+ggoo71SQkBiQANKevt6PdXHMgNMx17lJ6NnjMwVA4IXzWWJx4=' SSH_PUBLIC_KEY_ED25519 = 'AAAAC3NzaC1lZDI1NTE5AAAAIBrNsRCISAtKXV5OVxqV6unVcdis5Uh3oiC6B7CMB7HQ' SSH_PUBLIC_KEY_ED25519_0_BYTE = 'AAAAC3NzaC1lZDI1NTE5AAAAIADK9x9t3yQQH7h4OEJpUa7l2j7mcmKf4LAsNXHxNbSm' SSH_PUBLIC_KEY_ECDSA_256 = 'AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBHJFDZ5qymZfIzoJcxYeu3C9HjJ08QAbqR28C2zSMLwcb3ZzWdRApnj6wEgRvizsBmr9zyPKb2u5Rp0vjJtQcZo=' SSH_PUBLIC_KEY_ECDSA_384 = 'AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBP+GtUCOR8aW7xTtpkbJS0qqNZ98PgbUNtTFhE+Oe+khgoFMX+o0JG5bckVuvtkRl8dr+63kUK0QPTtzP9O5yixB9CYnB8CgCgYo1FCXZuJIImf12wW5nWKglrCH4kV1Qg==' SSH_PUBLIC_KEY_ECDSA_521 = 'AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBACsunidnIZ77AjCHSDp/xknLGDW3M0Ia7nxLdImmp0XGbxtbwYm2ga5XUzV9dMO9wF9ICC3OuH6g9DtGOBNPru1PwFDjaPISGgm0vniEzWazLsvjJVLThOA3VyYLxmtjm0WfS+/DfxgWVS6oeCTnDjjoVVpwU/fDbUbYPPRZI84/hOGNA==' SSH_PUBLIC_KEY_ECDSA_256_COMPRESSED = 'AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAAAhA+YNpJJrrUsu5OLLvqGX5pAH3+x6/yEFU2AYdxb54Jk8' SSH_PUBLIC_KEY_ECDSA_384_COMPRESSED = 'AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAAAxAgMhp0cNvtzncxXF0W5nrkBCTrxJIcYqUTX4RcKWIM74FfxizmWJqP/C+looEz6dLQ==' SSH_PUBLIC_KEY_ECDSA_521_COMPRESSED = 'AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAABDAgDoeNR4bndT24BosNaTKCLOALjL6tXrpNHn0HJzHO5z30L4SvH0Gz9jvAiqehNHOgmK3/bFbwLVW1W4TJbNsp8BVA==' KEY1_MD5_FINGERPRINT = "2a:89:84:c9:29:05:d1:f8:49:79:1c:ba:73:99:eb:af" KEY2_MD5_FINGERPRINT = "3c:af:74:87:cc:cc:a1:12:05:1a:09:b7:7b:ce:ed:ce" KEY3_MD5_FINGERPRINT = "14:f6:6a:12:96:be:44:32:e6:3c:77:43:94:52:f5:7a" KEY4_MD5_FINGERPRINT = "38:0b:0f:63:36:64:b6:f0:43:94:de:32:75:eb:57:68" ED25519_MD5_FINGERPRINT = "6f:1a:8a:c1:4f:13:5c:36:6e:3f:be:eb:49:3b:8e:3e" ECDSA_256_MD5_FINGERPRINT = "d9:3a:7f:de:b2:65:04:ac:62:05:1a:1e:97:e9:2b:9d" ECDSA_384_MD5_FINGERPRINT = "b5:bb:3e:f6:eb:3b:0f:1e:18:37:1f:36:ac:7c:87:0d" ECDSA_521_MD5_FINGERPRINT = "98:8e:a9:4c:b9:aa:58:35:d1:42:65:c3:41:dd:04:e1" KEY1_SHA1_FINGERPRINT = "e4:f9:79:f2:fe:d6:be:2d:ef:2e:c2:fa:aa:f8:b0:17:34:fe:0d:c0" KEY2_SHA1_FINGERPRINT = "9a:52:78:2b:6b:cb:39:b7:85:ed:90:8a:28:62:aa:b3:98:88:e6:07" KEY3_SHA1_FINGERPRINT = "15:68:c6:72:ac:18:d1:fc:ab:a2:b7:b5:8c:d1:fe:8f:b9:ae:a9:47" KEY4_SHA1_FINGERPRINT = "aa:b5:e6:62:27:87:b8:05:f6:d6:8f:31:dc:83:81:d9:8f:f8:71:29" ED25519_SHA1_FINGERPRINT = "57:41:7c:d0:e2:53:28:87:7e:87:53:d4:69:ef:ef:63:ec:c0:0e:5e" ECDSA_256_SHA1_FINGERPRINT = "94:e8:92:2b:1b:ec:49:de:ff:85:ea:6e:10:d6:8d:87:7a:67:40:ee" ECDSA_384_SHA1_FINGERPRINT = "cc:fb:4c:d6:e9:d0:03:ae:2d:82:e1:fc:70:d8:47:98:25:e1:83:2b" ECDSA_521_SHA1_FINGERPRINT = "6b:2c:a2:6e:3a:82:6c:73:28:57:91:20:71:82:bc:8f:f8:9d:6c:41" KEY1_SHA256_FINGERPRINT = "js3llFehloxCfsVuDw5xu3NtS9AOAxcXY8WL6vkDIts=" KEY2_SHA256_FINGERPRINT = "23f/6U/LdxIFx1CQFKHylw76n+LIHYoY4nRxKcFoos4=" KEY3_SHA256_FINGERPRINT = "mPqEPQlOPGORrTJrU17sPax1jOqeutZja6MOsFIca+8=" KEY4_SHA256_FINGERPRINT = "foUpf1ox3KfG3eKgJxGoSdZFRxHPsBYJgfD+CMYky6Y=" ED25519_SHA256_FINGERPRINT = "gyzHUKl1eO8Bk1Cvn4joRgxRlXo1+1HJ3Vho/hAtKEg=" ECDSA_256_SHA256_FINGERPRINT = "ncy2crhoL44R58GCZPQ5chPRrjlQKKgu07FDNelDmdk=" ECDSA_384_SHA256_FINGERPRINT = "mrr4QcP6qD05DUS6Rwefb9f0uuvjyMcO28LSiq2283U=" ECDSA_521_SHA256_FINGERPRINT = "QnaiGMIVDZyTG47hMWK6Y1z/yUzHIcTBGpNNuUwlhAk=" KEY1_RANDOMART = <<-EOF.rstrip +--[ RSA 2048]----+ |o+ o.. | |..+.o | | ooo | |.++. o | |+o+ + S | |.. + o . | | . + . | | . . | | Eo. | +-----------------+ EOF KEY2_RANDOMART = <<-EOF.rstrip +--[ RSA 2048]----+ | ..o.. | | ..+ . | | o . | | . o | | . o S . | | + o O o | | + + O . | | = o . | | .E | +-----------------+ EOF KEY3_RANDOMART = <<-EOF.rstrip +--[ DSA 1024]----+ | .=o. | | .+.o . | | + =.o . . | | + * + . . | | + = S . E | | + = . . | | . | | | | | +-----------------+ EOF # ssh-keygen -lv -E md5 -f ./id_ecdsa_ssh_public_key4.pub KEY4_RANDOMART = <<-EOF.rstrip +--[ECDSA 256]----+ | .. | | .. . . | | ..=o . . . | | B+.... E . | | @oo.S. . | | o B o. . | | o . | | | | | +-----------------+ EOF # ssh-keygen -lv -E sha256 -f ./id_ecdsa_ssh_public_key4.pub KEY4_RANDOMART_USING_SHA256_DIGEST = <<-EOF.rstrip +--[ECDSA 256]----+ | .. o++B+ | | .. ...* | | . ...o o o | | . =o.o .= . | | +o+oS o.= . .| | o .oo =.. + +.| | E o +.+ = o| | ..=.+ . | | oo . | +-----------------+ EOF # ssh-keygen -lv -E sha384 -f ./id_ecdsa_ssh_public_key4.pub KEY4_RANDOMART_USING_SHA384_DIGEST = <<-EOF.rstrip +--[ECDSA 256]----+ | o++. | | . *oo. . | |o .o+B.o.. | |+o ooB+O *..| |.=+ .SB== ^.+.| |+ o +o .O Xo.| | . ... .. + .o| | . E. o + + +..| | .... . o..Bo..| +-----------------+ EOF # ssh-keygen -lv -E sha512 -f ./id_ecdsa_ssh_public_key4.pub KEY4_RANDOMART_USING_SHA512_DIGEST = <<-EOF.rstrip +--[ECDSA 256]----+ | +*+o oo| | . .o o . +| | . o. oo oo| |.. .+ . .*.o+ | |..Bo.* S ..=o..| | .+X+ Oo ...+ | | +o.B*+=o .+ +| |+=+O.+=+.+. +.o+.| |@**EB*O++=o+ =o.+| +-----------------+ EOF KEY1_SSHFP = <<-EOF.rstrip localhost IN SSHFP 1 1 e4f979f2fed6be2def2ec2faaaf8b01734fe0dc0 localhost IN SSHFP 1 2 8ecde59457a1968c427ec56e0f0e71bb736d4bd00e03171763c58beaf90322db EOF KEY2_SSHFP = <<-EOF.rstrip localhost IN SSHFP 1 1 9a52782b6bcb39b785ed908a2862aab39888e607 localhost IN SSHFP 1 2 db77ffe94fcb771205c7509014a1f2970efa9fe2c81d8a18e2747129c168a2ce EOF KEY3_SSHFP = <<-EOF.rstrip localhost IN SSHFP 2 1 1568c672ac18d1fcaba2b7b58cd1fe8fb9aea947 localhost IN SSHFP 2 2 98fa843d094e3c6391ad326b535eec3dac758cea9ebad6636ba30eb0521c6bef EOF SSH2_PUBLIC_KEY1 = <<-EOF.rstrip ---- BEGIN SSH2 PUBLIC KEY ---- Comment: me@example.com AAAAB3NzaC1yc2EAAAABIwAAAQEArfTA/lKVR84IMc9ZzXOCHr8DVtR8hzWuEVHF6KElav RHlk14g0SZu3m908Ejm/XF3EfNHjX9wN+62IMA0QBxkBMFCuLF+U/oeUs0NoDdAEKxjj4n 6lq6Ss8aLct+anMy7D1jwvOLbcwV54w1d5JDdlZVdZ6AvHm9otwJq6rNpDgdmXY4HgC2nM 9csFpuy0cDpL6fdJx9lcNL2RnkRC4+RMsIB+PxDw0j3vDi04dYLBXMGYjyeGH+mIFpL3PT PXGXwL2XDYXZ2H4SQX6bOoKmazTXq6QXuEB665njh1GxXldoIMcSshoJL0hrk3WrTOG22N 2CQA+IfHgrXJ+A+QUzKQ== ---- END SSH2 PUBLIC KEY ---- EOF SSH2_PUBLIC_KEY2 = <<-EOF.rstrip ---- BEGIN SSH2 PUBLIC KEY ---- AAAAB3NzaC1yc2EAAAABIwAAAQEAxl6TpN7uFiY/JZ8qDnD7UrxDP+ABeh2PVg8Du1LEgX Nk0+YWCeP5S6oHklqaWeDlbmAs1oHsBwCMAVpMa5tgONOLvz4JgwgkiqQEbKR8ofWJ+LAD UElvqRVGmGiNEMLI6GJWeneL4sjmbb8d6U+M53c6iWG0si9XE5m7teBQSsCl0Tk3qMIkQG w5zpJeCXjZ8KpJhIJRYgexFkGgPlYRV+UYIhxpUW90t0Ra5i6JOFYwq98k5S/6SJIZQ/A9 F4JNzwLw3eVxZj0yVHWxkGz1+TyELNY1kOyMxnZaqSfGzSQJTrnIXpdweVHuYh1LtOgedR QhCyiELeSMGwio1vRPKw== ---- END SSH2 PUBLIC KEY ---- EOF SSH2_PUBLIC_KEY3 = <<-EOF.rstrip ---- BEGIN SSH2 PUBLIC KEY ---- Comment: 1024-bit DSA with provided comment x-private-use-header: some value that is long enough to go to wrap aro\\ und to a new line. AAAAB3NzaC1kc3MAAACBALyVy5dwVwgL3CxXzsvo8DBh58qArQLBNIPW/f9pptmy7jD5QX zOw+12w0/z4lZ86ncoVutRMf44OABcX9ovhRl+luxB7jjpkVXy/p2ZaqPbeyTQUtdTmXa2 y4n053Jd61VeMG+iLP7+viT+Ib96y9aVUYQfCrl5heBDUZ9cAFjdAAAAFQDFXnO7JJpFKw keoor4GWGHtz0D2QAAAIEAqel0RUBO0MY5b3DZ69J/mRzUifN1O6twk4er2ph0JpryuUwZ ohLpcVZwqoGWmPQy/ZHmV1b3RtT9GWUa+HUqKdMhFVOx/iq1khVfLi83whjMMvXj3ecqd0 yzGxGHnSsjVKefa2ywCLHrh4nlUVIaXI5gQpgMyVbMcromDe1WZzoAAACBAIwTRPAEcroq OzaebiVspFcmsXxDQ4wXQZQdho1ExW6FKS8s7/6pItmZYXTvJDwLXgq2/iK1fRRcKk2PJE aSuJR7WeNGsJKfWmQ2UbOhqA3wWLDazIZtcMKjFzD0hM4E8qgjHjMvKDE6WgT6SFP+tqx3 nnh7pJWwsbGjSMQexpyR ---- END SSH2 PUBLIC KEY ---- EOF def setup @key1 = SSHKey.new(SSH_PRIVATE_KEY1, :comment => "me@example.com") @key2 = SSHKey.new(SSH_PRIVATE_KEY2, :comment => "me@example.com") @key3 = SSHKey.new(SSH_PRIVATE_KEY3, :comment => "me@example.com") @key4 = SSHKey.new(SSH_PRIVATE_KEY4, :comment => "me@example.com") @key_without_comment = SSHKey.new(SSH_PRIVATE_KEY1) end def test_generator_with_no_args assert_kind_of SSHKey, SSHKey.generate end def test_generator_with_comment assert_equal "foo", SSHKey.generate(:comment => "foo").comment end def test_generator_with_type assert_equal "rsa", SSHKey.generate(:type => "rsa").type assert_equal "dsa", SSHKey.generate(:type => "dsa").type if ecdsa_supported? assert_equal "ecdsa", SSHKey.generate(:type => "ecdsa").type else assert_raises(NotImplementedError) { SSHKey.generate(:type => "ecdsa").type } end end def test_generator_with_passphrase assert_equal "password", SSHKey.generate(:passphrase => "password").passphrase end def test_private_key1 assert_equal SSH_PRIVATE_KEY1, @key1.private_key assert_equal SSH_PRIVATE_KEY1, @key1.rsa_private_key end def test_private_key2 assert_equal SSH_PRIVATE_KEY2, @key2.private_key assert_equal SSH_PRIVATE_KEY2, @key2.rsa_private_key end def test_private_key3 assert_equal SSH_PRIVATE_KEY3, @key3.private_key assert_equal SSH_PRIVATE_KEY3, @key3.dsa_private_key end def test_private_key4 if ecdsa_supported? assert_equal SSH_PRIVATE_KEY4, @key4.private_key else assert_raises(NotImplementedError) { @key4.private_key } end end def test_public_key_1 assert_equal PUBLIC_KEY1, @key1.public_key end def test_public_key_2 assert_equal PUBLIC_KEY2, @key2.public_key end def test_public_key_3 assert_equal PUBLIC_KEY3, @key3.public_key end def test_public_key_4 assert_equal PUBLIC_KEY4, @key4.public_key end def test_ssh_public_key_decoded1 assert_equal Base64.decode64(SSH_PUBLIC_KEY1), @key1.send(:ssh_public_key_conversion) end def test_ssh_public_key_decoded2 assert_equal Base64.decode64(SSH_PUBLIC_KEY2), @key2.send(:ssh_public_key_conversion) end def test_ssh_public_key_decoded3 assert_equal Base64.decode64(SSH_PUBLIC_KEY3), @key3.send(:ssh_public_key_conversion) end def test_ssh_public_key_decoded4 if ecdsa_supported? assert_equal Base64.decode64(SSH_PUBLIC_KEY4), @key4.send(:ssh_public_key_conversion) else assert_raises(NotImplementedError) { @key4.send(:ssh_public_key_conversion) } end end def test_ssh_public_key_encoded1 assert_equal SSH_PUBLIC_KEY1, Base64.encode64(@key1.send(:ssh_public_key_conversion)).gsub("\n", "") end def test_ssh_public_key_encoded2 assert_equal SSH_PUBLIC_KEY2, Base64.encode64(@key2.send(:ssh_public_key_conversion)).gsub("\n", "") end def test_ssh_public_key_encoded3 assert_equal SSH_PUBLIC_KEY3, Base64.encode64(@key3.send(:ssh_public_key_conversion)).gsub("\n", "") end def test_ssh_public_key_encoded4 if ecdsa_supported? assert_equal SSH_PUBLIC_KEY4, Base64.encode64(@key4.send(:ssh_public_key_conversion)).gsub("\n", "") else assert_raises(NotImplementedError) { Base64.encode64(@key4.send(:ssh_public_key_conversion)) } end end def test_ssh_public_key_output expected1 = "ssh-rsa #{SSH_PUBLIC_KEY1} me@example.com" expected2 = "ssh-rsa #{SSH_PUBLIC_KEY2} me@example.com" expected3 = "ssh-dss #{SSH_PUBLIC_KEY3} me@example.com" expected4 = "ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY4} me@example.com" expected1b = "ssh-rsa #{SSH_PUBLIC_KEY1}" assert_equal expected1, @key1.ssh_public_key assert_equal expected2, @key2.ssh_public_key assert_equal expected3, @key3.ssh_public_key if ecdsa_supported? assert_equal expected4, @key4.ssh_public_key else assert_raises(NotImplementedError) { @key4.ssh_public_key } end assert_equal expected1b, @key_without_comment.ssh_public_key end def test_ssh2_public_key_output expected1 = SSH2_PUBLIC_KEY1 expected2 = SSH2_PUBLIC_KEY2 expected3 = SSH2_PUBLIC_KEY3 assert_equal expected1, @key1.ssh2_public_key assert_equal expected2, @key2.ssh2_public_key({}) assert_equal expected3, @key3.ssh2_public_key({'Comment' => '1024-bit DSA with provided comment', 'x-private-use-header' => 'some value that is long enough to go to wrap around to a new line.'}) end def test_ssh_public_key_output_from_generated generated_rsa = SSHKey.generate(:type => "rsa", :comment => "rsa key") generated_dsa = SSHKey.generate(:type => "dsa", :comment => "dsa key") generated_ecdsa = SSHKey.generate(:type => "ecdsa", :comment => "ecdsa key") if ecdsa_supported? encoded_rsa = Base64.encode64(generated_rsa.send(:ssh_public_key_conversion)).gsub("\n", "") encoded_dsa = Base64.encode64(generated_dsa.send(:ssh_public_key_conversion)).gsub("\n", "") encoded_ecdsa = Base64.encode64(generated_ecdsa.send(:ssh_public_key_conversion)).gsub("\n", "") if ecdsa_supported? expected_rsa = "ssh-rsa #{encoded_rsa} rsa key" expected_dsa = "ssh-dss #{encoded_dsa} dsa key" expected_ecdsa = "ecdsa-sha2-nistp256 #{encoded_ecdsa} ecdsa key" assert_equal expected_rsa, generated_rsa.ssh_public_key assert_equal expected_dsa, generated_dsa.ssh_public_key assert_equal expected_ecdsa, generated_ecdsa.ssh_public_key if ecdsa_supported? end def test_public_key_directives assert_equal [], SSHKey.generate.directives @key1.directives = "no-pty" assert_equal ["no-pty"], @key1.directives @key1.directives = ["no-pty"] assert_equal ["no-pty"], @key1.directives @key1.directives = [ "no-port-forwarding", "no-X11-forwarding", "no-agent-forwarding", "no-pty", "command='/home/user/bin/authprogs'" ] expected1 = "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command='/home/user/bin/authprogs' ssh-rsa #{SSH_PUBLIC_KEY1} me@example.com" assert_equal expected1, @key1.ssh_public_key assert SSHKey.valid_ssh_public_key?(expected1) @key2.directives = "no-pty" expected2 = "no-pty ssh-rsa #{SSH_PUBLIC_KEY2} me@example.com" assert_equal expected2, @key2.ssh_public_key assert SSHKey.valid_ssh_public_key?(expected2) end def test_ssh_public_key_validation expected1 = "ssh-rsa #{SSH_PUBLIC_KEY1} me@example.com" expected2 = "ssh-rsa #{SSH_PUBLIC_KEY2} me@example.com" expected3 = "ssh-dss #{SSH_PUBLIC_KEY3} me@example.com" expected4 = "ssh-rsa #{SSH_PUBLIC_KEY1}" expected5 = %Q{from="trusted.eng.cam.ac.uk",no-port-forwarding,no-pty ssh-rsa #{SSH_PUBLIC_KEY1}} invalid1 = "ssh-rsa #{SSH_PUBLIC_KEY1}= me@example.com" invalid2 = "ssh-rsa #{SSH_PUBLIC_KEY2}= me@example.com" invalid3 = "ssh-dss #{SSH_PUBLIC_KEY3}= me@example.com" invalid4 = "ssh-rsa A#{SSH_PUBLIC_KEY1}" invalid5 = "ssh-rsa #{SSH_PUBLIC_KEY3} me@example.com" assert SSHKey.valid_ssh_public_key?(expected1) assert SSHKey.valid_ssh_public_key?(expected2) assert SSHKey.valid_ssh_public_key?(expected3) assert SSHKey.valid_ssh_public_key?(expected4) assert SSHKey.valid_ssh_public_key?(expected5) assert !SSHKey.valid_ssh_public_key?(invalid1) assert !SSHKey.valid_ssh_public_key?(invalid2) assert !SSHKey.valid_ssh_public_key?(invalid3) assert !SSHKey.valid_ssh_public_key?(invalid4) assert !SSHKey.valid_ssh_public_key?(invalid5) end def test_ssh_public_key_validation_elliptic assert SSHKey.valid_ssh_public_key?("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519} me@example.com") assert SSHKey.valid_ssh_public_key?("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519_0_BYTE} me@example.com") assert SSHKey.valid_ssh_public_key?("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256}") assert SSHKey.valid_ssh_public_key?("ecdsa-sha2-nistp384 #{SSH_PUBLIC_KEY_ECDSA_384} me@example.com") assert SSHKey.valid_ssh_public_key?(%Q{from="trusted.eng.cam.ac.uk",no-port-forwarding,no-pty ecdsa-sha2-nistp521 #{SSH_PUBLIC_KEY_ECDSA_521} me@example.com}) assert !SSHKey.valid_ssh_public_key?("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519}= me@example.com") # bad base64 assert !SSHKey.valid_ssh_public_key?("ssh-ed25519 #{SSH_PUBLIC_KEY_ECDSA_384} me@example.com") # mismatched key format assert !SSHKey.valid_ssh_public_key?("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_384} me@example.com") # mismatched key format assert !SSHKey.valid_ssh_public_key?("ssh-ed25519 asdf me@example.com") # gibberish key data assert !SSHKey.valid_ssh_public_key?("ecdsa-sha2-nistp256 asdf me@example.com") # gibberish key data end def test_ssh_public_key_validation_with_newlines expected1 = "ssh-rsa #{SSH_PUBLIC_KEY1}\n" expected2 = "ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519} me@example.com\n" invalid1 = "ssh-rsa #{SSH_PUBLIC_KEY1}\nme@example.com" invalid2 = "ssh-rsa #{SSH_PUBLIC_KEY1}\n me@example.com" invalid3 = "ssh-rsa #{SSH_PUBLIC_KEY1} \nme@example.com" invalid4 = "ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256}\nme@example.com" assert SSHKey.valid_ssh_public_key?(expected1) assert SSHKey.valid_ssh_public_key?(expected2) assert !SSHKey.valid_ssh_public_key?(invalid1) assert !SSHKey.valid_ssh_public_key?(invalid2) assert !SSHKey.valid_ssh_public_key?(invalid3) assert !SSHKey.valid_ssh_public_key?(invalid4) end def test_ssh_public_key_validation_with_comments expected1 = "# Comment\nssh-rsa #{SSH_PUBLIC_KEY1}" expected2 = "# First comment\n\n# Second comment\n\nssh-ed25519 #{SSH_PUBLIC_KEY_ED25519} me@example.com" invalid1 = "No starting hash # Valid comment\nssh-rsa #{SSH_PUBLIC_KEY1} me@example.com" invalid2 = "# First comment\n\nSecond comment without hash\n\necdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256}\nme@example.com" assert SSHKey.valid_ssh_public_key?(expected1) assert SSHKey.valid_ssh_public_key?(expected2) assert !SSHKey.valid_ssh_public_key?(invalid1) assert !SSHKey.valid_ssh_public_key?(invalid2) end def test_ssh_public_key_sshfp assert_equal KEY1_SSHFP, SSHKey.sshfp("localhost", "ssh-rsa #{SSH_PUBLIC_KEY1}\n") assert_equal KEY2_SSHFP, SSHKey.sshfp("localhost", "ssh-rsa #{SSH_PUBLIC_KEY2}\n") assert_equal KEY3_SSHFP, SSHKey.sshfp("localhost", "ssh-dss #{SSH_PUBLIC_KEY3}\n") assert_equal KEY1_SSHFP, SSHKey.sshfp("localhost", SSH_PRIVATE_KEY1) assert_equal KEY2_SSHFP, SSHKey.sshfp("localhost", SSH_PRIVATE_KEY2) assert_equal KEY3_SSHFP, SSHKey.sshfp("localhost", SSH_PRIVATE_KEY3) end def test_ssh_public_key_bits expected1 = "ssh-rsa #{SSH_PUBLIC_KEY1} me@example.com" expected2 = "ssh-rsa #{SSH_PUBLIC_KEY2} me@example.com" expected3 = "ssh-dss #{SSH_PUBLIC_KEY3} me@example.com" expected4 = "ssh-rsa #{SSH_PUBLIC_KEY1}" expected5 = %Q{from="trusted.eng.cam.ac.uk",no-port-forwarding,no-pty ssh-rsa #{SSH_PUBLIC_KEY1}} invalid1 = "#{SSH_PUBLIC_KEY1} me@example.com" ecdsa256 = "ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256}" ecdsa384 = "ecdsa-sha2-nistp384 #{SSH_PUBLIC_KEY_ECDSA_384}" ecdsa521 = "ecdsa-sha2-nistp521 #{SSH_PUBLIC_KEY_ECDSA_521}" ecdsa256_compressed = "ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256_COMPRESSED}" ecdsa384_compressed = "ecdsa-sha2-nistp384 #{SSH_PUBLIC_KEY_ECDSA_384_COMPRESSED}" ecdsa521_compressed = "ecdsa-sha2-nistp521 #{SSH_PUBLIC_KEY_ECDSA_521_COMPRESSED}" assert_equal 2048, SSHKey.ssh_public_key_bits(expected1) assert_equal 2048, SSHKey.ssh_public_key_bits(expected2) assert_equal 1024, SSHKey.ssh_public_key_bits(expected3) assert_equal 2048, SSHKey.ssh_public_key_bits(expected4) assert_equal 2048, SSHKey.ssh_public_key_bits(expected5) assert_equal 512, SSHKey.ssh_public_key_bits(SSHKey.generate(:bits => 512).ssh_public_key) assert_equal 256, SSHKey.ssh_public_key_bits(ecdsa256) assert_equal 384, SSHKey.ssh_public_key_bits(ecdsa384) assert_equal 521, SSHKey.ssh_public_key_bits(ecdsa521) assert_equal 256, SSHKey.ssh_public_key_bits(ecdsa256_compressed) assert_equal 384, SSHKey.ssh_public_key_bits(ecdsa384_compressed) assert_equal 521, SSHKey.ssh_public_key_bits(ecdsa521_compressed) exception1 = assert_raises(SSHKey::PublicKeyError) { SSHKey.ssh_public_key_bits( expected1.gsub('A','.') ) } exception2 = assert_raises(SSHKey::PublicKeyError) { SSHKey.ssh_public_key_bits( expected1[0..-20] ) } exception3 = assert_raises(SSHKey::PublicKeyError) { SSHKey.ssh_public_key_bits(invalid1) } assert_equal( "validation error", exception1.message ) assert_equal( "byte array too short", exception2.message ) assert_equal( "cannot determine key type", exception3.message ) end def test_ssh2_public_key_bits public_key1 = "ssh-rsa #{SSH_PUBLIC_KEY1} me@example.com" public_key2 = "ssh-rsa #{SSH_PUBLIC_KEY2}" public_key3 = "ssh-dss #{SSH_PUBLIC_KEY3} 1024-bit DSA with provided comment" assert_equal(SSH2_PUBLIC_KEY1, SSHKey.ssh_public_key_to_ssh2_public_key(public_key1)) assert_equal(SSH2_PUBLIC_KEY2, SSHKey.ssh_public_key_to_ssh2_public_key(public_key2)) assert_equal(SSH2_PUBLIC_KEY2, SSHKey.ssh_public_key_to_ssh2_public_key(public_key2, {})) assert_equal(SSH2_PUBLIC_KEY3, SSHKey.ssh_public_key_to_ssh2_public_key(public_key3, {'Comment' => '1024-bit DSA with provided comment', 'x-private-use-header' => 'some value that is long enough to go to wrap around to a new line.'})) end def test_exponent assert_equal 35, @key1.key_object.e.to_i assert_equal 35, @key2.key_object.e.to_i end def test_modulus assert_equal 21959919395955180268707532246136630338880737002345156586705317733493418045367765414088155418090419238250026039981229751319343545922377196559932805781226688384973919515037364518167604848468288361633800200593870224270802677578686553567598208927704479575929054501425347794297979215349516030584575472280923909378896367886007339003194417496761108245404573433556449606964806956220743380296147376168499567508629678037211105349574822849913423806275470761711930875368363589001630573570236600319099783704171412637535837916991323769813598516411655563604244942820475880695152610674934239619752487880623016350579174487901241422633, @key1.key_object.n.to_i assert_equal 25041821909255634338594631709409930006900629565221199423527442992482865961613864019776541767273966885435978473182530882048894721263421597979360058644777295324028381353356143013803778109979347540540538361684778724178534886189535456555760676722894784592989232554962714835255398111716791175503010379276254975882143986862239829255392231575481418297073759441882528318940783011390002193682320028951031205422825662402426266933458263786546846123394508656926946338411182471843223455365249418245551220933173115037201835242811305615780499842939975996344432437312062643436832423359634116147870328774728910949553186982115987967787, @key2.key_object.n.to_i end def test_fingerprint assert_equal KEY1_MD5_FINGERPRINT, @key1.md5_fingerprint assert_equal KEY1_MD5_FINGERPRINT, @key1.fingerprint # Aliased method assert_equal KEY2_MD5_FINGERPRINT, @key2.md5_fingerprint assert_equal KEY3_MD5_FINGERPRINT, @key3.md5_fingerprint if ecdsa_supported? assert_equal KEY4_MD5_FINGERPRINT, @key4.md5_fingerprint else assert_raises(NotImplementedError) { @key4.md5_fingerprint } end assert_equal KEY1_SHA1_FINGERPRINT, @key1.sha1_fingerprint assert_equal KEY2_SHA1_FINGERPRINT, @key2.sha1_fingerprint assert_equal KEY3_SHA1_FINGERPRINT, @key3.sha1_fingerprint if ecdsa_supported? assert_equal KEY4_SHA1_FINGERPRINT, @key4.sha1_fingerprint else assert_raises(NotImplementedError) { @key4.sha1_fingerprint } end assert_equal KEY1_SHA256_FINGERPRINT, @key1.sha256_fingerprint assert_equal KEY2_SHA256_FINGERPRINT, @key2.sha256_fingerprint assert_equal KEY3_SHA256_FINGERPRINT, @key3.sha256_fingerprint if ecdsa_supported? assert_equal KEY4_SHA256_FINGERPRINT, @key4.sha256_fingerprint else assert_raises(NotImplementedError) { @key4.sha256_fingerprint } end assert_equal KEY1_MD5_FINGERPRINT, SSHKey.md5_fingerprint(SSH_PRIVATE_KEY1) assert_equal KEY1_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY1}") assert_equal KEY2_MD5_FINGERPRINT, SSHKey.md5_fingerprint(SSH_PRIVATE_KEY2) assert_equal KEY2_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY2} me@me.com") assert_equal KEY3_MD5_FINGERPRINT, SSHKey.md5_fingerprint(SSH_PRIVATE_KEY3) assert_equal KEY3_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-dss #{SSH_PUBLIC_KEY3}") assert_equal KEY4_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY4}") assert_equal ED25519_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519}") assert_equal ECDSA_256_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256} me@me.com") assert_equal ECDSA_384_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ecdsa-sha2-nistp384 #{SSH_PUBLIC_KEY_ECDSA_384} me@me.com") assert_equal ECDSA_521_MD5_FINGERPRINT, SSHKey.md5_fingerprint("ecdsa-sha2-nistp521 #{SSH_PUBLIC_KEY_ECDSA_521} me@me.com") assert_equal KEY1_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint(SSH_PRIVATE_KEY1) assert_equal KEY1_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY1}") assert_equal KEY2_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint(SSH_PRIVATE_KEY2) assert_equal KEY2_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY2} me@me.com") assert_equal KEY3_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint(SSH_PRIVATE_KEY3) assert_equal KEY3_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-dss #{SSH_PUBLIC_KEY3}") assert_equal KEY4_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY4}") assert_equal ED25519_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519}") assert_equal ECDSA_256_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256} me@me.com") assert_equal ECDSA_384_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ecdsa-sha2-nistp384 #{SSH_PUBLIC_KEY_ECDSA_384} me@me.com") assert_equal ECDSA_521_SHA1_FINGERPRINT, SSHKey.sha1_fingerprint("ecdsa-sha2-nistp521 #{SSH_PUBLIC_KEY_ECDSA_521} me@me.com") assert_equal KEY1_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint(SSH_PRIVATE_KEY1) assert_equal KEY1_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY1}") assert_equal KEY2_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint(SSH_PRIVATE_KEY2) assert_equal KEY2_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-rsa #{SSH_PUBLIC_KEY2} me@me.com") assert_equal KEY3_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint(SSH_PRIVATE_KEY3) assert_equal KEY3_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-dss #{SSH_PUBLIC_KEY3}") assert_equal KEY4_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY4}") assert_equal ED25519_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ssh-ed25519 #{SSH_PUBLIC_KEY_ED25519}") assert_equal ECDSA_256_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ecdsa-sha2-nistp256 #{SSH_PUBLIC_KEY_ECDSA_256} me@me.com") assert_equal ECDSA_384_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ecdsa-sha2-nistp384 #{SSH_PUBLIC_KEY_ECDSA_384} me@me.com") assert_equal ECDSA_521_SHA256_FINGERPRINT, SSHKey.sha256_fingerprint("ecdsa-sha2-nistp521 #{SSH_PUBLIC_KEY_ECDSA_521} me@me.com") end def test_bits assert_equal 2048, @key1.bits assert_equal 2048, @key2.bits assert_equal 1024, @key3.bits if ecdsa_supported? assert_equal 256, @key4.bits else assert_raises(NotImplementedError) { @key4.bits } end assert_equal 512, SSHKey.generate(:bits => 512).bits end def test_randomart assert_equal KEY1_RANDOMART, @key1.randomart assert_equal KEY2_RANDOMART, @key2.randomart assert_equal KEY3_RANDOMART, @key3.randomart if ecdsa_supported? assert_equal KEY4_RANDOMART, @key4.randomart else assert_raises(NotImplementedError) { @key4.randomart } end if ecdsa_supported? assert_equal KEY4_RANDOMART_USING_SHA256_DIGEST, @key4.randomart("SHA256") assert_equal KEY4_RANDOMART_USING_SHA384_DIGEST, @key4.randomart("SHA384") assert_equal KEY4_RANDOMART_USING_SHA512_DIGEST, @key4.randomart("SHA512") end end def test_sshfp assert_equal KEY1_SSHFP, @key1.sshfp("localhost") assert_equal KEY2_SSHFP, @key2.sshfp("localhost") assert_equal KEY3_SSHFP, @key3.sshfp("localhost") end end class SSHKeyEncryptedTest < Test::Unit::TestCase ENCRYPTED_PRIVATE_KEY = <<-EOF -----BEGIN RSA PRIVATE KEY----- Proc-Type: 4,ENCRYPTED DEK-Info: AES-128-CBC,3514D8812B519059944A811726594515 fSr1v51I65MZrSs7u12nype6RH6NS15xN5FDPaPKV++EBPxysEzicU5QHDt/aHa3 t87nXkra1M400+zNgFcfi5Ga7w5SBmqEjdNgwhUV2/j1Yqqlr5c7l804OfIPxdE6 ELoWH7pen72JnlZe6gXq495W96QTg3IzIWdiKEbKJlEwrNBligqT7GB2mup8nY1D o71R07dIrvfDy3xVgCoRjX4LKUilO6nRnwVCFRgVQTEVKclqt8NiSFGMjzv3iekR f1fJ8Wm6CiST8zdetIXgMnHEK1KELhNeMhI/42Tn/gHPDsckBiKLtM+85OOT92wh L9o/KUySdcsb/ld0yT/kAc99/wqNitHAqUEcLshIWDVhqoT1XK46hEuRN782AN2U shQKirF8QFopYF+u9K2Q0mr1EsYaBWOFFBR7EiwFvEYOx+ad6qGQGPcxWhbf6eCU D///9g1g5q8nWb80UH9Hw1aMhIA+VTlIasM6XJKmGr1LapxlrYsqRovPwkgOQg01 jhSV1fy10bbaFBwd9qTdTTVqa368/e3/TxF2VKhDaqoy5lqvRqKzGJxi3ubzDuz9 m3qRTCgy1v3XI5DgcjWt5xC5gZLHjKf79fQKRJjuEnWALahpDVWQ6PRCuqPfyph5 /vVqGHqvA53HJ9pmXz4J9qtQQ2gkYRj1m2tlRJjtGRMqnAj7bpcDKIrdLudOiWB0 FXwmsXljzPaf/SPUa+tGg7jbh+Jq+72vdpo1ijJtLXhWQAJasIbvSXOVHbZ6YhJj vES98gJPzevqemS//C1DMrr0ci6pM9ciT2szkrg71zRacnfqrjeZUI7qHKAsRbD5 258Jj6BeFPeQSrUg8sqQdoPxVTNnVr2bOB5SNfh7gqLanPksi6Kr7XNIzsYP5Wzf ADAElPdcRRwYc2kLVqugZTMLSn1r8rQjEyQ8/TT2QefI4ma8mCrBKgqYX+SDbhMJ +KUrah0jCgj86z6fSNkNHaKuzvGCovZsJHXt2SoIWVYWVUz91IHPKXXycIqvf3Yj 9HFpJRAPh30MYBgiImCJjmk8tqKGn0Tc80vOsrKMlVuMIIu2eNrddrnHUzSbjIHr tTtSDvsJ/Bn/6+Ox77U/FKg6s6/6PxOA1a+ffKkBXB/g4jS5CfGZl1owb3kX91C/ a+bcNWp07DsaTaZd/0UEL2gIvaEuCULgyIvmnBPCOY6Pc5GGegWEJne/sk7j9W4I 59YtVfcSXiovYR/QEywytfN/tfPxKfUoqNMIxLkukjFYz4Zzk5kXEeI+1lcry398 UQSaOboSDKY6boX4rWgiiqyn5LN+47eAIZPO+zsWXky16F04JpT7V7XqZPXQn7vI pMAoPCkT4qE9Gp2CcSj2l2CoZ3ZA5lOs6Wvxuz0q1zd0uSe8O81/3rnw28DthDQO SuzrY0HinPEFomwMGbfhosB5kOmBXEk7XbSWWHhK0QG63CYqp5caUst2Mie21b0Y FgFTrS1VqUiqDjCmt8F8UCPQS89aFm096wqtmwDO+VWKanuHUUShtTPlYyLe1RTm wqh1BBa05ydM0Vf1NagFB4JNT1qSIL5x4XtkOFwqcdXWYvwYfT8PkZjX/kz+W7jb -----END RSA PRIVATE KEY----- EOF DECRYPTED_PRIVATE_KEY = <<-EOF -----BEGIN RSA PRIVATE KEY----- MIIEpAIBAAKCAQEA33H9u4rG0SVMzK8PFyIi30kVHvogmpKVOQL2g2tozi2GipkA imzoCW9jIx1jo6zo0kMKCbMdJCNUc93tQxkJAAWA07WYwE9z+J6MC/3urb4Q0UAZ orlNyN3pPP8ATFIQomd0wW/EHwbmknlJOIZY4MNUQpNJunnBAAzZyLef6+sbLzQZ wc0/vgtmatoCxh4mZWFuFMopkMqScYDKxL30xXXPRGfJvAVWZDnY3ErJSe7uyefI Zo/lp4tsH5XGMcFm+nLs++nJjYZ6Ud/ie1g+ZCJv5Qit1m7zCVjiJ4RVMC/4yTb7 CO8n4PhX8gR9d1DRPVvlKa1sZfQeULEIXMniKQIDAQABAoIBAQDEXS74s5rJjhgS AP4n/E3dICK5mGMytAMDmUD+eVQfbQ7BmnhJLjA0qnjbESbRXlE1Bsk5gPjpG0tK kAvEXan1JOD0LLDSwIBQSzUUDNLGSTQKUGS3BlX/YlVoz0h5ydzofDa1D/2wrqXO r1vTmu1ciQvxffLbN8iOvLxfkk+uSMhqnhf/q3WVinu+VALPg8e/v3p4VbnSfP4D eClBiMKEKRFdsa9xBxShijcX1HxjIvp3zDgb127fo2iFw09PIHUZCUo6M77iGrQY mscoA+5q1qSyD6Btw0EkKK55ytNMC2T+KfGYV0ySwhadmM+5+G64etvNGCn/j7CU rhuRhMlpAoGBAPiJlcWaNVw3ttlea+jllxnaCEOmG29NGWxw080XRvjSIrltPAiE 8e4FynAMx49aXKsaB8Df2WspdKBxHJgv1U0sapADwxlMO8erj2eDSRqy6bwnI4CT T+vvo2vdmkvkV0D9RskXCi5tgTO5FYnf7CjON6JLkg83V3vzyjsKlvDzAoGBAOYn iC50OdZ84U58wQUsvwXFuyzMQX9r+h0jL2tIDv/yYlMWg9tNt0HkGUOo7H1ZVhdL 9Z1B0Ztl2qoJipcQvhzfwdo4XwuLk7D0bOAfZo9YMbU5Jqy+rqE5yv/P4wa7ba4S uUQYvSuv54CtiOZDFyK8dU7y9mm+no3Fvrd9RwdzAoGANzlLACctqBnxFQd37r3k /yeFIpLsEaUN+xxu02lSqcL3WEA/UJ1JrFu5CYCtbtrjMFmOU3rpsnf5pBS+B8rJ GGbAHtPXK+3Wcp1aNePkAHy0lswThWQ2I/SRWUxaFnbcNGKSsefeqUZHqRh9Aq+w p7h6gCNOhvcDB1W6H7hQpaUCgYEAyBRDygaWJUVI5N+FOUduBMmhb09d/TTUKTJm TcBF8fE30v12wVZtYqW15ODcPhhExFnverc2Tf6cukczKSKP8y/+KQPqdHHxgdrr L2d81E6aX+4AFhpqW5SPShXiSf70WWjDkFRlV65C9dVmdq6KVVM6M9j5qHHjCmKG 6qLI9csCgYBuhFwwI9DiYvJPR1LJZnJtE0qZiTwpmCjU2LoBRsywvuBeyXtwpmIM 5IgfgXXLK5qK/+cp9047T5rzT6ndu5fNZINPzynA8tNhTtHXK8l/GT/iq8Rd6AcM WJmIe8EnUDhHqg7Z2h5tGpX1QPMSA4G8RGPPyrcd3v0G/PZ6pFALlQ== -----END RSA PRIVATE KEY----- EOF DECRYPTED_KEY_FINGERPRINT = "2e:ba:06:b1:c7:13:37:24:6f:2d:3a:ba:45:e2:b4:78" def setup @key_encrypted = SSHKey.new(ENCRYPTED_PRIVATE_KEY, :passphrase => "password") end def test_encrypted_private_key_can_be_decrypted assert_equal DECRYPTED_PRIVATE_KEY, @key_encrypted.private_key end def test_encrypted_private_key_matches_when_reencrypted key = SSHKey.new(@key_encrypted.encrypted_private_key, :passphrase => "password") assert_equal DECRYPTED_PRIVATE_KEY, key.private_key assert_equal DECRYPTED_KEY_FINGERPRINT, key.md5_fingerprint end end sshkey-3.0.0/Rakefile0000644000004100000410000000040714570752160014525 0ustar www-datawww-datarequire "rake" require "rake/testtask" desc "Default: run unit tests." task :default => :test desc "Test the sshkey gem" Rake::TestTask.new(:test) do |t| t.libs << "lib" t.libs << "test" t.test_files = FileList['test/*_test.rb'] t.verbose = true end sshkey-3.0.0/Gemfile0000644000004100000410000000013214570752160014346 0ustar www-datawww-datasource "https://rubygems.org" gem "jruby-openssl", ">= 0.8.2", platform: :jruby gemspec sshkey-3.0.0/LICENSE0000644000004100000410000000204514570752160014065 0ustar www-datawww-dataCopyright (c) 2011-2023 James Miller Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. sshkey-3.0.0/README.md0000644000004100000410000003154314570752160014344 0ustar www-datawww-data# SSHKey Generate private and public SSH keys (RSA, DSA, and ECDSA supported) using pure Ruby. ## Requirements Tested / supported on CRuby 2.5+ and JRuby. ## Installation gem install sshkey ## Usage ### Generate a new key When generating a new keypair the default key type is 2048-bit RSA, but you can supply the `type` (RSA or DSA or ECDSA) and `bits` in the options. You can also (optionally) supply a `comment` or `passphrase`. ```ruby k = SSHKey.generate k = SSHKey.generate( type: "DSA", bits: 1024, comment: "foo@bar.com", passphrase: "foobar" ) ``` ### Use your existing key Return an SSHKey object from an existing RSA or DSA or ECDSA private key (provided as a string). ```ruby f = File.read(File.expand_path("~/.ssh/id_rsa")) k = SSHKey.new(f, comment: "foo@bar.com") ``` ### The SSHKey object #### Private and public keys Fetch the private and public keys as strings. Note that the `public_key` is the RSA or DSA or ECDSA public key, not an SSH public key. ```ruby k.private_key # => "-----BEGIN RSA PRIVATE KEY-----\nMIIEoAIBAAKCAQEAvR7l72CT7UBP6P+02Iut8gKKbKyekz/pQxnckPp1VafuaIwC\nMvYfP4ffVJTcY5IhU9mISNxZf6YDQ0TuD1aOrZYG9wsIgGY0nXhOUZxe/Q5I+V7D\nOI/hSzKF7W0cNCvaJPUSo8+soCLNSQ5mjnV3sRZ6uJwGFN30i1GulqHHKkx3vGxb\niaAL9YG58dPSbPGHFTA/epqUyd1fzCuWHyL9dHW7aw4RroNyEtVdiftAQfaK20I2\nueeDfuEtCPaxQYFQqbz5kKnXQx3fwHRpC7/84xHxsrY576evGxHw4p5EJD37scNN\ncneTG3Ly79/VVSAlrSm6ltutx0+S70scCqK0ewIDAQABAoH/MjwC15LPuDVdBIbn\ngp2XlrEWE8fGV1ainzA/ZkMg55+ztBF8hAzcQAPXTqA76jbmo18k1DWzkDSIqVWl\n5m0XeQRg1T4ZBAIh97H9G7BtispAl/yT3nJZZaAF8wsIctMzHp36VYjUUbTs0nsA\nwtZw9JkEAAVxmBlc26TWuyw9uv4fYXuR+uOsWH8jTTVPvxM9FaCCdK+dOMnswm7Y\nlOAlJj5dANkB2KPwIeE461ThyMo9GHEjpsvciMhKLuBoTSucNkhdgapAmYTSI+/1\nf1cA/KEdCMs9ANr1HFujeS01+N1Xrw/yW6EazaDN1oFHCVORtlB295Eac0Wq6y/P\npf1BAoGBAPIw4HQWsolU3f4FdIvc2POAcSJDRgt++I9Qt/QXq1SJ2dGKIveFiJgo\nZjCfHQFVZ8xl64cLzQ1WagZA1JBbbk9g5RxHDxRv7q+Kn3ogugDo9GUoQvpuuAU6\nXHoR/mLinDorJUnttL3U49xTMfrrut4qkUg+daBVptPtylpio6EDAoGBAMfnYq08\nfd/cPEQ2XPeswgtzXsKNLqA6UXBM7ZauKaFLByjy8peMMF6JPOYlBKQif5k+Egmu\nWIe8oTm8Nn5Ymt32bEd+MkHUC7kFzQeiXnM3u0oKzJMXLAvjSTs296g50YM5zJTC\nl64ACQmQOLZ9tdKorl52ZcmdbBEcZ2uwRvkpAoGAKhs5SrWPgLTSi5FjO9W/mkYg\nZTaQ/PqsOC5ubO+Yh/AXgIiln6cFon6Tlax0HIE+tJibpDT3B3SYplGrIxXiTcao\nzovEIWd8deSB6Xe7HuFhbBzd2DBbqf0FiuuJ8KM5ShuqNfovzDkxDGMic198c5eu\n/oJtbNy3Tm0vGxu/GwUCgYAgmRPXShkAq0pMmUzZups+AMdAFIO47ymelXzc6HOz\ncKevPsbefabZk6mRohG6rkF+fMe2Om8HW3QzFQUR32MJtQh9NA//+hMbTd3cU9bx\nFPJ+pXostkehfKPReyoxjZQjwQYicAUKA8l1fMYyxBclTgp5Lvd0RC5+L9KRlgJM\n2QKBgGVIWRNVpGg38dDqdq/4ue1BoTFhqoMGi6WQm3xa+NH+lyJGacdUhGRz8PxN\nhVKpIj8ljg2Rq/CA9qSgL/Z9rhn8QUMWULuAroCp0S2pMBtZ2RB+Mg2FdVFR9/Ft\nfG7co6mKUGkFPtr48EMfeKY88BRsp3yGOsROGdDsCHItjOVH\n-----END RSA PRIVATE KEY-----\n" k.public_key # => "-----BEGIN PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAvR7l72CT7UBP6P+02Iut\n8gKKbKyekz/pQxnckPp1VafuaIwCMvYfP4ffVJTcY5IhU9mISNxZf6YDQ0TuD1aO\nrZYG9wsIgGY0nXhOUZxe/Q5I+V7DOI/hSzKF7W0cNCvaJPUSo8+soCLNSQ5mjnV3\nsRZ6uJwGFN30i1GulqHHKkx3vGxbiaAL9YG58dPSbPGHFTA/epqUyd1fzCuWHyL9\ndHW7aw4RroNyEtVdiftAQfaK20I2ueeDfuEtCPaxQYFQqbz5kKnXQx3fwHRpC7/8\n4xHxsrY576evGxHw4p5EJD37scNNcneTG3Ly79/VVSAlrSm6ltutx0+S70scCqK0\newIDAQAB\n-----END PUBLIC KEY-----\n" ``` Fetch the SSH public key as a string. ```ruby k.ssh_public_key # => "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7" ``` #### Encryption If a passphrase is set when a key is generated or by setting the `passphrase` accessor, you can fetch the encrypted version of the private key. ```ruby k.passphrase = "foo" # => "foo" k.encrypted_private_key # => "-----BEGIN RSA PRIVATE KEY-----\nProc-Type: 4,ENCRYPTED\nDEK-Info: AES-128-CBC,748B766CFB185C3BD1D7E4D31113EBDA\n\ntWbfOuAjBlSZdq3kdJTLZJ7prjNWOKuGpeesNfVZDziIaZNCUakvgnUFdX3IZZnj\nEYITfjZ1TEUY3EkemL/57txiP3A4iOMDK2JGg8lp3G45x6c9XucJ2YxgvMye/ugP\n014MzLvBNunWq8TolkFj4gbc+WCqsyFqGdpRsf/hx7PcLDd2nvS5zxjBAPno87KN\nYgEnZYrpyl01ePucwFVWlrlGJdc0+F+0Ms5gpjMds56YL3Rwv9BlWzapVtrqN29r\nZg0otylPAyuGJOQ8srDOa+pbSySXvcdoKfR6xQ9fIB0tUfGgrH3c5O0/rEW7FSiO\n6ng4ntXXOKKkQfCezXQVvqMjKtKAbcKaPYAvrB2Gp2VIPUN5tN52nKuWvQWPA0P/\nm/uKiFkvzDWj8xMEOdzDAG9/7ysX+T5angvhfT23+NEdGIlPZLDRHI3f+2Itn99f\nvVoDYUXiyd5h7VwOTn6scebbvyPY8DiWpB/5iaU8WBPr7TVTl9n2z+Gmy4eg3wS0\nTU4hGlKv7MiITO2+dOCZTVrKn9/gTgmtyiLucb4huBH88Nsj4zWnTrVjMMBWsTUD\nkzvo9081zgDKKeawcbZYdI1Tc4epV7SMTHpx1ztzIlPdQ6kRaWomwMSarQeSlhJe\naFx67cde6M3Kc3LOgE0VT+3NvVLnkDwkytwnQKLd6oT3d1kFxWXjMwqiPbSzz3bf\nkOhG01gsJDXIzAgDlOlhE+Qlsd3yc734UIH98rTFMVB00HS36WLuz3hh+Ew4rsrf\nDIuRIdxL/4GVdQ8J5WpSoN0tF5iQD1wpEMU2vUjYjj9TZkhpOpnK3UVvbKd4WPsV\n956XJT7ZDvX4+pvHc5GJq/UX5h42kycY0hftUoLapXt5Nhb/fL8mUT8Eix184uiO\n5mA3fgRP3oGJ28N653X/+kL2YhXCeTd2VjkVhKruuoex96Igyt8W7wW5y7MOPezf\nwfo8IzidcJcDR1W4OEOXr+oDlCE1CLGCzmenR+AUIisqz45yb5G076l8PQkI3NWC\nBhT1YbTds4QzrndIDZgMm65ZCaklm+FVHWV61rXd9rlugcq+flQuXAE/EnFtySMc\n3lztrzXulLXzgLrYG355JbQFddwehO7LdxKZA9LHC9/odcoVI9RBj1CzshYtlftR\nn56nxPTIxRTVjQdgCZ6VcjZhwv1I904NtGm4SZupiShXsbHzAfaeJ54GMq4PRlgN\nmH7JrI9/puBb1dLD0XNgPtmYIo18v9e7g9o+un/wDtxCTxhQtD0npPo1IuW4cW7q\n07lZPwGkN2FD2PNTBGXeQ6/EXTHxlyFn62GSr+DmXu0O8MJS827Vd4b8QmKzRTxf\nFEmtVhiD15KlrQxwajmhqfY6KHRxbBuG/w7ioRr2Vl0G9NmKwmJkQO8dM+mJ8rVE\nsWvm8xVm1bowahzDVPnyFUUjuGNi6jFElkv8zvlQUoTcjSZHPrQSHuX742f5Spph\nLLCHdGZ2Ry8UGPlqKtvd6V/z25NsBgbuit+hNkBsdIztH7MVGAhKSMgk1FgXmKzV\nmZnPigq5WAHtIvojzI9NfZxU2Avif0yymXNtOnipw0sCJ0notN8NuGdQEmyxThqW\n-----END RSA PRIVATE KEY-----\n" ``` #### Comments Keys can optionally have a comment that is shown as part of the public SSH key. Get or set the key's comment with the `comment` accessor. ```ruby k.comment # => nil k.comment = "me@me.com" # => "me@me.com" k.ssh_public_key # => "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7 me@me.com" k.ssh2_public_key # => "---- BEGIN SSH2 PUBLIC KEY ----\nComment: me@me.com\nAAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+n\nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5\nXsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoA\nv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I\n9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVIC\nWtKbqW263HT5LvSxwKorR7\n---- END SSH2 PUBLIC KEY ----" ``` #### Bit length Determine the strength of the key in bits as an integer. ```ruby k.bits # => 2048 ``` #### Fingerprints It is often helpful to use a fingerprint to visually or programmatically check if one key matches another. Fetch an MD5, SHA1, or SHA256 fingerprint of the SSH public key. ```ruby k.md5_fingerprint # => "04:1b:d4:18:df:87:60:94:8c:83:8a:7b:5a:35:59:3d" k.sha1_fingerprint # => "e5:c2:43:9e:e4:0c:0c:47:82:7a:3b:e9:61:13:bd:9c:43:eb:4c:b7" k.sha256_fingerprint # => "x1GEnx1SRY/QwxjMAoyO6mhQlaBedDHtYLEmfeUXy3o=" ``` #### Public Key Directives Add optional directives prefixed to the public key that will be enforced when a key is authenticated. Accepts a string or an array of strings. ```ruby k.directives = "no-pty" # => ["no-pty"] k.directives = [ "no-port-forwarding", "no-X11-forwarding", "no-agent-forwarding", "no-pty", "command='/home/user/bin/authprogs'" ] # => ["no-port-forwarding", "no-X11-forwarding", "no-agent-forwarding", "no-pty", "command='/home/user/bin/authprogs'"] k.ssh_public_key # => "no-port-forwarding,no-X11-forwarding,no-agent-forwarding,no-pty,command='/home/user/bin/authprogs' ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7" ``` #### Randomart Generate [OpenSSH compatible](http://www.opensource.apple.com/source/OpenSSH/OpenSSH-175/openssh/key.c) ASCII art fingerprints. ```ruby puts k.randomart +--[ RSA 2048]----+ |o+ o.. | |..+.o | | ooo | |.++. o | |+o+ + S | |.. + o . | | . + . | | . . | | Eo. | +-----------------+ ``` #### Original OpenSSL key object Return the original [OpenSSL::PKey::RSA](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/PKey/RSA.html) or [OpenSSL::PKey::DSA](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/PKey/DSA.html) or [OpenSSL::PKey::EC](https://ruby-doc.org/3.2.2/exts/openssl/OpenSSL/PKey/EC.html)object. ```ruby k.key_object # => -----BEGIN RSA PRIVATE KEY-----\nMIIEowI... ``` ### Existing SSH public keys #### Validation Determine if a given SSH public key is valid. Very useful to test user input of public keys to make sure they accurately copy/pasted the key. Just pass the SSH public key as a string. Returns false if the key is invalid. ```ruby SSHKey.valid_ssh_public_key? "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7" # => true ``` #### Bit length Determine the strength of the key in bits as an integer. Returns `SSHKey::PublicKeyError` if bits cannot be determined. ```ruby SSHKey.ssh_public_key_bits "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7" # => 2048 ``` #### Fingerprints Fetch an MD5, SHA1, or SHA256 fingerprint of the SSH public key. Returns `SSHKey::PublicKeyError` if a fingerprint cannot be determined. ```ruby SSHKey.fingerprint "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7" # => "04:1b:d4:18:df:87:60:94:8c:83:8a:7b:5a:35:59:3d" SSHKey.sha1_fingerprint "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7" # => "e5:c2:43:9e:e4:0c:0c:47:82:7a:3b:e9:61:13:bd:9c:43:eb:4c:b7" SSHKey.sha256_fingerprint "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7" # => "x1GEnx1SRY/QwxjMAoyO6mhQlaBedDHtYLEmfeUXy3o=" ``` #### Convert to SSH2 Public Key Convert an existing SSH Public Key into an SSH2 Public key. Returns `SSHKey::PublicKeyError` if a valid key cannot be generated. ```ruby SSHKey.ssh_public_key_to_ssh2_public_key "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5XsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoAv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVICWtKbqW263HT5LvSxwKorR7 me@me.com" # => "---- BEGIN SSH2 PUBLIC KEY ----\nComment: me@me.com\nAAAAB3NzaC1yc2EAAAADAQABAAABAQC9HuXvYJPtQE/o/7TYi63yAopsrJ6TP+lDGdyQ+n\nVVp+5ojAIy9h8/h99UlNxjkiFT2YhI3Fl/pgNDRO4PVo6tlgb3CwiAZjSdeE5RnF79Dkj5\nXsM4j+FLMoXtbRw0K9ok9RKjz6ygIs1JDmaOdXexFnq4nAYU3fSLUa6WoccqTHe8bFuJoA\nv1gbnx09Js8YcVMD96mpTJ3V/MK5YfIv10dbtrDhGug3IS1V2J+0BB9orbQja554N+4S0I\n9rFBgVCpvPmQqddDHd/AdGkLv/zjEfGytjnvp68bEfDinkQkPfuxw01yd5MbcvLv39VVIC\nWtKbqW263HT5LvSxwKorR7\n---- END SSH2 PUBLIC KEY ----" ``` ## Copyright Copyright (c) 2011-2023 James Miller