From a7ae96365e7146f7fc09cfe0621909c91f56a982 Mon Sep 17 00:00:00 2001 From: Phil Porada Date: Tue, 5 Mar 2024 10:19:01 -0500 Subject: [PATCH 1/4] Add certificateProfileName to order message (cherry picked from commit 777d610d72aed26904fbc9047f1c1f141d2e4521) --- core/proto/core.pb.go | 62 ++++++++++++++++++++++++++----------------- core/proto/core.proto | 3 ++- 2 files changed, 39 insertions(+), 26 deletions(-) diff --git a/core/proto/core.pb.go b/core/proto/core.pb.go index a2ae1ffb69d..50852a4be29 100644 --- a/core/proto/core.pb.go +++ b/core/proto/core.pb.go @@ -700,17 +700,18 @@ type Order struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // Next unused field number: 14 - Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` - RegistrationID int64 `protobuf:"varint,2,opt,name=registrationID,proto3" json:"registrationID,omitempty"` - Expires *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=expires,proto3" json:"expires,omitempty"` - Error *ProblemDetails `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"` - CertificateSerial string `protobuf:"bytes,5,opt,name=certificateSerial,proto3" json:"certificateSerial,omitempty"` - Status string `protobuf:"bytes,7,opt,name=status,proto3" json:"status,omitempty"` - Names []string `protobuf:"bytes,8,rep,name=names,proto3" json:"names,omitempty"` - BeganProcessing bool `protobuf:"varint,9,opt,name=beganProcessing,proto3" json:"beganProcessing,omitempty"` - Created *timestamppb.Timestamp `protobuf:"bytes,13,opt,name=created,proto3" json:"created,omitempty"` - V2Authorizations []int64 `protobuf:"varint,11,rep,packed,name=v2Authorizations,proto3" json:"v2Authorizations,omitempty"` + // Next unused field number: 15 + Id int64 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"` + RegistrationID int64 `protobuf:"varint,2,opt,name=registrationID,proto3" json:"registrationID,omitempty"` + Expires *timestamppb.Timestamp `protobuf:"bytes,12,opt,name=expires,proto3" json:"expires,omitempty"` + Error *ProblemDetails `protobuf:"bytes,4,opt,name=error,proto3" json:"error,omitempty"` + CertificateSerial string `protobuf:"bytes,5,opt,name=certificateSerial,proto3" json:"certificateSerial,omitempty"` + Status string `protobuf:"bytes,7,opt,name=status,proto3" json:"status,omitempty"` + Names []string `protobuf:"bytes,8,rep,name=names,proto3" json:"names,omitempty"` + BeganProcessing bool `protobuf:"varint,9,opt,name=beganProcessing,proto3" json:"beganProcessing,omitempty"` + Created *timestamppb.Timestamp `protobuf:"bytes,13,opt,name=created,proto3" json:"created,omitempty"` + V2Authorizations []int64 `protobuf:"varint,11,rep,packed,name=v2Authorizations,proto3" json:"v2Authorizations,omitempty"` + CertificateProfileName string `protobuf:"bytes,14,opt,name=certificateProfileName,proto3" json:"certificateProfileName,omitempty"` } func (x *Order) Reset() { @@ -815,6 +816,13 @@ func (x *Order) GetV2Authorizations() []int64 { return nil } +func (x *Order) GetCertificateProfileName() string { + if x != nil { + return x.CertificateProfileName + } + return "" +} + type CRLEntry struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -1008,7 +1016,7 @@ var file_core_proto_rawDesc = []byte{ 0x0b, 0x32, 0x0f, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x52, 0x0a, 0x63, 0x68, 0x61, 0x6c, 0x6c, 0x65, 0x6e, 0x67, 0x65, 0x73, 0x4a, 0x04, 0x08, 0x05, 0x10, 0x06, 0x4a, 0x04, 0x08, 0x07, 0x10, 0x08, 0x4a, 0x04, 0x08, 0x08, 0x10, 0x09, - 0x22, 0x9b, 0x03, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, + 0x22, 0xd3, 0x03, 0x0a, 0x05, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x02, 0x69, 0x64, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, @@ -1032,19 +1040,23 @@ var file_core_proto_rawDesc = []byte{ 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x07, 0x63, 0x72, 0x65, 0x61, 0x74, 0x65, 0x64, 0x12, 0x2a, 0x0a, 0x10, 0x76, 0x32, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x0b, 0x20, 0x03, 0x28, 0x03, 0x52, 0x10, 0x76, 0x32, 0x41, 0x75, - 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x4a, 0x04, 0x08, 0x03, - 0x10, 0x04, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, 0x4a, 0x04, 0x08, 0x0a, 0x10, 0x0b, 0x22, 0x7a, - 0x0a, 0x08, 0x43, 0x52, 0x4c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, - 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, - 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, - 0x28, 0x05, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x09, 0x72, 0x65, - 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x18, 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, - 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, - 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, 0x70, 0x52, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, - 0x65, 0x64, 0x41, 0x74, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, - 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, - 0x72, 0x79, 0x70, 0x74, 0x2f, 0x62, 0x6f, 0x75, 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x72, - 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x36, 0x0a, 0x16, + 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, + 0x6c, 0x65, 0x4e, 0x61, 0x6d, 0x65, 0x18, 0x0e, 0x20, 0x01, 0x28, 0x09, 0x52, 0x16, 0x63, 0x65, + 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x50, 0x72, 0x6f, 0x66, 0x69, 0x6c, 0x65, + 0x4e, 0x61, 0x6d, 0x65, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x4a, 0x04, 0x08, 0x06, 0x10, 0x07, + 0x4a, 0x04, 0x08, 0x0a, 0x10, 0x0b, 0x22, 0x7a, 0x0a, 0x08, 0x43, 0x52, 0x4c, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x16, 0x0a, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x18, 0x01, 0x20, 0x01, + 0x28, 0x09, 0x52, 0x06, 0x73, 0x65, 0x72, 0x69, 0x61, 0x6c, 0x12, 0x16, 0x0a, 0x06, 0x72, 0x65, + 0x61, 0x73, 0x6f, 0x6e, 0x18, 0x02, 0x20, 0x01, 0x28, 0x05, 0x52, 0x06, 0x72, 0x65, 0x61, 0x73, + 0x6f, 0x6e, 0x12, 0x38, 0x0a, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x18, + 0x04, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, + 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x54, 0x69, 0x6d, 0x65, 0x73, 0x74, 0x61, 0x6d, + 0x70, 0x52, 0x09, 0x72, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x64, 0x41, 0x74, 0x4a, 0x04, 0x08, 0x03, + 0x10, 0x04, 0x42, 0x2b, 0x5a, 0x29, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, + 0x2f, 0x6c, 0x65, 0x74, 0x73, 0x65, 0x6e, 0x63, 0x72, 0x79, 0x70, 0x74, 0x2f, 0x62, 0x6f, 0x75, + 0x6c, 0x64, 0x65, 0x72, 0x2f, 0x63, 0x6f, 0x72, 0x65, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/core/proto/core.proto b/core/proto/core.proto index e4f488a362e..38608f92019 100644 --- a/core/proto/core.proto +++ b/core/proto/core.proto @@ -101,7 +101,7 @@ message Authorization { } message Order { - // Next unused field number: 14 + // Next unused field number: 15 int64 id = 1; int64 registrationID = 2; reserved 3; // Previously expiresNS @@ -115,6 +115,7 @@ message Order { reserved 10; // Previously createdNS google.protobuf.Timestamp created = 13; repeated int64 v2Authorizations = 11; + string certificateProfileName = 14; } message CRLEntry { From 6add559912e67b96a6be8425c0410dfbf1ab421b Mon Sep 17 00:00:00 2001 From: Aaron Gable Date: Wed, 6 Mar 2024 17:16:36 -0800 Subject: [PATCH 2/4] Propagate profile hash from precert to final cert --- ra/ra.go | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/ra/ra.go b/ra/ra.go index b046e6c26ad..866c0fb2799 100644 --- a/ra/ra.go +++ b/ra/ra.go @@ -1317,10 +1317,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") From 68a22ff76e3379bba9909043f5665a343d91756c Mon Sep 17 00:00:00 2001 From: Aaron Gable Date: Wed, 6 Mar 2024 17:18:37 -0800 Subject: [PATCH 3/4] Propagate profile name from db order to precert --- ra/ra.go | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/ra/ra.go b/ra/ra.go index 866c0fb2799..3837aa92287 100644 --- a/ra/ra.go +++ b/ra/ra.go @@ -1219,7 +1219,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 @@ -1276,6 +1276,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 { @@ -1297,9 +1298,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 { From e40e984a2c346340e0104cd9da55179b3b684335 Mon Sep 17 00:00:00 2001 From: Aaron Gable Date: Wed, 6 Mar 2024 18:22:26 -0800 Subject: [PATCH 4/4] Add test --- mocks/ca.go | 12 +++++++---- ra/ra_test.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 63 insertions(+), 5 deletions(-) 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_test.go b/ra/ra_test.go index 101799256c9..6c5f6bfec4f 100644 --- a/ra/ra_test.go +++ b/ra/ra_test.go @@ -3559,7 +3559,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 @@ -3578,6 +3578,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()