diff --git a/ca/ca.go b/ca/ca.go index 5ce5c07d01f..ebad4c709cf 100644 --- a/ca/ca.go +++ b/ca/ca.go @@ -3,12 +3,8 @@ package ca import ( "bytes" "context" - "crypto" "crypto/rand" - "crypto/sha256" "crypto/x509" - "crypto/x509/pkix" - "encoding/asn1" "encoding/hex" "errors" "fmt" @@ -251,7 +247,7 @@ func (ca *certificateAuthorityImpl) IssueCertificate(ctx context.Context, req *c return nil, err } - subjectKeyId, err := generateSKID(csr.PublicKey) + subjectKeyId, err := core.GenerateSKID(csr.PublicKey) if err != nil { return nil, fmt.Errorf("computing subject key ID: %w", err) } @@ -492,29 +488,6 @@ func (ca *certificateAuthorityImpl) generateSerialNumber() *big.Int { return serialBigInt } -// generateSKID computes the Subject Key Identifier using one of the methods in -// RFC 7093 Section 2 Additional Methods for Generating Key Identifiers: -// The keyIdentifier [may be] composed of the leftmost 160-bits of the -// SHA-256 hash of the value of the BIT STRING subjectPublicKey -// (excluding the tag, length, and number of unused bits). -func generateSKID(pk crypto.PublicKey) ([]byte, error) { - pkBytes, err := x509.MarshalPKIXPublicKey(pk) - if err != nil { - return nil, err - } - - var pkixPublicKey struct { - Algo pkix.AlgorithmIdentifier - BitString asn1.BitString - } - if _, err := asn1.Unmarshal(pkBytes, &pkixPublicKey); err != nil { - return nil, err - } - - skid := sha256.Sum256(pkixPublicKey.BitString.Bytes) - return skid[0:20:20], nil -} - // verifyTBSCertIsDeterministic verifies that x509.CreateCertificate signing // operation is deterministic and produced identical DER bytes between the given // lint certificate and leaf certificate. If the DER byte equality check fails diff --git a/ca/ca_test.go b/ca/ca_test.go index 81f4737c6f7..0bafcebabdc 100644 --- a/ca/ca_test.go +++ b/ca/ca_test.go @@ -941,18 +941,6 @@ func TestNoteSignError(t *testing.T) { test.AssertMetricWithLabelsEquals(t, metrics.signErrorCount, prometheus.Labels{"type": "HSM"}, 1) } -func TestGenerateSKID(t *testing.T) { - t.Parallel() - key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - test.AssertNotError(t, err, "Error generating key") - - sha256skid, err := generateSKID(key.Public()) - test.AssertNotError(t, err, "Error generating SKID") - test.AssertEquals(t, len(sha256skid), 20) - test.AssertEquals(t, cap(sha256skid), 20) - features.Reset() -} - func TestVerifyTBSCertIsDeterministic(t *testing.T) { t.Parallel() diff --git a/cmd/ceremony/cert.go b/cmd/ceremony/cert.go index 35ee938f929..e70c3829bd7 100644 --- a/cmd/ceremony/cert.go +++ b/cmd/ceremony/cert.go @@ -2,10 +2,8 @@ package main import ( "crypto" - "crypto/sha256" "crypto/x509" "crypto/x509/pkix" - "encoding/asn1" "errors" "fmt" "io" @@ -13,6 +11,8 @@ import ( "regexp" "slices" "time" + + "github.com/letsencrypt/boulder/core" ) type policyInfoConfig struct { @@ -187,25 +187,8 @@ var stringToKeyUsage = map[string]x509.KeyUsage{ "Cert Sign": x509.KeyUsageCertSign, } -func generateSKID(pk []byte) ([]byte, error) { - var pkixPublicKey struct { - Algo pkix.AlgorithmIdentifier - BitString asn1.BitString - } - if _, err := asn1.Unmarshal(pk, &pkixPublicKey); err != nil { - return nil, err - } - - // RFC 7093 Section 2 Additional Methods for Generating Key Identifiers: The - // keyIdentifier [may be] composed of the leftmost 160-bits of the SHA-256 - // hash of the value of the BIT STRING subjectPublicKey (excluding the tag, - // length, and number of unused bits). - skid := sha256.Sum256(pkixPublicKey.BitString.Bytes) - return skid[0:20:20], nil -} - // makeTemplate generates the certificate template for use in x509.CreateCertificate -func makeTemplate(randReader io.Reader, profile *certProfile, pubKey []byte, tbcs *x509.Certificate, ct certType) (*x509.Certificate, error) { +func makeTemplate(randReader io.Reader, profile *certProfile, pubKey crypto.PublicKey, tbcs *x509.Certificate, ct certType) (*x509.Certificate, error) { // Handle "unrestricted" vs "restricted" subordinate CA profile specifics. if ct == crossCert && tbcs == nil { return nil, fmt.Errorf("toBeCrossSigned cert field was nil, but was required to gather EKUs for the lint cert") @@ -220,7 +203,7 @@ func makeTemplate(randReader io.Reader, profile *certProfile, pubKey []byte, tbc issuingCertificateURL = []string{profile.IssuerURL} } - subjectKeyID, err := generateSKID(pubKey) + subjectKeyID, err := core.GenerateSKID(pubKey) if err != nil { return nil, err } diff --git a/cmd/ceremony/cert_test.go b/cmd/ceremony/cert_test.go index 2fd8f8c11f9..c0cbc96d723 100644 --- a/cmd/ceremony/cert_test.go +++ b/cmd/ceremony/cert_test.go @@ -6,7 +6,6 @@ import ( "crypto/rand" "crypto/x509" "crypto/x509/pkix" - "encoding/hex" "errors" "fmt" "io/fs" @@ -22,16 +21,6 @@ import ( "github.com/letsencrypt/boulder/test" ) -// samplePubkey returns a slice of bytes containing an encoded -// SubjectPublicKeyInfo for an example public key. -func samplePubkey() []byte { - pubKey, err := hex.DecodeString("3059301306072a8648ce3d020106082a8648ce3d03010703420004b06745ef0375c9c54057098f077964e18d3bed0aacd54545b16eab8c539b5768cc1cea93ba56af1e22a7a01c33048c8885ed17c9c55ede70649b707072689f5e") - if err != nil { - panic(err) - } - return pubKey -} - func realRand(_ pkcs11.SessionHandle, length int) ([]byte, error) { r := make([]byte, length) _, err := rand.Read(r) @@ -55,9 +44,13 @@ func TestMakeSubject(t *testing.T) { func TestMakeTemplateEnforcesRootNoEKUs(t *testing.T) { s, ctx := pkcs11helpers.NewSessionWithMock() randReader := newRandReader(s) - pubKey := samplePubkey() ctx.GenerateRandomFunc = realRand + key, err := ecdsa.GenerateKey(elliptic.P256(), nil) + if err != nil { + t.Fatalf("generating test keypair: %s", err) + } + workingRootProfile := &certProfile{ EKUs: "", KeyUsages: []string{"Digital Signature", "CRL Sign"}, @@ -65,27 +58,27 @@ func TestMakeTemplateEnforcesRootNoEKUs(t *testing.T) { NotBefore: "2026-05-11 00:00:00", NotAfter: "2026-05-12 00:00:00", } - _, err := makeTemplate(randReader, workingRootProfile, pubKey, nil, rootCert) + _, err = makeTemplate(randReader, workingRootProfile, key.Public(), nil, rootCert) if err != nil { t.Fatalf("makeTemplate with workingRootProfile: %s", err) } workingRootProfile.EKUs = "none" - _, err = makeTemplate(randReader, workingRootProfile, pubKey, nil, rootCert) + _, err = makeTemplate(randReader, workingRootProfile, key.Public(), nil, rootCert) if err != nil { t.Fatalf("makeTemplate with workingRootProfile: %s", err) } brokenRootProfile := *workingRootProfile brokenRootProfile.EKUs = "both" - _, err = makeTemplate(randReader, &brokenRootProfile, pubKey, nil, rootCert) + _, err = makeTemplate(randReader, &brokenRootProfile, key.Public(), nil, rootCert) if err == nil { t.Errorf("makeTemplate with brokenRootProfile: got nil error, want error") } brokenRootProfile = *workingRootProfile brokenRootProfile.EKUs = "unintelligible" - _, err = makeTemplate(randReader, &brokenRootProfile, pubKey, nil, rootCert) + _, err = makeTemplate(randReader, &brokenRootProfile, key.Public(), nil, rootCert) if err == nil { t.Errorf("makeTemplate with brokenRootProfile: got nil error, want error") } @@ -94,9 +87,13 @@ func TestMakeTemplateEnforcesRootNoEKUs(t *testing.T) { func TestMakeTemplateEnforcesCrossCertEKUs(t *testing.T) { s, ctx := pkcs11helpers.NewSessionWithMock() randReader := newRandReader(s) - pubKey := samplePubkey() ctx.GenerateRandomFunc = realRand + key, err := ecdsa.GenerateKey(elliptic.P256(), nil) + if err != nil { + t.Fatalf("generating test keypair: %s", err) + } + tbcsCert := &x509.Certificate{ SerialNumber: big.NewInt(666), Subject: pkix.Name{ @@ -117,7 +114,7 @@ func TestMakeTemplateEnforcesCrossCertEKUs(t *testing.T) { NotAfter: "2026-05-12 00:00:00", } - template, err := makeTemplate(randReader, crossCertProfile, pubKey, tbcsCert, crossCert) + template, err := makeTemplate(randReader, crossCertProfile, key.Public(), tbcsCert, crossCert) if err != nil { t.Fatalf("makeTemplate with crossCertProfile: %s", err) } @@ -128,7 +125,7 @@ func TestMakeTemplateEnforcesCrossCertEKUs(t *testing.T) { } crossCertProfile.EKUs = "server" - _, err = makeTemplate(randReader, crossCertProfile, pubKey, tbcsCert, crossCert) + _, err = makeTemplate(randReader, crossCertProfile, key.Public(), tbcsCert, crossCert) if err != nil { t.Fatalf("makeTemplate with crossCertProfile: %s", err) } @@ -139,7 +136,7 @@ func TestMakeTemplateEnforcesCrossCertEKUs(t *testing.T) { // This will error because the tbcsCert has [serverAuth], but "both" means [serverAuth, clientAuth] on the cross sign crossCertProfile.EKUs = "both" - _, err = makeTemplate(randReader, crossCertProfile, pubKey, tbcsCert, crossCert) + _, err = makeTemplate(randReader, crossCertProfile, key.Public(), tbcsCert, crossCert) if err == nil { t.Fatalf("makeTemplate with \"both\" and to-be-cross-signed certificate that has \"serverAuth\": got nil error, want error") } @@ -148,7 +145,7 @@ func TestMakeTemplateEnforcesCrossCertEKUs(t *testing.T) { liberalTBCS := *tbcsCert liberalTBCS.ExtKeyUsage = nil crossCertProfile.EKUs = "both" - _, err = makeTemplate(randReader, crossCertProfile, pubKey, &liberalTBCS, crossCert) + _, err = makeTemplate(randReader, crossCertProfile, key.Public(), &liberalTBCS, crossCert) if err != nil { t.Errorf("makeTemplate with \"both\" and liberal to-be-cross-signed certificate: %s", err) } @@ -157,7 +154,7 @@ func TestMakeTemplateEnforcesCrossCertEKUs(t *testing.T) { crossCertProfile.EKUs = "both" crossCertProfile.NotBefore = "2027-05-11 00:00:00" crossCertProfile.NotAfter = "2027-05-12 00:00:00" - _, err = makeTemplate(randReader, crossCertProfile, pubKey, &liberalTBCS, crossCert) + _, err = makeTemplate(randReader, crossCertProfile, key.Public(), &liberalTBCS, crossCert) if err == nil { t.Fatalf("makeTemplate with \"both\" and late notBefore: go nil error, want error") } @@ -169,9 +166,13 @@ func TestMakeTemplateEnforcesCrossCertEKUs(t *testing.T) { func TestMakeTemplateIntermediateEKUs(t *testing.T) { s, ctx := pkcs11helpers.NewSessionWithMock() randReader := newRandReader(s) - pubKey := samplePubkey() ctx.GenerateRandomFunc = realRand + key, err := ecdsa.GenerateKey(elliptic.P256(), nil) + if err != nil { + t.Fatalf("generating test keypair: %s", err) + } + intermediateProfile := &certProfile{ EKUs: "", KeyUsages: []string{"Digital Signature", "CRL Sign"}, @@ -180,7 +181,7 @@ func TestMakeTemplateIntermediateEKUs(t *testing.T) { NotAfter: "2026-05-12 00:00:00", } - template, err := makeTemplate(randReader, intermediateProfile, pubKey, nil, intermediateCert) + template, err := makeTemplate(randReader, intermediateProfile, key.Public(), nil, intermediateCert) if err != nil { t.Fatalf("makeTemplate with intermediateProfile: %s", err) } @@ -191,7 +192,7 @@ func TestMakeTemplateIntermediateEKUs(t *testing.T) { } intermediateProfile.EKUs = "server" - template, err = makeTemplate(randReader, intermediateProfile, pubKey, nil, intermediateCert) + template, err = makeTemplate(randReader, intermediateProfile, key.Public(), nil, intermediateCert) if err != nil { t.Fatalf("makeTemplate with intermediateProfile and EKUs: \"server\": %s", err) } @@ -202,7 +203,7 @@ func TestMakeTemplateIntermediateEKUs(t *testing.T) { } intermediateProfile.EKUs = "both" - template, err = makeTemplate(randReader, intermediateProfile, pubKey, nil, intermediateCert) + template, err = makeTemplate(randReader, intermediateProfile, key.Public(), nil, intermediateCert) if err != nil { t.Fatalf("makeTemplate with intermediateProfile and EKUs: \"both\": %s", err) } @@ -213,7 +214,7 @@ func TestMakeTemplateIntermediateEKUs(t *testing.T) { } intermediateProfile.EKUs = "unintelligible" - _, err = makeTemplate(randReader, intermediateProfile, pubKey, nil, intermediateCert) + _, err = makeTemplate(randReader, intermediateProfile, key.Public(), nil, intermediateCert) if err == nil { t.Fatalf("makeTemplate with intermediateProfile and EKUs: \"unintelligible\": got nil error, want error") } @@ -223,42 +224,46 @@ func TestMakeTemplateRoot(t *testing.T) { s, ctx := pkcs11helpers.NewSessionWithMock() profile := &certProfile{} randReader := newRandReader(s) - pubKey := samplePubkey() ctx.GenerateRandomFunc = realRand + key, err := ecdsa.GenerateKey(elliptic.P256(), nil) + if err != nil { + t.Fatalf("generating test keypair: %s", err) + } + profile.NotBefore = "1234" - _, err := makeTemplate(randReader, profile, pubKey, nil, rootCert) + _, err = makeTemplate(randReader, profile, key.Public(), nil, rootCert) test.AssertError(t, err, "makeTemplate didn't fail with invalid not before") profile.NotBefore = "2018-05-18 11:31:00" profile.NotAfter = "1234" - _, err = makeTemplate(randReader, profile, pubKey, nil, rootCert) + _, err = makeTemplate(randReader, profile, key.Public(), nil, rootCert) test.AssertError(t, err, "makeTemplate didn't fail with invalid not after") profile.NotAfter = "2018-05-18 11:31:00" profile.SignatureAlgorithm = "nope" - _, err = makeTemplate(randReader, profile, pubKey, nil, rootCert) + _, err = makeTemplate(randReader, profile, key.Public(), nil, rootCert) test.AssertError(t, err, "makeTemplate didn't fail with invalid signature algorithm") profile.SignatureAlgorithm = "SHA256WithRSA" ctx.GenerateRandomFunc = func(pkcs11.SessionHandle, int) ([]byte, error) { return nil, errors.New("bad") } - _, err = makeTemplate(randReader, profile, pubKey, nil, rootCert) + _, err = makeTemplate(randReader, profile, key.Public(), nil, rootCert) test.AssertError(t, err, "makeTemplate didn't fail when GenerateRandom failed") ctx.GenerateRandomFunc = realRand - _, err = makeTemplate(randReader, profile, pubKey, nil, rootCert) + _, err = makeTemplate(randReader, profile, key.Public(), nil, rootCert) test.AssertError(t, err, "makeTemplate didn't fail with empty key usages") profile.KeyUsages = []string{"asd"} - _, err = makeTemplate(randReader, profile, pubKey, nil, rootCert) + _, err = makeTemplate(randReader, profile, key.Public(), nil, rootCert) test.AssertError(t, err, "makeTemplate didn't fail with invalid key usages") profile.KeyUsages = []string{"Digital Signature", "CRL Sign"} profile.Policies = []policyInfoConfig{{}} - _, err = makeTemplate(randReader, profile, pubKey, nil, rootCert) + _, err = makeTemplate(randReader, profile, key.Public(), nil, rootCert) test.AssertError(t, err, "makeTemplate didn't fail with invalid (empty) policy OID") profile.Policies = []policyInfoConfig{{OID: "1.2.3"}, {OID: "1.2.3.4"}} @@ -267,7 +272,7 @@ func TestMakeTemplateRoot(t *testing.T) { profile.Country = "country" profile.CRLURL = "crl" profile.IssuerURL = "issuer" - cert, err := makeTemplate(randReader, profile, pubKey, nil, rootCert) + cert, err := makeTemplate(randReader, profile, key.Public(), nil, rootCert) test.AssertNotError(t, err, "makeTemplate failed when everything worked as expected") test.AssertEquals(t, cert.Subject.CommonName, profile.CommonName) test.AssertEquals(t, len(cert.Subject.Organization), 1) @@ -282,7 +287,7 @@ func TestMakeTemplateRoot(t *testing.T) { test.AssertEquals(t, len(cert.Policies), 2) test.AssertEquals(t, len(cert.ExtKeyUsage), 0) - cert, err = makeTemplate(randReader, profile, pubKey, nil, intermediateCert) + cert, err = makeTemplate(randReader, profile, key.Public(), nil, intermediateCert) test.AssertNotError(t, err, "makeTemplate failed when everything worked as expected") test.Assert(t, cert.MaxPathLenZero, "MaxPathLenZero not set in intermediate template") test.AssertEquals(t, len(cert.ExtKeyUsage), 1) @@ -293,7 +298,12 @@ func TestMakeTemplateRestrictedCrossCertificate(t *testing.T) { s, ctx := pkcs11helpers.NewSessionWithMock() ctx.GenerateRandomFunc = realRand randReader := newRandReader(s) - pubKey := samplePubkey() + + key, err := ecdsa.GenerateKey(elliptic.P256(), nil) + if err != nil { + t.Fatalf("generating test keypair: %s", err) + } + profile := &certProfile{ SignatureAlgorithm: "SHA256WithRSA", CommonName: "common name", @@ -318,7 +328,7 @@ func TestMakeTemplateRestrictedCrossCertificate(t *testing.T) { BasicConstraintsValid: true, } - cert, err := makeTemplate(randReader, profile, pubKey, &tbcsCert, crossCert) + cert, err := makeTemplate(randReader, profile, key.Public(), &tbcsCert, crossCert) test.AssertNotError(t, err, "makeTemplate failed when everything worked as expected") test.Assert(t, !cert.MaxPathLenZero, "MaxPathLenZero was set in cross-sign") test.AssertEquals(t, len(cert.ExtKeyUsage), 1) @@ -575,10 +585,3 @@ func TestLoadCert(t *testing.T) { _, err = loadCert("../../test/hierarchy/int-e1.key.pem") test.AssertError(t, err, "should have failed when trying to parse a private key") } - -func TestGenerateSKID(t *testing.T) { - sha256skid, err := generateSKID(samplePubkey()) - test.AssertNotError(t, err, "Error generating SKID") - test.AssertEquals(t, len(sha256skid), 20) - test.AssertEquals(t, cap(sha256skid), 20) -} diff --git a/cmd/ceremony/key.go b/cmd/ceremony/key.go index 2315a2081a3..5db5987d8a2 100644 --- a/cmd/ceremony/key.go +++ b/cmd/ceremony/key.go @@ -36,12 +36,10 @@ type generateArgs struct { } // keyInfo is a struct used to pass around information about the public key -// associated with the generated private key. der contains the DER encoding -// of the SubjectPublicKeyInfo structure for the public key. id contains the -// HSM key pair object ID. +// associated with the generated private key. id contains the HSM key pair +// object ID. type keyInfo struct { key crypto.PublicKey - der []byte id []byte } @@ -81,5 +79,5 @@ func generateKey(session *pkcs11helpers.Session, label string, outputPath string } log.Printf("Public key written to %q\n", outputPath) - return &keyInfo{key: pubKey, der: der, id: keyID}, nil + return &keyInfo{key: pubKey, id: keyID}, nil } diff --git a/cmd/ceremony/main.go b/cmd/ceremony/main.go index 604a06a3536..d7f87b80515 100644 --- a/cmd/ceremony/main.go +++ b/cmd/ceremony/main.go @@ -534,30 +534,28 @@ func signAndWriteCert(tbs, issuer *x509.Certificate, lintCert lintCert, subjectP return cert, nil } -// loadPubKey loads a PEM public key specified by filename. It returns a -// crypto.PublicKey, the PEM bytes of the public key, and an error. If an error -// exists, no public key or bytes are returned. The public key is checked by the -// GoodKey package. -func loadPubKey(filename string) (crypto.PublicKey, []byte, error) { +// loadPubKey loads a PEM public key specified by filename. The public key is +// checked by the GoodKey package. +func loadPubKey(filename string) (crypto.PublicKey, error) { keyPEM, err := os.ReadFile(filename) if err != nil { - return nil, nil, err + return nil, err } log.Printf("Loaded public key from %s\n", filename) block, _ := pem.Decode(keyPEM) if block == nil { - return nil, nil, fmt.Errorf("no data in cert PEM file %q", filename) + return nil, fmt.Errorf("no data in cert PEM file %q", filename) } key, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { - return nil, nil, err + return nil, err } err = kp.GoodKey(context.Background(), key) if err != nil { - return nil, nil, err + return nil, err } - return key, block.Bytes, nil + return key, nil } func rootCeremony(configBytes []byte) error { @@ -584,7 +582,7 @@ func rootCeremony(configBytes []byte) error { if err != nil { return fmt.Errorf("failed to retrieve signer: %s", err) } - template, err := makeTemplate(newRandReader(session), &config.CertProfile, keyInfo.der, nil, rootCert) + template, err := makeTemplate(newRandReader(session), &config.CertProfile, keyInfo.key, nil, rootCert) if err != nil { return fmt.Errorf("failed to create certificate profile: %s", err) } @@ -616,7 +614,7 @@ func intermediateCeremony(configBytes []byte) error { if err != nil { return fmt.Errorf("failed to validate config: %s", err) } - pub, pubBytes, err := loadPubKey(config.Inputs.PublicKeyPath) + pub, err := loadPubKey(config.Inputs.PublicKeyPath) if err != nil { return err } @@ -628,7 +626,7 @@ func intermediateCeremony(configBytes []byte) error { if err != nil { return err } - template, err := makeTemplate(randReader, &config.CertProfile, pubBytes, nil, intermediateCert) + template, err := makeTemplate(randReader, &config.CertProfile, pub, nil, intermediateCert) if err != nil { return fmt.Errorf("failed to create certificate profile: %s", err) } @@ -668,7 +666,7 @@ func crossCertCeremony(configBytes []byte) error { if err != nil { return fmt.Errorf("failed to validate config: %s", err) } - pub, pubBytes, err := loadPubKey(config.Inputs.PublicKeyPath) + pub, err := loadPubKey(config.Inputs.PublicKeyPath) if err != nil { return err } @@ -684,7 +682,7 @@ func crossCertCeremony(configBytes []byte) error { if err != nil { return err } - template, err := makeTemplate(randReader, &config.CertProfile, pubBytes, toBeCrossSigned, crossCert) + template, err := makeTemplate(randReader, &config.CertProfile, pub, toBeCrossSigned, crossCert) if err != nil { return fmt.Errorf("failed to create certificate profile: %s", err) } @@ -770,7 +768,7 @@ func csrCeremony(configBytes []byte) error { return fmt.Errorf("failed to validate config: %s", err) } - pub, _, err := loadPubKey(config.Inputs.PublicKeyPath) + pub, err := loadPubKey(config.Inputs.PublicKeyPath) if err != nil { return err } diff --git a/cmd/ceremony/main_test.go b/cmd/ceremony/main_test.go index 899cb2909cc..697d97ec172 100644 --- a/cmd/ceremony/main_test.go +++ b/cmd/ceremony/main_test.go @@ -24,21 +24,21 @@ func TestLoadPubKey(t *testing.T) { tmp := t.TempDir() key, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) - _, _, err := loadPubKey(path.Join(tmp, "does", "not", "exist")) + _, err := loadPubKey(path.Join(tmp, "does", "not", "exist")) test.AssertError(t, err, "should fail on non-existent file") test.AssertErrorIs(t, err, fs.ErrNotExist) - _, _, err = loadPubKey("../../test/hierarchy/README.md") + _, err = loadPubKey("../../test/hierarchy/README.md") test.AssertError(t, err, "should fail on non-PEM file") priv, _ := x509.MarshalPKCS8PrivateKey(key) _ = os.WriteFile(path.Join(tmp, "priv.pem"), pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: priv}), 0644) - _, _, err = loadPubKey(path.Join(tmp, "priv.pem")) + _, err = loadPubKey(path.Join(tmp, "priv.pem")) test.AssertError(t, err, "should fail on non-pubkey PEM") pub, _ := x509.MarshalPKIXPublicKey(key.Public()) _ = os.WriteFile(path.Join(tmp, "pub.pem"), pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pub}), 0644) - _, _, err = loadPubKey(path.Join(tmp, "pub.pem")) + _, err = loadPubKey(path.Join(tmp, "pub.pem")) test.AssertNotError(t, err, "should not have errored") } diff --git a/core/util.go b/core/util.go index 97892b122d0..6e9282de9d5 100644 --- a/core/util.go +++ b/core/util.go @@ -8,6 +8,8 @@ import ( "crypto/rsa" "crypto/sha256" "crypto/x509" + "crypto/x509/pkix" + "encoding/asn1" "encoding/base64" "encoding/hex" "encoding/pem" @@ -166,6 +168,29 @@ func PublicKeysEqual(a, b crypto.PublicKey) (bool, error) { } } +// GenerateSKID computes the Subject Key Identifier using one of the methods in +// RFC 7093 Section 2 Additional Methods for Generating Key Identifiers: +// The keyIdentifier [may be] composed of the leftmost 160-bits of the +// SHA-256 hash of the value of the BIT STRING subjectPublicKey +// (excluding the tag, length, and number of unused bits). +func GenerateSKID(pub crypto.PublicKey) ([]byte, error) { + pkBytes, err := x509.MarshalPKIXPublicKey(pub) + if err != nil { + return nil, err + } + + var pkixPublicKey struct { + Algo pkix.AlgorithmIdentifier + BitString asn1.BitString + } + if _, err := asn1.Unmarshal(pkBytes, &pkixPublicKey); err != nil { + return nil, err + } + + skid := sha256.Sum256(pkixPublicKey.BitString.Bytes) + return skid[0:20:20], nil +} + // SerialToString converts a certificate serial number (big.Int) to a String // consistently. func SerialToString(serial *big.Int) string { diff --git a/core/util_test.go b/core/util_test.go index a36b7307ab5..db4ad12b7e6 100644 --- a/core/util_test.go +++ b/core/util_test.go @@ -7,6 +7,7 @@ import ( "crypto/rand" "crypto/x509" "crypto/x509/pkix" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -122,6 +123,28 @@ func TestKeyDigestEquals(t *testing.T) { test.Assert(t, !KeyDigestEquals(struct{}{}, struct{}{}), "Unknown key types should not match anything") } +func TestGenerateSKID(t *testing.T) { + t.Parallel() + + // Test basics with a random key. + key1, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + test.AssertNotError(t, err, "Error generating key") + skid, err := GenerateSKID(key1.Public()) + test.AssertNotError(t, err, "Error generating SKID") + test.AssertEquals(t, len(skid), 20) + test.AssertEquals(t, cap(skid), 20) + + // Test specific output with the known test vector from RFC 7093 Section 3: + spkiDER, err := hex.DecodeString( + "3059301306072a8648ce3d020106082a8648ce3d030107034200047f7f35a79794c950060b8029fc8f363a28f11159692d9d34e6ac948190434735f833b1a66652dc514337aff7f5c9c75d670c019d95a5d639b72744c64a9128bb") + test.AssertNotError(t, err, "Error decoding test vector SPKI") + key2, err := x509.ParsePKIXPublicKey(spkiDER) + test.AssertNotError(t, err, "Error parsing test vector SPKI") + skid, err = GenerateSKID(key2) + test.AssertNotError(t, err, "Error generating SKID") + test.AssertEquals(t, hex.EncodeToString(skid), "bf37b3e5808fd46d54b28e846311bcce1cad2e1a") +} + // TestCertKeyDigest ensures that CertKeyDigest (which hashes a certificate's // SubjectPublicKeyInfo) and KeyDigest (which hashes an in-memory public key, // usually from a parsed JWK) produce the same result for the same underlying diff --git a/issuance/cert_test.go b/issuance/cert_test.go index aa184d00621..0e245f30227 100644 --- a/issuance/cert_test.go +++ b/issuance/cert_test.go @@ -21,12 +21,16 @@ import ( "github.com/jmhodges/clock" "github.com/letsencrypt/boulder/config" + "github.com/letsencrypt/boulder/core" "github.com/letsencrypt/boulder/ctpolicy/loglist" "github.com/letsencrypt/boulder/linter" "github.com/letsencrypt/boulder/test" ) var ( + // goodSKID is a fake subject key ID for tests which only exercise request + // validation. Tests which also run lints must use core.GenerateSKID because + // our custom lints verify that the SKID matches the actual public key. goodSKID = []byte{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9} ) @@ -357,9 +361,11 @@ func TestIssue(t *testing.T) { test.AssertNotError(t, err, "NewIssuer failed") pk, err := tc.generateFunc() test.AssertNotError(t, err, "failed to generate test key") + skid, err := core.GenerateSKID(pk.Public()) + test.AssertNotError(t, err, "failed to compute subject key ID") lintCertBytes, issuanceToken, err := signer.Prepare(defaultProfile(), &IssuanceRequest{ PublicKey: MarshalablePublicKey{pk.Public()}, - SubjectKeyId: goodSKID, + SubjectKeyId: skid, Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, DNSNames: []string{"example.com"}, IPAddresses: []net.IP{net.ParseIP("128.101.101.101"), net.ParseIP("3fff:aaa:a:c0ff:ee:a:bad:deed")}, @@ -440,9 +446,13 @@ func TestIssueDNSNamesOnly(t *testing.T) { if err != nil { t.Fatalf("ecdsa.GenerateKey: %s", err) } + skid, err := core.GenerateSKID(pk.Public()) + if err != nil { + t.Fatalf("core.GenerateSKID: %s", err) + } _, issuanceToken, err := signer.Prepare(defaultProfile(), &IssuanceRequest{ PublicKey: MarshalablePublicKey{pk.Public()}, - SubjectKeyId: goodSKID, + SubjectKeyId: skid, Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, DNSNames: []string{"example.com"}, NotBefore: fc.Now(), @@ -479,9 +489,13 @@ func TestIssueIPAddressesOnly(t *testing.T) { if err != nil { t.Fatalf("ecdsa.GenerateKey: %s", err) } + skid, err := core.GenerateSKID(pk.Public()) + if err != nil { + t.Fatalf("core.GenerateSKID: %s", err) + } _, issuanceToken, err := signer.Prepare(defaultProfile(), &IssuanceRequest{ PublicKey: MarshalablePublicKey{pk.Public()}, - SubjectKeyId: goodSKID, + SubjectKeyId: skid, Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, IPAddresses: []net.IP{net.ParseIP("128.101.101.101"), net.ParseIP("3fff:aaa:a:c0ff:ee:a:bad:deed")}, NotBefore: fc.Now(), @@ -521,10 +535,14 @@ func TestIssueWithCRLDP(t *testing.T) { if err != nil { t.Fatalf("ecdsa.GenerateKey: %s", err) } + skid, err := core.GenerateSKID(pk.Public()) + if err != nil { + t.Fatalf("core.GenerateSKID: %s", err) + } profile := defaultProfile() _, issuanceToken, err := signer.Prepare(profile, &IssuanceRequest{ PublicKey: MarshalablePublicKey{pk.Public()}, - SubjectKeyId: goodSKID, + SubjectKeyId: skid, Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, DNSNames: []string{"example.com"}, NotBefore: fc.Now(), @@ -561,9 +579,11 @@ func TestIssueCommonName(t *testing.T) { test.AssertNotError(t, err, "NewIssuer failed") pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) test.AssertNotError(t, err, "failed to generate test key") + skid, err := core.GenerateSKID(pk.Public()) + test.AssertNotError(t, err, "failed to compute subject key ID") ir := &IssuanceRequest{ PublicKey: MarshalablePublicKey{pk.Public()}, - SubjectKeyId: goodSKID, + SubjectKeyId: skid, Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, DNSNames: []string{"example.com", "www.example.com"}, NotBefore: fc.Now(), @@ -697,9 +717,11 @@ func TestIssueOmissions(t *testing.T) { pk, err := rsa.GenerateKey(rand.Reader, 2048) test.AssertNotError(t, err, "failed to generate test key") + skid, err := core.GenerateSKID(pk.Public()) + test.AssertNotError(t, err, "failed to compute subject key ID") _, issuanceToken, err := signer.Prepare(prof, &IssuanceRequest{ PublicKey: MarshalablePublicKey{pk.Public()}, - SubjectKeyId: goodSKID, + SubjectKeyId: skid, Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, DNSNames: []string{"example.com"}, CommonName: "example.com", @@ -726,9 +748,11 @@ func TestIssueCTPoison(t *testing.T) { test.AssertNotError(t, err, "NewIssuer failed") pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) test.AssertNotError(t, err, "failed to generate test key") + skid, err := core.GenerateSKID(pk.Public()) + test.AssertNotError(t, err, "failed to compute subject key ID") _, issuanceToken, err := signer.Prepare(defaultProfile(), &IssuanceRequest{ PublicKey: MarshalablePublicKey{pk.Public()}, - SubjectKeyId: goodSKID, + SubjectKeyId: skid, Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, DNSNames: []string{"example.com"}, IncludeCTPoison: true, @@ -774,9 +798,11 @@ func TestIssueSCTList(t *testing.T) { test.AssertNotError(t, err, "NewIssuer failed") pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) test.AssertNotError(t, err, "failed to generate test key") + skid, err := core.GenerateSKID(pk.Public()) + test.AssertNotError(t, err, "failed to compute subject key ID") _, issuanceToken, err := signer.Prepare(enforceSCTsProfile, &IssuanceRequest{ PublicKey: MarshalablePublicKey{pk.Public()}, - SubjectKeyId: goodSKID, + SubjectKeyId: skid, Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, DNSNames: []string{"example.com"}, NotBefore: fc.Now(), @@ -842,9 +868,11 @@ func TestIssueBadLint(t *testing.T) { test.AssertNotError(t, err, "NewIssuer failed") pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) test.AssertNotError(t, err, "failed to generate test key") + skid, err := core.GenerateSKID(pk.Public()) + test.AssertNotError(t, err, "failed to compute subject key ID") _, _, err = signer.Prepare(noSkipLintsProfile, &IssuanceRequest{ PublicKey: MarshalablePublicKey{pk.Public()}, - SubjectKeyId: goodSKID, + SubjectKeyId: skid, Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, DNSNames: []string{"example-com"}, NotBefore: fc.Now(), @@ -871,9 +899,11 @@ func TestIssuanceToken(t *testing.T) { pk, err := rsa.GenerateKey(rand.Reader, 2048) test.AssertNotError(t, err, "failed to generate test key") + skid, err := core.GenerateSKID(pk.Public()) + test.AssertNotError(t, err, "failed to compute subject key ID") _, issuanceToken, err := signer.Prepare(defaultProfile(), &IssuanceRequest{ PublicKey: MarshalablePublicKey{pk.Public()}, - SubjectKeyId: goodSKID, + SubjectKeyId: skid, Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, DNSNames: []string{"example.com"}, NotBefore: fc.Now(), @@ -890,7 +920,7 @@ func TestIssuanceToken(t *testing.T) { _, issuanceToken, err = signer.Prepare(defaultProfile(), &IssuanceRequest{ PublicKey: MarshalablePublicKey{pk.Public()}, - SubjectKeyId: goodSKID, + SubjectKeyId: skid, Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, DNSNames: []string{"example.com"}, NotBefore: fc.Now(), @@ -918,9 +948,11 @@ func TestInvalidProfile(t *testing.T) { test.AssertNotError(t, err, "NewIssuer failed") pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) test.AssertNotError(t, err, "failed to generate test key") + skid, err := core.GenerateSKID(pk.Public()) + test.AssertNotError(t, err, "failed to compute subject key ID") _, _, err = signer.Prepare(defaultProfile(), &IssuanceRequest{ PublicKey: MarshalablePublicKey{pk.Public()}, - SubjectKeyId: goodSKID, + SubjectKeyId: skid, Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, DNSNames: []string{"example.com"}, NotBefore: fc.Now(), @@ -932,7 +964,7 @@ func TestInvalidProfile(t *testing.T) { _, _, err = signer.Prepare(defaultProfile(), &IssuanceRequest{ PublicKey: MarshalablePublicKey{pk.Public()}, - SubjectKeyId: goodSKID, + SubjectKeyId: skid, Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, DNSNames: []string{"example.com"}, NotBefore: fc.Now(), @@ -967,9 +999,11 @@ func TestMismatchedProfiles(t *testing.T) { pk, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) test.AssertNotError(t, err, "failed to generate test key") + skid, err := core.GenerateSKID(pk.Public()) + test.AssertNotError(t, err, "failed to compute subject key ID") _, issuanceToken, err := issuer1.Prepare(cnProfile, &IssuanceRequest{ PublicKey: MarshalablePublicKey{pk.Public()}, - SubjectKeyId: goodSKID, + SubjectKeyId: skid, Serial: []byte{1, 2, 3, 4, 5, 6, 7, 8, 9}, CommonName: "example.com", DNSNames: []string{"example.com"},