diff --git a/mocks/ca.go b/mocks/ca.go index ecab7e270cc..ed303b9e7c5 100644 --- a/mocks/ca.go +++ b/mocks/ca.go @@ -2,15 +2,17 @@ package mocks import ( "context" + "crypto/sha256" "crypto/x509" "encoding/pem" "fmt" "time" - capb "github.com/letsencrypt/boulder/ca/proto" - corepb "github.com/letsencrypt/boulder/core/proto" "google.golang.org/grpc" "google.golang.org/protobuf/types/known/timestamppb" + + capb "github.com/letsencrypt/boulder/ca/proto" + corepb "github.com/letsencrypt/boulder/core/proto" ) // MockCA is a mock of a CA that always returns the cert from PEM in response to @@ -20,7 +22,7 @@ type MockCA struct { } // IssuePrecertificate is a mock -func (ca *MockCA) IssuePrecertificate(ctx context.Context, _ *capb.IssueCertificateRequest, _ ...grpc.CallOption) (*capb.IssuePrecertificateResponse, error) { +func (ca *MockCA) IssuePrecertificate(ctx context.Context, req *capb.IssueCertificateRequest, _ ...grpc.CallOption) (*capb.IssuePrecertificateResponse, error) { if ca.PEM == nil { return nil, fmt.Errorf("MockCA's PEM field must be set before calling IssueCertificate") } @@ -29,8 +31,10 @@ func (ca *MockCA) IssuePrecertificate(ctx context.Context, _ *capb.IssueCertific if err != nil { return nil, err } + profHash := sha256.Sum256([]byte(req.CertProfileName)) return &capb.IssuePrecertificateResponse{ - DER: cert.Raw, + DER: cert.Raw, + CertProfileHash: profHash[:8], }, nil } diff --git a/ra/ra.go b/ra/ra.go index 3d0a8926b57..ea609da8f8b 100644 --- a/ra/ra.go +++ b/ra/ra.go @@ -1225,7 +1225,7 @@ func (ra *RegistrationAuthorityImpl) issueCertificateOuter( // Step 3: Issue the Certificate cert, err := ra.issueCertificateInner( - ctx, csr, accountID(order.RegistrationID), orderID(order.Id)) + ctx, csr, order.CertificateProfileName, accountID(order.RegistrationID), orderID(order.Id)) // Step 4: Fail the order if necessary, and update metrics and log fields var result string @@ -1282,6 +1282,7 @@ func (ra *RegistrationAuthorityImpl) issueCertificateOuter( func (ra *RegistrationAuthorityImpl) issueCertificateInner( ctx context.Context, csr *x509.CertificateRequest, + profileName string, acctID accountID, oID orderID) (*x509.Certificate, error) { if features.Get().AsyncFinalize { @@ -1303,9 +1304,10 @@ func (ra *RegistrationAuthorityImpl) issueCertificateInner( } issueReq := &capb.IssueCertificateRequest{ - Csr: csr.Raw, - RegistrationID: int64(acctID), - OrderID: int64(oID), + Csr: csr.Raw, + RegistrationID: int64(acctID), + OrderID: int64(oID), + CertProfileName: profileName, } precert, err := ra.CA.IssuePrecertificate(ctx, issueReq) if err != nil { @@ -1323,10 +1325,11 @@ func (ra *RegistrationAuthorityImpl) issueCertificateInner( } cert, err := ra.CA.IssueCertificateForPrecertificate(ctx, &capb.IssueCertificateForPrecertificateRequest{ - DER: precert.DER, - SCTs: scts, - RegistrationID: int64(acctID), - OrderID: int64(oID), + DER: precert.DER, + SCTs: scts, + RegistrationID: int64(acctID), + OrderID: int64(oID), + CertProfileHash: precert.CertProfileHash, }) if err != nil { return nil, wrapError(err, "issuing certificate for precertificate") diff --git a/ra/ra_test.go b/ra/ra_test.go index 2f272622669..20dfc579f13 100644 --- a/ra/ra_test.go +++ b/ra/ra_test.go @@ -3633,7 +3633,7 @@ func TestIssueCertificateInnerErrs(t *testing.T) { // Mock the CA ra.CA = tc.Mock // Attempt issuance - _, err = ra.issueCertificateInner(ctx, csrOb, accountID(Registration.Id), orderID(order.Id)) + _, err = ra.issueCertificateInner(ctx, csrOb, order.CertificateProfileName, accountID(Registration.Id), orderID(order.Id)) // We expect all of the testcases to fail because all use mocked CAs that deliberately error test.AssertError(t, err, "issueCertificateInner with failing mock CA did not fail") // If there is an expected `error` then match the error message @@ -3652,6 +3652,60 @@ func TestIssueCertificateInnerErrs(t *testing.T) { } } +type MockCARecordingProfile struct { + inner *mocks.MockCA + profileName string + profileHash []byte +} + +func (ca *MockCARecordingProfile) IssuePrecertificate(ctx context.Context, req *capb.IssueCertificateRequest, _ ...grpc.CallOption) (*capb.IssuePrecertificateResponse, error) { + ca.profileName = req.CertProfileName + return ca.inner.IssuePrecertificate(ctx, req) +} + +func (ca *MockCARecordingProfile) IssueCertificateForPrecertificate(ctx context.Context, req *capb.IssueCertificateForPrecertificateRequest, _ ...grpc.CallOption) (*corepb.Certificate, error) { + ca.profileHash = req.CertProfileHash + return ca.inner.IssueCertificateForPrecertificate(ctx, req) +} + +func TestIssueCertificateInnerWithProfile(t *testing.T) { + _, _, ra, fc, cleanup := initAuthorities(t) + defer cleanup() + + // Generate a reasonable-looking CSR and cert to pass the matchesCSR check. + testKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + test.AssertNotError(t, err, "generating test key") + csrDER, err := x509.CreateCertificateRequest(rand.Reader, &x509.CertificateRequest{DNSNames: []string{"example.com"}}, testKey) + test.AssertNotError(t, err, "creating test csr") + csr, err := x509.ParseCertificateRequest(csrDER) + test.AssertNotError(t, err, "parsing test csr") + certDER, err := x509.CreateCertificate(rand.Reader, &x509.Certificate{ + SerialNumber: big.NewInt(1), + DNSNames: []string{"example.com"}, + NotBefore: fc.Now(), + BasicConstraintsValid: true, + ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, + }, &x509.Certificate{}, testKey.Public(), testKey) + test.AssertNotError(t, err, "creating test cert") + certPEM := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certDER}) + + // Use a mock CA that will record the profile name and profile hash included + // in the RA's request messages. Populate it with the cert generated above. + mockCA := MockCARecordingProfile{inner: &mocks.MockCA{PEM: certPEM}} + ra.CA = &mockCA + + // The basic mocks.StorageAuthority always succeeds on FinalizeOrder, which is + // the only SA call that issueCertificateInner makes. + ra.SA = &mocks.StorageAuthority{} + + // Call issueCertificateInner with the CSR generated above and the profile + // name "default", which will cause the mockCA to return a specific hash. + _, err = ra.issueCertificateInner(context.Background(), csr, "default", 1, 1) + test.AssertNotError(t, err, "issuing cert with profile name") + test.AssertEquals(t, mockCA.profileName, "default") + test.AssertByteEquals(t, mockCA.profileHash, []byte{0x37, 0xa8, 0xee, 0xc1, 0xce, 0x19, 0x68, 0x7d}) +} + func TestNewOrderMaxNames(t *testing.T) { _, _, ra, _, cleanUp := initAuthorities(t) defer cleanUp()