diff --git a/cmd/admin-revoker/main_test.go b/cmd/admin-revoker/main_test.go index ffca14e618ee..3c6f5dafcd66 100644 --- a/cmd/admin-revoker/main_test.go +++ b/cmd/admin-revoker/main_test.go @@ -70,6 +70,7 @@ func TestRevokeSerialBatchFile(t *testing.T) { } err = testCtx.revoker.revokeSerialBatchFile(context.Background(), serialFile.Name(), 0, 2) test.AssertNotError(t, err, "revokeBatch failed") + test.AssertEquals(t, len(testCtx.log.GetAllMatching("failed to revoke")), 0) for _, e := range entries { status, err := testCtx.ssa.GetCertificateStatus(context.Background(), &sapb.Serial{Serial: core.SerialToString(e.serial)}) @@ -110,6 +111,7 @@ func TestRevokeIncidentTableSerials(t *testing.T) { err = testCtx.revoker.revokeIncidentTableSerials(ctx, "incident_foo", 0, 1) test.AssertNotError(t, err, "revokeIncidentTableSerials failed") + test.AssertEquals(t, len(testCtx.log.GetAllMatching("failed to revoke")), 0) // Ensure that a populated incident table results in the expected log output. test.AssertNotError(t, err, "revokeIncidentTableSerials failed") @@ -398,18 +400,31 @@ func (c testCtx) addRegistation(t *testing.T, names []string, jwk string) int64 func (c testCtx) addCertificate(t *testing.T, serial *big.Int, names []string, pubKey rsa.PublicKey, regId int64) *x509.Certificate { t.Helper() + now := time.Now() + template := &x509.Certificate{ SerialNumber: serial, Subject: pkix.Name{Organization: []string{"tests"}}, - NotBefore: time.Now(), - NotAfter: time.Now().AddDate(0, 0, 1), + NotBefore: now, + NotAfter: now.AddDate(0, 0, 1), DNSNames: names, } rawCert, err := x509.CreateCertificate(rand.Reader, template, c.issuer.Certificate, &pubKey, c.signer) test.AssertNotError(t, err, "Failed to generate test cert") - now := time.Now() + _, err = c.ssa.AddSerial( + context.Background(), &sapb.AddSerialRequest{ + RegID: regId, + Serial: core.SerialToString(serial), + CreatedNS: now.UnixNano(), + Created: timestamppb.New(now), + ExpiresNS: now.AddDate(0, 0, 1).UnixNano(), + Expires: timestamppb.New(now.AddDate(0, 0, 1)), + }, + ) + test.AssertNotError(t, err, "Failed to add test serial") + _, err = c.ssa.AddPrecertificate( context.Background(), &sapb.AddCertificateRequest{ Der: rawCert, @@ -488,6 +503,9 @@ func setup(t *testing.T) testCtx { nil, &mockPurger{}, []*issuance.Certificate{issuer}, + "http://c.boulder.test", + 10, + 24*time.Hour, ) ra.SA = isa.SA{Impl: ssa} ra.OCSP = &mockOCSPA{} diff --git a/cmd/boulder-ra/main.go b/cmd/boulder-ra/main.go index df87adb82380..3d03281c3a8c 100644 --- a/cmd/boulder-ra/main.go +++ b/cmd/boulder-ra/main.go @@ -90,6 +90,25 @@ type Config struct { // generate OCSP URLs to purge during revocation. IssuerCerts []string `validate:"min=1,dive,required"` + // CRLDPBase is the piece of the CRL Distribution Point URI which is common + // across all issuers and shards. It must use the http:// scheme, and must + // not end with a slash. Example: "http://prod.c.lencr.org". + // Warning: This value must exactly match the CA config. + // TODO(#7904): Make this mandatory once the configs are in place. + CRLDPBase string `validate:"omitempty,url,startswith=http://,endsnotwith=/"` + + // CRLNumShards is the number of shards into which each issuer's "full and + // complete" CRL is split. + // Warning: This value must exactly match the crl-updater config. + // TODO(#7904): Make this mandatory once the configs are in place. + CRLNumShards int `validate:"omitempty,min=1"` + + // CRLShardWidth is the amount of time (width on a timeline) that a single + // shard covers. + // Warning: This value must exactly match the crl-updater config. + // TODO(#7904): Make this mandatory once the configs are in place. + CRLShardWidth config.Duration `validate:"-"` + Features map[string]bool } @@ -244,6 +263,9 @@ func main() { ctp, apc, issuerCerts, + c.RA.CRLDPBase, + c.RA.CRLNumShards, + c.RA.CRLShardWidth.Duration, ) defer rai.DrainFinalize() diff --git a/ra/proto/ra.pb.go b/ra/proto/ra.pb.go index cbe1c487c705..2d9bdb8db56a 100644 --- a/ra/proto/ra.pb.go +++ b/ra/proto/ra.pb.go @@ -358,16 +358,18 @@ type AdministrativelyRevokeCertificateRequest struct { sizeCache protoimpl.SizeCache unknownFields protoimpl.UnknownFields - // The `cert` field may be omitted. If it is omitted, - // the revocation reason (`code`) must not be keyCompromise, - // and purging the Akamai cache will not happen because the - // base URL for the certificate's OCSP server is not known. + // The `cert` field may be omitted. If it is omitted, the revocation reason + // (`code`) must not be keyCompromise, the crlShard field must be populated, + // and purging the Akamai cache will not happen because the base URL for the + // certificate's OCSP server is not known. Cert []byte `protobuf:"bytes,1,opt,name=cert,proto3" json:"cert,omitempty"` // The `serial` field is required. Serial string `protobuf:"bytes,4,opt,name=serial,proto3" json:"serial,omitempty"` Code int64 `protobuf:"varint,2,opt,name=code,proto3" json:"code,omitempty"` AdminName string `protobuf:"bytes,3,opt,name=adminName,proto3" json:"adminName,omitempty"` SkipBlockKey bool `protobuf:"varint,5,opt,name=skipBlockKey,proto3" json:"skipBlockKey,omitempty"` + // The `crlShard` field is required if the `cert` field is omitted. + CrlShard int64 `protobuf:"varint,6,opt,name=crlShard,proto3" json:"crlShard,omitempty"` } func (x *AdministrativelyRevokeCertificateRequest) Reset() { @@ -437,6 +439,13 @@ func (x *AdministrativelyRevokeCertificateRequest) GetSkipBlockKey() bool { return false } +func (x *AdministrativelyRevokeCertificateRequest) GetCrlShard() int64 { + if x != nil { + return x.CrlShard + } + return 0 +} + type NewOrderRequest struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -591,7 +600,7 @@ var file_ra_proto_rawDesc = []byte{ 0x44, 0x22, 0x32, 0x0a, 0x16, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x04, 0x63, 0x65, 0x72, 0x74, 0x4a, - 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xac, 0x01, 0x0a, 0x28, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, + 0x04, 0x08, 0x02, 0x10, 0x03, 0x22, 0xc8, 0x01, 0x0a, 0x28, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x12, 0x0a, 0x04, 0x63, 0x65, 0x72, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, @@ -602,71 +611,73 @@ var file_ra_proto_rawDesc = []byte{ 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x09, 0x61, 0x64, 0x6d, 0x69, 0x6e, 0x4e, 0x61, 0x6d, 0x65, 0x12, 0x22, 0x0a, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x6c, 0x6f, 0x63, 0x6b, 0x4b, 0x65, 0x79, 0x18, 0x05, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0c, 0x73, 0x6b, 0x69, 0x70, 0x42, 0x6c, 0x6f, 0x63, - 0x6b, 0x4b, 0x65, 0x79, 0x22, 0x4f, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, - 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, - 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, - 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x4b, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, - 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, - 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, - 0x12, 0x10, 0x0a, 0x03, 0x63, 0x73, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x63, - 0x73, 0x72, 0x32, 0xad, 0x06, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0f, - 0x4e, 0x65, 0x77, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, + 0x6b, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x0a, 0x08, 0x63, 0x72, 0x6c, 0x53, 0x68, 0x61, 0x72, 0x64, + 0x18, 0x06, 0x20, 0x01, 0x28, 0x03, 0x52, 0x08, 0x63, 0x72, 0x6c, 0x53, 0x68, 0x61, 0x72, 0x64, + 0x22, 0x4f, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x26, 0x0a, 0x0e, 0x72, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0e, 0x72, 0x65, 0x67, + 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x49, 0x44, 0x12, 0x14, 0x0a, 0x05, 0x6e, + 0x61, 0x6d, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x09, 0x52, 0x05, 0x6e, 0x61, 0x6d, 0x65, + 0x73, 0x22, 0x4b, 0x0a, 0x14, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, + 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x21, 0x0a, 0x05, 0x6f, 0x72, 0x64, + 0x65, 0x72, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x05, 0x6f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x10, 0x0a, 0x03, + 0x63, 0x73, 0x72, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x03, 0x63, 0x73, 0x72, 0x32, 0xad, + 0x06, 0x0a, 0x15, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x41, + 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x3b, 0x0a, 0x0f, 0x4e, 0x65, 0x77, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x63, 0x6f, + 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, - 0x69, 0x6f, 0x6e, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x12, 0x55, 0x70, 0x64, - 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, - 0x1d, 0x2e, 0x72, 0x61, 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, - 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x11, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, - 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x72, 0x61, 0x2e, 0x50, - 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, - 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x46, - 0x0a, 0x16, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, - 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, - 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x16, 0x2e, 0x67, - 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, - 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x48, 0x0a, 0x17, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, - 0x76, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x12, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, - 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, - 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, - 0x12, 0x53, 0x0a, 0x15, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, - 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, 0x12, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x52, - 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, - 0x63, 0x61, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, - 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, - 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x47, 0x0a, 0x0f, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, - 0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, - 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, - 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, - 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x6b, - 0x0a, 0x21, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, - 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, - 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x72, 0x61, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, - 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, - 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, - 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, - 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x08, 0x4e, - 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x13, 0x2e, 0x72, 0x61, 0x2e, 0x4e, 0x65, 0x77, - 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, - 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0d, 0x46, - 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x72, - 0x61, 0x2e, 0x46, 0x69, 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, - 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, - 0x64, 0x65, 0x72, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x0c, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, - 0x65, 0x4f, 0x43, 0x53, 0x50, 0x12, 0x17, 0x2e, 0x72, 0x61, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, - 0x61, 0x74, 0x65, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, - 0x2e, 0x63, 0x61, 0x2e, 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, - 0x22, 0x00, 0x42, 0x29, 0x5a, 0x27, 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, 0x72, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, - 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x49, 0x0a, 0x12, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, + 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1d, 0x2e, 0x72, 0x61, + 0x2e, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x12, 0x2e, 0x63, 0x6f, 0x72, + 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, + 0x12, 0x48, 0x0a, 0x11, 0x50, 0x65, 0x72, 0x66, 0x6f, 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x1c, 0x2e, 0x72, 0x61, 0x2e, 0x50, 0x65, 0x72, 0x66, 0x6f, + 0x72, 0x6d, 0x56, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x13, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, + 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x00, 0x12, 0x46, 0x0a, 0x16, 0x44, 0x65, + 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, 0x52, 0x65, 0x67, 0x69, 0x73, 0x74, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x12, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x52, 0x65, 0x67, 0x69, + 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, + 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, + 0x22, 0x00, 0x12, 0x48, 0x0a, 0x17, 0x44, 0x65, 0x61, 0x63, 0x74, 0x69, 0x76, 0x61, 0x74, 0x65, + 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x12, 0x13, 0x2e, + 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x7a, 0x61, 0x74, 0x69, + 0x6f, 0x6e, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, + 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x53, 0x0a, 0x15, + 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, + 0x69, 0x63, 0x61, 0x6e, 0x74, 0x12, 0x20, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, + 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x41, 0x70, 0x70, 0x6c, 0x69, 0x63, 0x61, 0x6e, 0x74, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, + 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, + 0x00, 0x12, 0x47, 0x0a, 0x0f, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x42, + 0x79, 0x4b, 0x65, 0x79, 0x12, 0x1a, 0x2e, 0x72, 0x61, 0x2e, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, + 0x43, 0x65, 0x72, 0x74, 0x42, 0x79, 0x4b, 0x65, 0x79, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x16, 0x2e, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, + 0x75, 0x66, 0x2e, 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x6b, 0x0a, 0x21, 0x41, 0x64, + 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, + 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, + 0x2c, 0x2e, 0x72, 0x61, 0x2e, 0x41, 0x64, 0x6d, 0x69, 0x6e, 0x69, 0x73, 0x74, 0x72, 0x61, 0x74, + 0x69, 0x76, 0x65, 0x6c, 0x79, 0x52, 0x65, 0x76, 0x6f, 0x6b, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, + 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, + 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x75, 0x66, 0x2e, + 0x45, 0x6d, 0x70, 0x74, 0x79, 0x22, 0x00, 0x12, 0x2e, 0x0a, 0x08, 0x4e, 0x65, 0x77, 0x4f, 0x72, + 0x64, 0x65, 0x72, 0x12, 0x13, 0x2e, 0x72, 0x61, 0x2e, 0x4e, 0x65, 0x77, 0x4f, 0x72, 0x64, 0x65, + 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, + 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, 0x00, 0x12, 0x38, 0x0a, 0x0d, 0x46, 0x69, 0x6e, 0x61, 0x6c, + 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x12, 0x18, 0x2e, 0x72, 0x61, 0x2e, 0x46, 0x69, + 0x6e, 0x61, 0x6c, 0x69, 0x7a, 0x65, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x52, 0x65, 0x71, 0x75, 0x65, + 0x73, 0x74, 0x1a, 0x0b, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x4f, 0x72, 0x64, 0x65, 0x72, 0x22, + 0x00, 0x12, 0x3b, 0x0a, 0x0c, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x4f, 0x43, 0x53, + 0x50, 0x12, 0x17, 0x2e, 0x72, 0x61, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x4f, + 0x43, 0x53, 0x50, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x10, 0x2e, 0x63, 0x61, 0x2e, + 0x4f, 0x43, 0x53, 0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x42, 0x29, + 0x5a, 0x27, 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, 0x72, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, + 0x33, } var ( diff --git a/ra/proto/ra.proto b/ra/proto/ra.proto index cd2db5e26baa..665056b2aed1 100644 --- a/ra/proto/ra.proto +++ b/ra/proto/ra.proto @@ -54,16 +54,18 @@ message RevokeCertByKeyRequest { } message AdministrativelyRevokeCertificateRequest { - // The `cert` field may be omitted. If it is omitted, - // the revocation reason (`code`) must not be keyCompromise, - // and purging the Akamai cache will not happen because the - // base URL for the certificate's OCSP server is not known. + // The `cert` field may be omitted. If it is omitted, the revocation reason + // (`code`) must not be keyCompromise, the crlShard field must be populated, + // and purging the Akamai cache will not happen because the base URL for the + // certificate's OCSP server is not known. bytes cert = 1; // The `serial` field is required. string serial = 4; int64 code = 2; string adminName = 3; bool skipBlockKey = 5; + // The `crlShard` field is required if the `cert` field is omitted. + int64 crlShard = 6; } message NewOrderRequest { diff --git a/ra/ra.go b/ra/ra.go index ab19f842de55..7d85369f7789 100644 --- a/ra/ra.go +++ b/ra/ra.go @@ -34,6 +34,7 @@ import ( capb "github.com/letsencrypt/boulder/ca/proto" "github.com/letsencrypt/boulder/core" corepb "github.com/letsencrypt/boulder/core/proto" + crl "github.com/letsencrypt/boulder/crl/updater" csrlib "github.com/letsencrypt/boulder/csr" "github.com/letsencrypt/boulder/ctpolicy" berrors "github.com/letsencrypt/boulder/errors" @@ -108,6 +109,10 @@ type RegistrationAuthorityImpl struct { ctpolicy *ctpolicy.CTPolicy + crlDPBase string + crlNumShards int + crlShardWidth time.Duration + ctpolicyResults *prometheus.HistogramVec revocationReasonCounter *prometheus.CounterVec namesPerCert *prometheus.HistogramVec @@ -139,6 +144,9 @@ func NewRegistrationAuthorityImpl( ctp *ctpolicy.CTPolicy, purger akamaipb.AkamaiPurgerClient, issuers []*issuance.Certificate, + crlDPBase string, + crlNumShards int, + crlShardWidth time.Duration, ) *RegistrationAuthorityImpl { ctpolicyResults := prometheus.NewHistogramVec( prometheus.HistogramOpts{ @@ -260,6 +268,9 @@ func NewRegistrationAuthorityImpl( purger: purger, issuersByNameID: issuersByNameID, issuersByID: issuersByID, + crlDPBase: crlDPBase, + crlNumShards: crlNumShards, + crlShardWidth: crlShardWidth, namesPerCert: namesPerCert, rlCheckLatency: rlCheckLatency, rlOverrideUsageGauge: overrideUsageGauge, @@ -335,6 +346,11 @@ type certificateRequestEvent struct { // as JSON to the audit log as the result of a revocation event. type certificateRevocationEvent struct { ID string `json:",omitempty"` + // IssuerID is the unique IssuerNameID of the issuer of the revoked certificate. + IssuerID int64 `json:",omitempty"` + // CrlShard is the number of the CRL shard that the revoked certificate will + // appear in. + CrlShard int64 `json:",omitempty"` // SerialNumber is the string representation of the revoked certificate's // serial number. SerialNumber string `json:",omitempty"` @@ -344,10 +360,10 @@ type certificateRevocationEvent struct { // It will be one of the strings: "applicant", "subscriber", "control", "key", or "admin". Method string `json:",omitempty"` // RequesterID is the account ID of the requester. - // Will be zero for admin revocations. + // Will be zero for key and admin revocations. RequesterID int64 `json:",omitempty"` // AdminName is the name of the admin requester. - // Will be zero for subscriber revocations. + // Will be zero for key and subscriber revocations. AdminName string `json:",omitempty"` // Error contains any error encountered during revocation. Error string `json:",omitempty"` @@ -1914,16 +1930,17 @@ func (ra *RegistrationAuthorityImpl) PerformValidation( // revokeCertificate updates the database to mark the certificate as revoked, // with the given reason and current timestamp. // TODO(#5152) make the issuerID argument an issuance.IssuerNameID -func (ra *RegistrationAuthorityImpl) revokeCertificate(ctx context.Context, serial *big.Int, issuerID int64, reason revocation.Reason) error { +func (ra *RegistrationAuthorityImpl) revokeCertificate(ctx context.Context, serial *big.Int, issuerID int64, crlShard int64, reason revocation.Reason) error { serialString := core.SerialToString(serial) now := ra.clk.Now() _, err := ra.SA.RevokeCertificate(ctx, &sapb.RevokeCertificateRequest{ + IssuerID: issuerID, + ShardIdx: crlShard, Serial: serialString, Reason: int64(reason), DateNS: now.UnixNano(), Date: timestamppb.New(now), - IssuerID: issuerID, }) if err != nil { return err @@ -1938,7 +1955,7 @@ func (ra *RegistrationAuthorityImpl) revokeCertificate(ctx context.Context, seri // certificates that were previously revoked for a reason other than // keyCompromise, and which are now being updated to keyCompromise instead. // TODO(#5152) make the issuerID argument an issuance.IssuerNameID -func (ra *RegistrationAuthorityImpl) updateRevocationForKeyCompromise(ctx context.Context, serial *big.Int, issuerID int64) error { +func (ra *RegistrationAuthorityImpl) updateRevocationForKeyCompromise(ctx context.Context, serial *big.Int, issuerID int64, crlShard int64) error { serialString := core.SerialToString(serial) now := ra.clk.Now() @@ -1957,13 +1974,14 @@ func (ra *RegistrationAuthorityImpl) updateRevocationForKeyCompromise(ctx contex } _, err = ra.SA.UpdateRevokedCertificate(ctx, &sapb.RevokeCertificateRequest{ + IssuerID: issuerID, + ShardIdx: crlShard, Serial: serialString, Reason: int64(ocsp.KeyCompromise), DateNS: now.UnixNano(), Date: timestamppb.New(now), BackdateNS: status.RevokedDateNS, Backdate: timestamppb.New(time.Unix(0, status.RevokedDateNS)), - IssuerID: issuerID, }) if err != nil { return err @@ -1973,6 +1991,45 @@ func (ra *RegistrationAuthorityImpl) updateRevocationForKeyCompromise(ctx contex return nil } +// crlShardForCert determines the CRL shard to use for a given certificate, +// preferably by trying to parse the shard index from the cert's +// crlDistributionPoint, but falling back to computing the shard number directly +// using the same math as the crl-updater if no distribution point is present. +func (ra *RegistrationAuthorityImpl) crlShardForCert(cert *x509.Certificate) (int64, error) { + // Return 0, which will then be elided from the gRPC request to the SA, if + // we don't have all the necessary config parameters yet. + // TODO(#7904): Remove this carve-out once the configs are in place. + if ra.crlNumShards == 0 || ra.crlShardWidth == 0 || ra.crlDPBase == "" { + return 0, nil + } + + if len(cert.CRLDistributionPoints) == 0 { + chunk, err := crl.GetChunkAtTime(ra.crlShardWidth, ra.crlNumShards, cert.NotAfter) + if err != nil { + return 0, fmt.Errorf("computing crl shard from notAfter: %w", err) + } + + return int64(chunk.Idx), nil + } + + if len(cert.CRLDistributionPoints) > 1 { + return 0, fmt.Errorf("cert has multiple crl distribution points (%v)", cert.CRLDistributionPoints) + } + + crldp := cert.CRLDistributionPoints[0] + expectedPrefix := fmt.Sprintf("%s/%d/", ra.crlDPBase, issuance.GetIssuerNameID(cert)) + if !strings.HasPrefix(cert.CRLDistributionPoints[0], expectedPrefix) { + return 0, fmt.Errorf("crl distribution point has unrecognized prefix %q", crldp) + } + + shardStr := strings.TrimPrefix(cert.CRLDistributionPoints[0], expectedPrefix) + shard, err := strconv.Atoi(shardStr) + if err != nil { + return 0, fmt.Errorf("extracting crl shard from distributionPoint URL: %w", err) + } + return int64(shard), nil +} + // purgeOCSPCache makes a request to akamai-purger to purge the cache entries // for the given certificate. // TODO(#5152) make the issuerID argument an issuance.IssuerNameID @@ -2026,10 +2083,17 @@ func (ra *RegistrationAuthorityImpl) RevokeCertByApplicant(ctx context.Context, return nil, err } + issuerID := issuance.GetIssuerNameID(cert) serialString := core.SerialToString(cert.SerialNumber) + crlShard, err := ra.crlShardForCert(cert) + if err != nil { + return nil, fmt.Errorf("computing CRL shard for cert: %w", err) + } logEvent := certificateRevocationEvent{ ID: core.NewToken(), + IssuerID: int64(issuerID), + CrlShard: crlShard, SerialNumber: serialString, Reason: req.Code, Method: "applicant", @@ -2089,11 +2153,11 @@ func (ra *RegistrationAuthorityImpl) RevokeCertByApplicant(ctx context.Context, logEvent.Reason = req.Code } - issuerID := issuance.GetIssuerNameID(cert) err = ra.revokeCertificate( ctx, cert.SerialNumber, int64(issuerID), + crlShard, revocation.Reason(req.Code), ) if err != nil { @@ -2148,9 +2212,15 @@ func (ra *RegistrationAuthorityImpl) RevokeCertByKey(ctx context.Context, req *r } issuerID := issuance.GetIssuerNameID(cert) + crlShard, err := ra.crlShardForCert(cert) + if err != nil { + return nil, fmt.Errorf("computing CRL shard for cert: %w", err) + } logEvent := certificateRevocationEvent{ ID: core.NewToken(), + IssuerID: int64(issuerID), + CrlShard: crlShard, SerialNumber: core.SerialToString(cert.SerialNumber), Reason: ocsp.KeyCompromise, Method: "key", @@ -2175,6 +2245,7 @@ func (ra *RegistrationAuthorityImpl) RevokeCertByKey(ctx context.Context, req *r ctx, cert.SerialNumber, int64(issuerID), + crlShard, revocation.Reason(ocsp.KeyCompromise), ) @@ -2197,7 +2268,7 @@ func (ra *RegistrationAuthorityImpl) RevokeCertByKey(ctx context.Context, req *r } else if errors.Is(err, berrors.AlreadyRevoked) { // If it was an AlreadyRevoked error, try to re-revoke the cert in case // it was revoked for a reason other than keyCompromise. - err = ra.updateRevocationForKeyCompromise(ctx, cert.SerialNumber, int64(issuerID)) + err = ra.updateRevocationForKeyCompromise(ctx, cert.SerialNumber, int64(issuerID), crlShard) // Perform an Akamai cache purge to handle occurrences of a client // previously successfully revoking a certificate, but the cache purge had @@ -2248,6 +2319,7 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte // so badly malformed that it can't be parsed. var cert *x509.Certificate var issuerID int64 // TODO(#5152) make this an issuance.IssuerNameID + var crlShard int64 var err error if req.Cert == nil { status, err := ra.SA.GetCertificateStatus(ctx, &sapb.Serial{Serial: req.Serial}) @@ -2255,19 +2327,29 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte return nil, fmt.Errorf("unable to confirm that serial %q was ever issued: %w", req.Serial, err) } issuerID = status.IssuerID + if req.CrlShard <= 0 { + return nil, fmt.Errorf("no CRL shard given for serial %q", req.Serial) + } + crlShard = req.CrlShard } else { cert, err = x509.ParseCertificate(req.Cert) if err != nil { return nil, err } issuerID = int64(issuance.GetIssuerNameID(cert)) + crlShard, err = ra.crlShardForCert(cert) + if err != nil { + return nil, fmt.Errorf("computing CRL shard for cert: %w", err) + } } logEvent := certificateRevocationEvent{ ID: core.NewToken(), - Method: "key", - AdminName: req.AdminName, + IssuerID: issuerID, + CrlShard: crlShard, SerialNumber: req.Serial, + Method: "admin", + AdminName: req.AdminName, } // Below this point, do not re-declare `err` (i.e. type `err :=`) in a @@ -2286,7 +2368,7 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte return nil, err } - err = ra.revokeCertificate(ctx, serialInt, issuerID, revocation.Reason(req.Code)) + err = ra.revokeCertificate(ctx, serialInt, issuerID, crlShard, revocation.Reason(req.Code)) // Perform an Akamai cache purge to handle occurrences of a client // successfully revoking a certificate, but the initial cache purge failing. if errors.Is(err, berrors.AlreadyRevoked) { @@ -2300,7 +2382,7 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte } if err != nil { if req.Code == ocsp.KeyCompromise && errors.Is(err, berrors.AlreadyRevoked) { - err = ra.updateRevocationForKeyCompromise(ctx, serialInt, issuerID) + err = ra.updateRevocationForKeyCompromise(ctx, serialInt, issuerID, crlShard) if err != nil { return nil, err } diff --git a/ra/ra_test.go b/ra/ra_test.go index d95dd3322507..b669d3e02cfb 100644 --- a/ra/ra_test.go +++ b/ra/ra_test.go @@ -371,7 +371,9 @@ func initAuthorities(t *testing.T) (*DummyValidationAuthority, sapb.StorageAutho 300*24*time.Hour, 7*24*time.Hour, nil, noopCAA{}, 0, 5*time.Minute, - ctp, nil, nil) + ctp, nil, nil, + "http://c.boulder.test", 10, 24*time.Hour, + ) ra.SA = sa ra.VA = va ra.CA = ca @@ -4029,6 +4031,15 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) { }) test.AssertError(t, err, "AdministrativelyRevokeCertificate should have failed for nil `Cert`") + // Revoking with neither a cert nor a crl shard index should fail immediately. + _, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{ + Serial: serial, + Code: ocsp.Unspecified, + AdminName: "root", + }) + test.AssertError(t, err, "AdministrativelyRevokeCertificate should have failed with no cert or serial") + test.AssertContains(t, err.Error(), "no CRL shard given") + // Revoking without an admin name should fail immediately. _, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{ Cert: cert.Raw, @@ -4063,6 +4074,7 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) { mockSA.revoked = make(map[string]int64) _, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{ Serial: core.SerialToString(cert.SerialNumber), + CrlShard: 1, Code: ocsp.Unspecified, AdminName: "root", }) @@ -4075,6 +4087,7 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) { // should fail and not block the key _, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{ Serial: core.SerialToString(cert.SerialNumber), + CrlShard: 1, Code: ocsp.Unspecified, AdminName: "root", }) @@ -4106,8 +4119,58 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) { mockSA.revoked = make(map[string]int64) _, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{ Serial: core.SerialToString(cert.SerialNumber), + CrlShard: 1, Code: ocsp.KeyCompromise, AdminName: "root", }) test.AssertError(t, err, "AdministrativelyRevokeCertificate should have failed with just serial for keyCompromise") } + +func TestCRLShardForCert(t *testing.T) { + _, _, ra, clk, cleanUp := initAuthorities(t) + defer cleanUp() + + _, cert := test.ThrowAwayCert(t, clk) + + // A cert with multiple distribution points should cause an error: we don't + // know which one is the right one. + // Note that modifying the cert in this way means that the go struct no longer + // matches its own .Raw field, and the signature doesn't match, but that's ok + // for the purpose of this test. + cert.CRLDistributionPoints = []string{"first", "second"} + _, err := ra.crlShardForCert(cert) + test.AssertError(t, err, "multiple crldps should fail") + test.AssertContains(t, err.Error(), "multiple crl distribution points") + + // A cert with an unexpected distribution point should cause an error because + // the cert *must* show up in the CRL in its own embedded CRLDP. + cert.CRLDistributionPoints = []string{"https://wrong.url/crls/123.pem"} + _, err = ra.crlShardForCert(cert) + test.AssertError(t, err, "bad crldp prefix should fail") + test.AssertContains(t, err.Error(), "unrecognized prefix") + + // A cert with an unparsable distribution point should cause an error for the + // same reasons as the above. + // Note that the IssuerNameID portion of the URL used in this case and the + // one below is empirically determined. + cert.CRLDistributionPoints = []string{"http://c.boulder.test/70162984478308736/asdf"} + _, err = ra.crlShardForCert(cert) + test.AssertError(t, err, "unparsable shard number should fail") + test.AssertContains(t, err.Error(), "extracting crl shard") + + // A cert with the expected distribution point should succeed. + cert.CRLDistributionPoints = []string{"http://c.boulder.test/70162984478308736/987"} + shard, err := ra.crlShardForCert(cert) + test.AssertNotError(t, err, "proper crldp should succeed") + test.AssertEquals(t, shard, int64(987)) + + // A cert with no distribution point should succeed, and give a shard index + // based on its expiry. + cert.CRLDistributionPoints = []string{} + shard, err = ra.crlShardForCert(cert) + test.AssertNotError(t, err, "proper crldp should succeed") + cert.NotAfter = cert.NotAfter.Add(2 * ra.crlShardWidth) + shard2, err := ra.crlShardForCert(cert) + test.AssertNotError(t, err, "proper crldp should succeed") + test.AssertEquals(t, shard2, (shard+2)%int64(ra.crlNumShards)) +} diff --git a/test/config-next/ra.json b/test/config-next/ra.json index 99c9aad25942..be7210c8ad19 100644 --- a/test/config-next/ra.json +++ b/test/config-next/ra.json @@ -19,6 +19,9 @@ "/hierarchy/intermediate-cert-rsa-b.pem", "/hierarchy/intermediate-cert-ecdsa-a.pem" ], + "crlDPBase": "http://c.boulder.test/crl", + "crlNumShards": 10, + "crlShardWidth": "240h", "tls": { "caCertFile": "test/grpc-creds/minica.pem", "certFile": "test/grpc-creds/ra.boulder/cert.pem", diff --git a/test/inmem/sa/sa.go b/test/inmem/sa/sa.go index 878c1a40eb55..e07816cad5d3 100644 --- a/test/inmem/sa/sa.go +++ b/test/inmem/sa/sa.go @@ -101,6 +101,10 @@ func (sa SA) FinalizeOrder(ctx context.Context, req *sapb.FinalizeOrderRequest, return sa.Impl.FinalizeOrder(ctx, req) } +func (sa SA) AddSerial(ctx context.Context, req *sapb.AddSerialRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) { + return sa.Impl.AddSerial(ctx, req) +} + func (sa SA) AddPrecertificate(ctx context.Context, req *sapb.AddCertificateRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) { return sa.Impl.AddPrecertificate(ctx, req) }