From e3adc279e06dfeef62f1096c3b132889d89a3233 Mon Sep 17 00:00:00 2001 From: Piotr Zduniak Date: Fri, 11 Sep 2026 20:37:08 +0200 Subject: [PATCH] Use RFC 7093 SHA-512 truncated SKIs Always generate subject key identifiers as the leftmost 160 bits of SHA-512 so Pebble can run in FIPS mode and stays distinct from Boulder's truncated SHA-256 method. Co-authored-by: Cursor --- ca/ca.go | 13 ++++++++----- ca/ca_test.go | 28 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 5 deletions(-) diff --git a/ca/ca.go b/ca/ca.go index e4489e1a..c0165926 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -7,7 +7,7 @@ import ( "crypto/elliptic" "crypto/rand" "crypto/rsa" - "crypto/sha1" + "crypto/sha512" "crypto/x509" "crypto/x509/pkix" "encoding/asn1" @@ -76,7 +76,11 @@ func makeSerial() *big.Int { return serial } -// Taken from https://github.com/cloudflare/cfssl/blob/b94e044bb51ec8f5a7232c71b1ed05dbe4da96ce/signer/signer.go#L221-L244 +// makeSubjectKeyID generates a Subject Key Identifier according to +// RFC 7093 Section 2 method 3: the leftmost 160 bits of the SHA-512 hash of +// the subjectPublicKey BIT STRING (excluding the tag, length, and number of +// unused bits). This differs from Boulder's RFC 7093 Section 2 method 1 +// (truncated SHA-256) so ACME clients cannot overfit on Let's Encrypt. func makeSubjectKeyID(key crypto.PublicKey) ([]byte, error) { // Marshal the public key as ASN.1 pubAsDER, err := x509.MarshalPKIXPublicKey(key) @@ -94,9 +98,8 @@ func makeSubjectKeyID(key crypto.PublicKey) ([]byte, error) { return nil, err } - // Hash it according to https://tools.ietf.org/html/rfc5280#section-4.2.1.2 Method #1: - ski := sha1.Sum(pubInfo.SubjectPublicKey.Bytes) - return ski[:], nil + ski := sha512.Sum512(pubInfo.SubjectPublicKey.Bytes) + return ski[:20], nil } // makeKey and makeRootCert are adapted from MiniCA: diff --git a/ca/ca_test.go b/ca/ca_test.go index 970bf85b..016c8388 100644 --- a/ca/ca_test.go +++ b/ca/ca_test.go @@ -8,6 +8,7 @@ import ( "crypto/x509" "crypto/x509/pkix" "encoding/asn1" + "encoding/hex" "log" "net" "os" @@ -30,6 +31,33 @@ func makeCa() *CAImpl { return New(logger, db, "", "ecdsa", 0, 1, map[string]Profile{"default": {}}) } +func TestMakeSubjectKeyID(t *testing.T) { + // RFC 7093 Section 3 example P-256 public key. + publicKeyBytes, err := hex.DecodeString( + "047f7f35a79794c950060b8029fc8f363a28f11159692d9d34e6ac948190434735" + + "f833b1a66652dc514337aff7f5c9c75d670c019d95a5d639b72744c64a9128bb", + ) + if err != nil { + t.Fatal(err) + } + x, y := elliptic.Unmarshal(elliptic.P256(), publicKeyBytes) + if x == nil || y == nil { + t.Fatal("failed to parse RFC 7093 public key") + } + + got, err := makeSubjectKeyID(&ecdsa.PublicKey{Curve: elliptic.P256(), X: x, Y: y}) + if err != nil { + t.Fatal(err) + } + want, err := hex.DecodeString("907e7e9d05878a273d597f2aea91bdb6056245cb") + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(got, want) { + t.Fatalf("unexpected subject key identifier: got %x, want %x", got, want) + } +} + func makeCertOrderWithExtensions(extensions []pkix.Extension) core.Order { privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) if err != nil {