-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add utility methods to generate a key fingerprint
- Loading branch information
Showing
6 changed files
with
194 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,74 @@ | ||
package keyutil | ||
|
||
import ( | ||
"crypto" | ||
"crypto/sha256" | ||
"crypto/x509" | ||
"crypto/x509/pkix" | ||
"encoding/asn1" | ||
"fmt" | ||
|
||
"go.step.sm/crypto/fingerprint" | ||
) | ||
|
||
// FingerprintEncoding defines the supported encodings in certificate | ||
// fingerprints. | ||
type FingerprintEncoding = fingerprint.Encoding | ||
|
||
// Supported fingerprint encodings. | ||
const ( | ||
// DefaultFingerprint represents the base64 encoding of the fingerprint. | ||
DefaultFingerprint = FingerprintEncoding(0) | ||
// HexFingerprint represents the hex encoding of the fingerprint. | ||
HexFingerprint = fingerprint.HexFingerprint | ||
// Base64Fingerprint represents the base64 encoding of the fingerprint. | ||
Base64Fingerprint = fingerprint.Base64Fingerprint | ||
// Base64URLFingerprint represents the base64URL encoding of the fingerprint. | ||
Base64URLFingerprint = fingerprint.Base64URLFingerprint | ||
// Base64RawFingerprint represents the base64RawStd encoding of the fingerprint. | ||
Base64RawFingerprint = fingerprint.Base64RawFingerprint | ||
// Base64RawURLFingerprint represents the base64RawURL encoding of the fingerprint. | ||
Base64RawURLFingerprint = fingerprint.Base64RawURLFingerprint | ||
// EmojiFingerprint represents the emoji encoding of the fingerprint. | ||
EmojiFingerprint = fingerprint.EmojiFingerprint | ||
) | ||
|
||
// subjectPublicKeyInfo is a PKIX public key structure defined in RFC 5280. | ||
type subjectPublicKeyInfo struct { | ||
Algorithm pkix.AlgorithmIdentifier | ||
SubjectPublicKey asn1.BitString | ||
} | ||
|
||
// Fingerprint returns the SHA-256 fingerprint of an public key. | ||
// | ||
// The fingerprint is calculated from the encoding of the key according to RFC | ||
// 5280 section 4.2.1.2, but using SHA-256 instead of SHA-1. | ||
func Fingerprint(pub crypto.PublicKey) (string, error) { | ||
return EncodedFingerprint(pub, DefaultFingerprint) | ||
} | ||
|
||
// EncodedFingerprint returns the SHA-256 hash of the certificate using the | ||
// specified encoding. | ||
// | ||
// The fingerprint is calculated from the encoding of the key according to RFC | ||
// 5280 section 4.2.1.2, but using SHA-256 instead of SHA-1. | ||
func EncodedFingerprint(pub crypto.PublicKey, encoding FingerprintEncoding) (string, error) { | ||
b, err := x509.MarshalPKIXPublicKey(pub) | ||
if err != nil { | ||
return "", fmt.Errorf("error marshaling public key: %w", err) | ||
} | ||
var info subjectPublicKeyInfo | ||
if _, err = asn1.Unmarshal(b, &info); err != nil { | ||
return "", fmt.Errorf("error unmarshaling public key: %w", err) | ||
} | ||
if encoding == DefaultFingerprint { | ||
encoding = Base64Fingerprint | ||
} | ||
|
||
sum := sha256.Sum256(info.SubjectPublicKey.Bytes) | ||
fp := fingerprint.Fingerprint(sum[:], encoding) | ||
if fp == "" { | ||
return "", fmt.Errorf("error formatting fingerprint: unsupported encoding") | ||
} | ||
return "SHA256:" + fp, nil | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
package keyutil | ||
|
||
import ( | ||
"crypto" | ||
"crypto/x509" | ||
"encoding/pem" | ||
"os" | ||
"testing" | ||
) | ||
|
||
func readPublicKey(t *testing.T, filename string) crypto.PublicKey { | ||
t.Helper() | ||
b, err := os.ReadFile(filename) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
block, _ := pem.Decode(b) | ||
if block == nil { | ||
t.Fatal("error decoding pem") | ||
} | ||
pub, err := x509.ParsePKIXPublicKey(block.Bytes) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
return pub | ||
} | ||
|
||
func TestFingerprint(t *testing.T) { | ||
ecdsaKey := readPublicKey(t, "testdata/p256.pub") | ||
rsaKey := readPublicKey(t, "testdata/rsa.pub") | ||
ed25519Key := readPublicKey(t, "testdata/ed25519.pub") | ||
|
||
type args struct { | ||
pub crypto.PublicKey | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want string | ||
wantErr bool | ||
}{ | ||
{"ecdsa", args{ecdsaKey}, "SHA256:BlA/0e0DGQ8Gcpv+EPNDp3aa8O4TZ6VDLKMIXi40qlE=", false}, | ||
{"rsa", args{rsaKey}, "SHA256:Su5MWuU91vpyPy2YlX7lqTXomZ1AoGqKbvbZbf0Ff6M=", false}, | ||
{"ed25519", args{ed25519Key}, "SHA256:r/tA+Uv4M2ff1ZrAz8l+5mu0aJ1yOGwnWV5jDotBySI=", false}, | ||
{"fail", args{[]byte("not a key")}, "", true}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got, err := Fingerprint(tt.args.pub) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("Fingerprint() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
if got != tt.want { | ||
t.Errorf("Fingerprint() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} | ||
|
||
func TestEncodedFingerprint(t *testing.T) { | ||
ecdsaKey := readPublicKey(t, "testdata/p256.pub") | ||
rsaKey := readPublicKey(t, "testdata/rsa.pub") | ||
ed25519Key := readPublicKey(t, "testdata/ed25519.pub") | ||
|
||
type args struct { | ||
pub crypto.PublicKey | ||
encoding FingerprintEncoding | ||
} | ||
tests := []struct { | ||
name string | ||
args args | ||
want string | ||
wantErr bool | ||
}{ | ||
{"ecdsa", args{ecdsaKey, DefaultFingerprint}, "SHA256:BlA/0e0DGQ8Gcpv+EPNDp3aa8O4TZ6VDLKMIXi40qlE=", false}, | ||
{"rsa", args{rsaKey, HexFingerprint}, "SHA256:4aee4c5ae53dd6fa723f2d98957ee5a935e8999d40a06a8a6ef6d96dfd057fa3", false}, | ||
{"ed25519", args{ed25519Key, Base64RawURLFingerprint}, "SHA256:r_tA-Uv4M2ff1ZrAz8l-5mu0aJ1yOGwnWV5jDotBySI", false}, | ||
{"fail", args{[]byte("not a key"), DefaultFingerprint}, "", true}, | ||
{"fail bad encoding", args{ed25519Key, 100}, "", true}, | ||
} | ||
for _, tt := range tests { | ||
t.Run(tt.name, func(t *testing.T) { | ||
got, err := EncodedFingerprint(tt.args.pub, tt.args.encoding) | ||
if (err != nil) != tt.wantErr { | ||
t.Errorf("EncodedFingerprint() error = %v, wantErr %v", err, tt.wantErr) | ||
return | ||
} | ||
if got != tt.want { | ||
t.Errorf("EncodedFingerprint() = %v, want %v", got, tt.want) | ||
} | ||
}) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
-----BEGIN PUBLIC KEY----- | ||
MCowBQYDK2VwAyEAZ/fKNzve1MQ802E86hmqnAFRg8HwVUSycSyD8UhBWp0= | ||
-----END PUBLIC KEY----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
-----BEGIN PUBLIC KEY----- | ||
MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEcoeNMyqS0/lveul4nVhxHAtpK2UJ | ||
Qkodxz3jSYKLr2u7Q5/0psUwFSmhUK4ZN+8TNKFJNV72HjAbFtcARH/WkA== | ||
-----END PUBLIC KEY----- |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
-----BEGIN PUBLIC KEY----- | ||
MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAoifEXKTF5rOvbTXbXXFD | ||
DdXEE7iwI6S6cEf5N8pdCgbk+RdPYBwK0Y6BZU27md22ZJZmH9uts/6mx6Ajty/q | ||
YsngMEyLNhVIvu7H2KfKqed4vTRJ13/ID9HNB9eMCPnG+NUu1U/CEPk0p+/lhKM1 | ||
mQyyS6at6wmI1Wn7jiJQeCoS9fQNl5OsevnAU0304jwQHcuFhBAQTwxmLr2iG3+8 | ||
f8VHL14YlmcYXl4RWFEvgsoe8KGdQRUHaCLpCQAaO2OmEEYZGO+4UWRMS/kKUrDS | ||
RHiX9oGUDnweP2YJ4VAymOkrWpQMJeXJi+pp8fWyl/D+SWCPT/+O3IFitb/ff1QA | ||
xw81+Q2ascCmZ61nq3bvrKcGqjjMDYxrXut+LxxHnZqeTCmTShMQJTRnrzMP5le9 | ||
4FN6h6zpI6jcr6friMzec3B87z9XtFN7xlNLCOOi7LkzikvKta4flDUCEnPfZFes | ||
OOQMfkG1W8PFyWzZtEyWPneVhttncsoZsM7gM1QsGw+lAgMBAAE= | ||
-----END PUBLIC KEY----- |