Skip to content

Commit

Permalink
Add sshfp support (#30)
Browse files Browse the repository at this point in the history
as per

      https://tools.ietf.org/html/rfc4255
      https://tools.ietf.org/html/rfc6594

this is similar to

     ssh-keygen -r localhost -f /etc/ssh/ssh_host_rsa_key
  • Loading branch information
agx authored and bensie committed Dec 14, 2018
1 parent ba6d3bb commit ae8e50d
Show file tree
Hide file tree
Showing 2 changed files with 60 additions and 0 deletions.
29 changes: 29 additions & 0 deletions lib/sshkey.rb
Expand Up @@ -2,6 +2,7 @@
require 'base64'
require 'digest/md5'
require 'digest/sha1'
require 'digest/sha2'

class SSHKey
SSH_TYPES = {
Expand All @@ -12,6 +13,13 @@ class SSHKey
"ecdsa-sha2-nistp384" => "ecdsa",
"ecdsa-sha2-nistp521" => "ecdsa",
}

SSHFP_TYPES = {
"rsa" => 1,
"dsa" => 2,
"ecdsa" => 3,
"ed25519" => 4,
}
SSH_CONVERSION = {"rsa" => ["e", "n"], "dsa" => ["p", "q", "g", "pub_key"]}
SSH2_LINE_LENGTH = 70 # +1 (for line wrap '/' character) must be <= 72

Expand Down Expand Up @@ -112,6 +120,16 @@ def sha256_fingerprint(key)
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
Expand All @@ -135,6 +153,13 @@ def ssh_public_key_to_ssh2_public_key(ssh_public_key, headers = nil)
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)
Expand Down Expand Up @@ -341,6 +366,10 @@ def randomart
output
end

def sshfp(hostname)
SSHKey.format_sshfp_record(hostname, @type, ssh_public_key_conversion)
end

def directives=(directives)
@directives = Array[directives].flatten.compact
end
Expand Down
31 changes: 31 additions & 0 deletions test/sshkey_test.rb
Expand Up @@ -142,6 +142,21 @@ class SSHKeyTest < Test::Unit::TestCase
| |
| |
+-----------------+
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
Expand Down Expand Up @@ -349,6 +364,15 @@ def test_ssh_public_key_validation_with_newlines
assert !SSHKey.valid_ssh_public_key?(invalid4)
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"
Expand Down Expand Up @@ -448,6 +472,13 @@ def test_randomart
assert_equal KEY2_RANDOMART, @key2.randomart
assert_equal KEY3_RANDOMART, @key3.randomart
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
Expand Down

0 comments on commit ae8e50d

Please sign in to comment.