RA: Propagate profile name and hash from SA to CA (#7367)
When the order object retrieved from the SA contains a profile name, propagate that into the request for the CA to issue a precertificate. Similarly, when the CA's precertificate issuance response contains a profile hash, propagate that into the request for the CA to issue the corresponding final certificate. Fixes https://github.com/letsencrypt/boulder/issues/7366
This commit is contained in:
parent
8d169a8dfb
commit
8ac88f557b
12
mocks/ca.go
12
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
|
||||
}
|
||||
|
||||
|
|
19
ra/ra.go
19
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")
|
||||
|
|
|
@ -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()
|
||||
|
|
Loading…
Reference in New Issue