diff --git a/ssh/certs.go b/ssh/certs.go index 385770036a..318e9741d9 100644 --- a/ssh/certs.go +++ b/ssh/certs.go @@ -6,6 +6,7 @@ package ssh import ( "bytes" + "crypto/md5" "errors" "fmt" "io" @@ -470,6 +471,13 @@ func (c *Certificate) Verify(data []byte, sig *Signature) error { return c.Key.Verify(data, sig) } +// Fingerprint returns the key's fingerprint. It is part of the +// PublicKey interface. +func (c *Certificate) Fingerprint() string { + md5sum := md5.Sum(c.Key.Marshal()) + return rfc4716hex(md5sum[:]) +} + func parseSignatureBody(in []byte) (out *Signature, rest []byte, ok bool) { format, in, ok := parseString(in) if !ok { diff --git a/ssh/keys.go b/ssh/keys.go index 8ce4185853..8917f4a327 100644 --- a/ssh/keys.go +++ b/ssh/keys.go @@ -10,6 +10,7 @@ import ( "crypto/dsa" "crypto/ecdsa" "crypto/elliptic" + "crypto/md5" "crypto/rsa" "crypto/x509" "encoding/asn1" @@ -200,6 +201,10 @@ type PublicKey interface { // Verify that sig is a signature on the given data using this // key. This function will hash the data appropriately first. Verify(data []byte, sig *Signature) error + + // Fingerprint returns the user presentation of the key's + // fingerprint as described by RFC 4716 section 4. + Fingerprint() string } // A Signer can create signatures that verify against a public key. @@ -217,6 +222,10 @@ type rsaPublicKey rsa.PublicKey func (r *rsaPublicKey) Type() string { return "ssh-rsa" } +func (r *rsaPublicKey) Fingerprint() string { + md5sum := md5.Sum(r.Marshal()) + return rfc4716hex(md5sum[:]) +} // parseRSA parses an RSA key according to RFC 4253, section 6.6. func parseRSA(in []byte) (out PublicKey, rest []byte, err error) { @@ -273,6 +282,11 @@ func (r *dsaPublicKey) Type() string { return "ssh-dss" } +func (r *dsaPublicKey) Fingerprint() string { + md5sum := md5.Sum(r.Marshal()) + return rfc4716hex(md5sum[:]) +} + // parseDSA parses an DSA key according to RFC 4253, section 6.6. func parseDSA(in []byte) (out PublicKey, rest []byte, err error) { var w struct { @@ -381,6 +395,11 @@ func (key *ecdsaPublicKey) nistID() string { panic("ssh: unsupported ecdsa key size") } +func (r *ecdsaPublicKey) Fingerprint() string { + md5sum := md5.Sum(r.Marshal()) + return rfc4716hex(md5sum[:]) +} + func supportedEllipticCurve(curve elliptic.Curve) bool { return curve == elliptic.P256() || curve == elliptic.P384() || curve == elliptic.P521() } @@ -644,3 +663,14 @@ func ParseDSAPrivateKey(der []byte) (*dsa.PrivateKey, error) { X: k.Pub, }, nil } + +func rfc4716hex(data []byte) string { + var fingerprint string + for i := 0; i < len(data); i++ { + fingerprint = fmt.Sprintf("%s%0.2x", fingerprint, data[i]) + if i != len(data)-1 { + fingerprint = fingerprint + ":" + } + } + return fingerprint +} diff --git a/ssh/keys_test.go b/ssh/keys_test.go index b4cceaffbb..9aa772df29 100644 --- a/ssh/keys_test.go +++ b/ssh/keys_test.go @@ -304,3 +304,15 @@ func TestInvalidEntry(t *testing.T) { t.Errorf("got valid entry for %q", authInvalid) } } + +func TestFingerprint(t *testing.T) { + dsa := testPublicKeys["dsa"] + if dsa.Fingerprint() != "c9:67:63:e7:b5:34:5c:72:e3:7d:41:1b:cc:cd:89:28" { + t.Errorf("got invalid fingerprint %s", dsa.Fingerprint()) + } + + rsa := testPublicKeys["rsa"] + if rsa.Fingerprint() != "5c:00:08:96:4e:02:63:6c:ab:15:1c:b6:ea:6c:59:37" { + t.Errorf("got invalid fingerprint %s", rsa.Fingerprint()) + } +}