CA: Add GenerateCRL gRPC method (#6187)

Add a new CA gRPC method named `GenerateCRL`. In the
style of the existing `GenerateOCSP` method, this new endpoint
is implemented as a separate service, for which the CA binary
spins up an additional gRPC service.

This method uses gRPC streaming for both its input and output.
For input, the stream must contain exactly one metadata message
identifying the crl number, issuer, and timestamp, and then any
number of messages identifying a single certificate which should
be included in the CRL. For output, it simply streams chunks of
bytes.

Fixes #6161
This commit is contained in:
Aaron Gable 2022-06-29 11:03:12 -07:00 committed by GitHub
parent c65329202e
commit e13918b50e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 1172 additions and 61 deletions

View File

@ -55,6 +55,7 @@ type certificateAuthorityImpl struct {
sa sapb.StorageAuthorityCertificateClient
pa core.PolicyAuthority
ocsp *ocspImpl
crl *crlImpl
issuers issuerMaps
// This is temporary, and will be used for testing and slow roll-out
@ -101,6 +102,7 @@ func NewCertificateAuthorityImpl(
sa sapb.StorageAuthorityCertificateClient,
pa core.PolicyAuthority,
ocsp *ocspImpl,
crl *crlImpl,
boulderIssuers []*issuance.Issuer,
ecdsaAllowList *ECDSAAllowList,
certExpiry time.Duration,
@ -154,6 +156,7 @@ func NewCertificateAuthorityImpl(
sa: sa,
pa: pa,
ocsp: ocsp,
crl: crl,
issuers: issuers,
validityPeriod: certExpiry,
backdate: certBackdate,
@ -582,3 +585,10 @@ func (ca *certificateAuthorityImpl) integrateOrphan() error {
func (ca *certificateAuthorityImpl) GenerateOCSP(ctx context.Context, req *capb.GenerateOCSPRequest) (*capb.OCSPResponse, error) {
return ca.ocsp.GenerateOCSP(ctx, req)
}
// GenerateCRL is simply a passthrough to crlImpl.GenerateCRL so that other
// services which need to talk to the CA anyway can do so without configuring
// two separate gRPC service backends.
func (ca *certificateAuthorityImpl) GenerateCRL(stream capb.CertificateAuthority_GenerateCRLServer) error {
return ca.crl.GenerateCRL(stream)
}

View File

@ -117,6 +117,7 @@ func mustRead(path string) []byte {
type testCtx struct {
pa core.PolicyAuthority
ocsp *ocspImpl
crl *crlImpl
certExpiry time.Duration
certBackdate time.Duration
serialPrefix int
@ -259,9 +260,18 @@ func setup(t *testing.T) *testCtx {
)
test.AssertNotError(t, err, "Failed to create ocsp impl")
crl, err := NewCRLImpl(
boulderIssuers,
time.Hour,
100,
blog.NewMock(),
)
test.AssertNotError(t, err, "Failed to create crl impl")
return &testCtx{
pa: pa,
ocsp: ocsp,
crl: crl,
certExpiry: 8760 * time.Hour,
certBackdate: time.Hour,
serialPrefix: 17,
@ -285,6 +295,7 @@ func TestFailNoSerialPrefix(t *testing.T) {
nil,
nil,
nil,
nil,
testCtx.certExpiry,
testCtx.certBackdate,
0,
@ -382,6 +393,7 @@ func issueCertificateSubTestSetup(t *testing.T) (*certificateAuthorityImpl, *moc
sa,
testCtx.pa,
testCtx.ocsp,
testCtx.crl,
testCtx.boulderIssuers,
&ECDSAAllowList{},
testCtx.certExpiry,
@ -429,6 +441,7 @@ func TestMultipleIssuers(t *testing.T) {
sa,
testCtx.pa,
testCtx.ocsp,
testCtx.crl,
testCtx.boulderIssuers,
nil,
testCtx.certExpiry,
@ -565,6 +578,7 @@ func TestInvalidCSRs(t *testing.T) {
sa,
testCtx.pa,
testCtx.ocsp,
testCtx.crl,
testCtx.boulderIssuers,
nil,
testCtx.certExpiry,
@ -603,6 +617,7 @@ func TestRejectValidityTooLong(t *testing.T) {
sa,
testCtx.pa,
testCtx.ocsp,
testCtx.crl,
testCtx.boulderIssuers,
nil,
testCtx.certExpiry,
@ -705,6 +720,7 @@ func TestIssueCertificateForPrecertificate(t *testing.T) {
sa,
testCtx.pa,
testCtx.ocsp,
testCtx.crl,
testCtx.boulderIssuers,
nil,
testCtx.certExpiry,
@ -812,6 +828,7 @@ func TestIssueCertificateForPrecertificateDuplicateSerial(t *testing.T) {
sa,
testCtx.pa,
testCtx.ocsp,
testCtx.crl,
testCtx.boulderIssuers,
nil,
testCtx.certExpiry,
@ -855,6 +872,7 @@ func TestIssueCertificateForPrecertificateDuplicateSerial(t *testing.T) {
errorsa,
testCtx.pa,
testCtx.ocsp,
testCtx.crl,
testCtx.boulderIssuers,
nil,
testCtx.certExpiry,
@ -932,6 +950,7 @@ func TestPrecertOrphanQueue(t *testing.T) {
qsa,
testCtx.pa,
testCtx.ocsp,
testCtx.crl,
testCtx.boulderIssuers,
nil,
testCtx.certExpiry,
@ -999,6 +1018,7 @@ func TestOrphanQueue(t *testing.T) {
qsa,
testCtx.pa,
testCtx.ocsp,
testCtx.crl,
testCtx.boulderIssuers,
nil,
testCtx.certExpiry,

221
ca/crl.go Normal file
View File

@ -0,0 +1,221 @@
package ca
import (
"crypto/rand"
"crypto/sha256"
"crypto/x509"
"crypto/x509/pkix"
"encoding/asn1"
"errors"
"fmt"
"io"
"math/big"
"strings"
"time"
capb "github.com/letsencrypt/boulder/ca/proto"
"github.com/letsencrypt/boulder/core"
corepb "github.com/letsencrypt/boulder/core/proto"
"github.com/letsencrypt/boulder/issuance"
blog "github.com/letsencrypt/boulder/log"
)
type crlImpl struct {
capb.UnimplementedCRLGeneratorServer
issuers map[issuance.IssuerNameID]*issuance.Issuer
lifetime time.Duration
maxLogLen int
log blog.Logger
}
func NewCRLImpl(issuers []*issuance.Issuer, lifetime time.Duration, maxLogLen int, logger blog.Logger) (*crlImpl, error) {
issuersByNameID := make(map[issuance.IssuerNameID]*issuance.Issuer, len(issuers))
for _, issuer := range issuers {
issuersByNameID[issuer.Cert.NameID()] = issuer
}
if lifetime == 0 {
logger.Warningf("got zero for crl lifetime; setting to default 9 days")
lifetime = 9 * 24 * time.Hour
} else if lifetime >= 10*24*time.Hour {
return nil, fmt.Errorf("crl lifetime cannot be more than 10 days, got %q", lifetime)
} else if lifetime <= 0*time.Hour {
return nil, fmt.Errorf("crl lifetime must be positive, got %q", lifetime)
}
return &crlImpl{
issuers: issuersByNameID,
lifetime: lifetime,
maxLogLen: maxLogLen,
log: logger,
}, nil
}
func (ci *crlImpl) GenerateCRL(stream capb.CRLGenerator_GenerateCRLServer) error {
var issuer *issuance.Issuer
var template *x509.RevocationList
var shard int64
rcs := make([]pkix.RevokedCertificate, 0)
for {
in, err := stream.Recv()
if err != nil {
if err == io.EOF {
break
}
return err
}
switch payload := in.Payload.(type) {
case *capb.GenerateCRLRequest_Metadata:
if template != nil {
return errors.New("got more than one metadata message")
}
template, err = ci.metadataToTemplate(payload.Metadata)
if err != nil {
return err
}
var ok bool
issuer, ok = ci.issuers[issuance.IssuerNameID(payload.Metadata.IssuerNameID)]
if !ok {
return fmt.Errorf("got unrecognized IssuerNameID: %d", payload.Metadata.IssuerNameID)
}
shard = payload.Metadata.Shard
case *capb.GenerateCRLRequest_Entry:
rc, err := ci.entryToRevokedCertificate(payload.Entry)
if err != nil {
return err
}
rcs = append(rcs, *rc)
default:
return errors.New("got empty or malformed message in input stream")
}
}
if template == nil {
return errors.New("no crl metadata received")
}
// Compute a unique ID for this issuer-number-shard combo, to tie together all
// the audit log lines related to its issuance.
logID := blog.LogLineChecksum(fmt.Sprintf("%d", issuer.Cert.NameID()) + template.Number.String() + fmt.Sprintf("%d", shard))
ci.log.AuditInfof(
"Signing CRL: logID=[%s] issuer=[%s] number=[%s] shard=[%d] thisUpdate=[%s] nextUpdate=[%s] numEntries=[%d]",
logID, issuer.Cert.Subject.CommonName, template.Number.String(), template.ThisUpdate, template.NextUpdate, len(rcs),
)
builder := strings.Builder{}
for i := 0; i < len(rcs); i += 1 {
if builder.Len() == 0 {
fmt.Fprintf(&builder, "Signing CRL: logID=[%s] entries=[", logID)
}
// TODO: Figure out how best to include the reason code here, since it's
// slow/difficult to extract it from the already-encoded entry extension.
fmt.Fprintf(&builder, "%x,", rcs[i].SerialNumber.Bytes())
if builder.Len() != ci.maxLogLen {
ci.log.AuditInfof("%s", builder)
builder = strings.Builder{}
}
}
template.RevokedCertificates = rcs
crlBytes, err := x509.CreateRevocationList(
rand.Reader,
template,
issuer.Cert.Certificate,
issuer.Signer,
)
if err != nil {
return fmt.Errorf("signing crl: %w", err)
}
hash := sha256.Sum256(crlBytes)
ci.log.AuditInfof(
"Signing CRL success: logID=[%s] size=[%d] hash=[%d]",
logID, len(crlBytes), hash[:],
)
for i := 0; i < len(crlBytes); i += 1000 {
j := i + 1000
if j > len(crlBytes) {
j = len(crlBytes)
}
err = stream.Send(&capb.GenerateCRLResponse{
Chunk: crlBytes[i:j],
})
if err != nil {
return err
}
if i%1000 == 0 {
ci.log.Debugf("Wrote %d bytes to output stream", i*1000)
}
}
return nil
}
func (ci *crlImpl) metadataToTemplate(meta *capb.CRLMetadata) (*x509.RevocationList, error) {
if meta.IssuerNameID == 0 || meta.ThisUpdate == 0 {
return nil, errors.New("got incomplete metadata message")
}
// The CRL Number MUST be at most 20 octets, per RFC 5280 Section 5.2.3.
// A 64-bit (8-byte) integer will never exceed that requirement, but lets
// us guarantee that the CRL Number is always increasing without having to
// store or look up additional state.
number := big.NewInt(meta.ThisUpdate)
thisUpdate := time.Unix(0, meta.ThisUpdate)
return &x509.RevocationList{
Number: number,
ThisUpdate: thisUpdate,
NextUpdate: thisUpdate.Add(-time.Second).Add(ci.lifetime),
}, nil
}
func (ci *crlImpl) entryToRevokedCertificate(entry *corepb.CRLEntry) (*pkix.RevokedCertificate, error) {
serial, err := core.StringToSerial(entry.Serial)
if err != nil {
return nil, err
}
if entry.RevokedAt == 0 {
return nil, errors.New("got empty or zero revocation timestamp")
}
revokedAt := time.Unix(0, entry.RevokedAt)
// RFC 5280 Section 5.3.1 says "the reason code CRL entry extension SHOULD be
// absent instead of using the unspecified (0) reasonCode value.", so we make
// sure we only add this extension if we have a non-zero revocation reason.
var extensions []pkix.Extension
if entry.Reason != 0 {
reasonBytes, err := asn1.Marshal(asn1.Enumerated(entry.Reason))
if err != nil {
return nil, err
}
extensions = []pkix.Extension{
// The Reason Code extension, as defined in RFC 5280 Section 5.3.1:
// https://datatracker.ietf.org/doc/html/rfc5280#section-5.3.1
{
Id: asn1.ObjectIdentifier{2, 5, 29, 21}, // id-ce-reasonCode
Value: reasonBytes,
},
}
}
return &pkix.RevokedCertificate{
SerialNumber: serial,
RevocationTime: revokedAt,
Extensions: extensions,
}, nil
}

268
ca/crl_test.go Normal file
View File

@ -0,0 +1,268 @@
package ca
import (
"crypto/x509"
"io"
"testing"
"time"
"google.golang.org/grpc"
capb "github.com/letsencrypt/boulder/ca/proto"
corepb "github.com/letsencrypt/boulder/core/proto"
"github.com/letsencrypt/boulder/test"
)
type mockGenerateCRLBidiStream struct {
grpc.ServerStream
input <-chan *capb.GenerateCRLRequest
output chan<- *capb.GenerateCRLResponse
}
func (s mockGenerateCRLBidiStream) Recv() (*capb.GenerateCRLRequest, error) {
next, ok := <-s.input
if !ok {
return nil, io.EOF
}
return next, nil
}
func (s mockGenerateCRLBidiStream) Send(entry *capb.GenerateCRLResponse) error {
s.output <- entry
return nil
}
func TestGenerateCRL(t *testing.T) {
testCtx := setup(t)
crli := testCtx.crl
errs := make(chan error, 1)
// Test that we get an error when no metadata is sent.
ins := make(chan *capb.GenerateCRLRequest)
go func() {
errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: nil})
}()
close(ins)
err := <-errs
test.AssertError(t, err, "can't generate CRL with no metadata")
test.AssertContains(t, err.Error(), "no crl metadata received")
// Test that we get an error when incomplete metadata is sent.
ins = make(chan *capb.GenerateCRLRequest)
go func() {
errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: nil})
}()
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Metadata{
Metadata: &capb.CRLMetadata{},
},
}
close(ins)
err = <-errs
test.AssertError(t, err, "can't generate CRL with incomplete metadata")
test.AssertContains(t, err.Error(), "got incomplete metadata message")
// Test that we get an error when unrecognized metadata is sent.
ins = make(chan *capb.GenerateCRLRequest)
go func() {
errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: nil})
}()
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Metadata{
Metadata: &capb.CRLMetadata{
IssuerNameID: 1,
ThisUpdate: time.Now().UnixNano(),
},
},
}
close(ins)
err = <-errs
test.AssertError(t, err, "can't generate CRL with bad metadata")
test.AssertContains(t, err.Error(), "got unrecognized IssuerNameID")
// Test that we get an error when two metadata are sent.
ins = make(chan *capb.GenerateCRLRequest)
go func() {
errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: nil})
}()
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Metadata{
Metadata: &capb.CRLMetadata{
IssuerNameID: int64(testCtx.boulderIssuers[0].Cert.NameID()),
ThisUpdate: time.Now().UnixNano(),
},
},
}
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Metadata{
Metadata: &capb.CRLMetadata{
IssuerNameID: int64(testCtx.boulderIssuers[0].Cert.NameID()),
ThisUpdate: time.Now().UnixNano(),
},
},
}
close(ins)
err = <-errs
test.AssertError(t, err, "can't generate CRL with duplicate metadata")
test.AssertContains(t, err.Error(), "got more than one metadata message")
// Test that we get an error when an entry has a bad serial.
ins = make(chan *capb.GenerateCRLRequest)
go func() {
errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: nil})
}()
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Entry{
Entry: &corepb.CRLEntry{
Serial: "123",
Reason: 1,
RevokedAt: time.Now().UnixNano(),
},
},
}
close(ins)
err = <-errs
test.AssertError(t, err, "can't generate CRL with bad serials")
test.AssertContains(t, err.Error(), "Invalid serial number")
// Test that we get an error when an entry has a bad revocation time.
ins = make(chan *capb.GenerateCRLRequest)
go func() {
errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: nil})
}()
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Entry{
Entry: &corepb.CRLEntry{
Serial: "deadbeefdeadbeefdeadbeefdeadbeefdead",
Reason: 1,
RevokedAt: 0,
},
},
}
close(ins)
err = <-errs
test.AssertError(t, err, "can't generate CRL with bad serials")
test.AssertContains(t, err.Error(), "got empty or zero revocation timestamp")
// Test that generating an empty CRL works.
ins = make(chan *capb.GenerateCRLRequest)
outs := make(chan *capb.GenerateCRLResponse)
go func() {
errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: outs})
close(outs)
}()
crlBytes := make([]byte, 0)
done := make(chan struct{})
go func() {
for resp := range outs {
crlBytes = append(crlBytes, resp.Chunk...)
}
close(done)
}()
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Metadata{
Metadata: &capb.CRLMetadata{
IssuerNameID: int64(testCtx.boulderIssuers[0].Cert.NameID()),
ThisUpdate: time.Now().UnixNano(),
},
},
}
close(ins)
err = <-errs
<-done
test.AssertNotError(t, err, "generating empty CRL should work")
test.Assert(t, len(crlBytes) > 0, "should have gotten some CRL bytes")
crl, err := x509.ParseCRL(crlBytes)
test.AssertNotError(t, err, "should be able to parse empty CRL")
test.AssertEquals(t, len(crl.TBSCertList.RevokedCertificates), 0)
err = testCtx.boulderIssuers[0].Cert.CheckCRLSignature(crl)
test.AssertNotError(t, err, "CRL signature should validate")
// Test that generating a CRL with some entries works.
ins = make(chan *capb.GenerateCRLRequest)
outs = make(chan *capb.GenerateCRLResponse)
go func() {
errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: outs})
close(outs)
}()
crlBytes = make([]byte, 0)
done = make(chan struct{})
go func() {
for resp := range outs {
crlBytes = append(crlBytes, resp.Chunk...)
}
close(done)
}()
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Metadata{
Metadata: &capb.CRLMetadata{
IssuerNameID: int64(testCtx.boulderIssuers[0].Cert.NameID()),
ThisUpdate: time.Now().UnixNano(),
},
},
}
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Entry{
Entry: &corepb.CRLEntry{
Serial: "000000000000000000000000000000000000",
RevokedAt: time.Now().UnixNano(),
// Reason 0, Unspecified, is omitted.
},
},
}
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Entry{
Entry: &corepb.CRLEntry{
Serial: "111111111111111111111111111111111111",
Reason: 1, // keyCompromise
RevokedAt: time.Now().UnixNano(),
},
},
}
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Entry{
Entry: &corepb.CRLEntry{
Serial: "333333333333333333333333333333333333",
Reason: 3, // affiliationChanged
RevokedAt: time.Now().UnixNano(),
},
},
}
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Entry{
Entry: &corepb.CRLEntry{
Serial: "444444444444444444444444444444444444",
Reason: 4, // superseded
RevokedAt: time.Now().UnixNano(),
},
},
}
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Entry{
Entry: &corepb.CRLEntry{
Serial: "555555555555555555555555555555555555",
Reason: 5, // cessationOfOperation
RevokedAt: time.Now().UnixNano(),
},
},
}
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Entry{
Entry: &corepb.CRLEntry{
Serial: "999999999999999999999999999999999999",
Reason: 9, // privilegeWithdrawn
RevokedAt: time.Now().UnixNano(),
},
},
}
close(ins)
err = <-errs
<-done
test.AssertNotError(t, err, "generating empty CRL should work")
test.Assert(t, len(crlBytes) > 0, "should have gotten some CRL bytes")
crl, err = x509.ParseCRL(crlBytes)
test.AssertNotError(t, err, "should be able to parse empty CRL")
test.AssertEquals(t, len(crl.TBSCertList.RevokedCertificates), 6)
err = testCtx.boulderIssuers[0].Cert.CheckCRLSignature(crl)
test.AssertNotError(t, err, "CRL signature should validate")
}

View File

@ -30,6 +30,7 @@ func TestOCSP(t *testing.T) {
&mockSA{},
testCtx.pa,
testCtx.ocsp,
testCtx.crl,
testCtx.boulderIssuers,
nil,
testCtx.certExpiry,

View File

@ -337,6 +337,196 @@ func (x *OCSPResponse) GetResponse() []byte {
return nil
}
type GenerateCRLRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
// Types that are assignable to Payload:
// *GenerateCRLRequest_Metadata
// *GenerateCRLRequest_Entry
Payload isGenerateCRLRequest_Payload `protobuf_oneof:"payload"`
}
func (x *GenerateCRLRequest) Reset() {
*x = GenerateCRLRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_ca_proto_msgTypes[5]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GenerateCRLRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GenerateCRLRequest) ProtoMessage() {}
func (x *GenerateCRLRequest) ProtoReflect() protoreflect.Message {
mi := &file_ca_proto_msgTypes[5]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GenerateCRLRequest.ProtoReflect.Descriptor instead.
func (*GenerateCRLRequest) Descriptor() ([]byte, []int) {
return file_ca_proto_rawDescGZIP(), []int{5}
}
func (m *GenerateCRLRequest) GetPayload() isGenerateCRLRequest_Payload {
if m != nil {
return m.Payload
}
return nil
}
func (x *GenerateCRLRequest) GetMetadata() *CRLMetadata {
if x, ok := x.GetPayload().(*GenerateCRLRequest_Metadata); ok {
return x.Metadata
}
return nil
}
func (x *GenerateCRLRequest) GetEntry() *proto.CRLEntry {
if x, ok := x.GetPayload().(*GenerateCRLRequest_Entry); ok {
return x.Entry
}
return nil
}
type isGenerateCRLRequest_Payload interface {
isGenerateCRLRequest_Payload()
}
type GenerateCRLRequest_Metadata struct {
Metadata *CRLMetadata `protobuf:"bytes,1,opt,name=metadata,proto3,oneof"`
}
type GenerateCRLRequest_Entry struct {
Entry *proto.CRLEntry `protobuf:"bytes,2,opt,name=entry,proto3,oneof"`
}
func (*GenerateCRLRequest_Metadata) isGenerateCRLRequest_Payload() {}
func (*GenerateCRLRequest_Entry) isGenerateCRLRequest_Payload() {}
type CRLMetadata struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
IssuerNameID int64 `protobuf:"varint,1,opt,name=issuerNameID,proto3" json:"issuerNameID,omitempty"`
ThisUpdate int64 `protobuf:"varint,2,opt,name=thisUpdate,proto3" json:"thisUpdate,omitempty"` // Unix timestamp (nanoseconds), also used for CRLNumber.
Shard int64 `protobuf:"varint,3,opt,name=shard,proto3" json:"shard,omitempty"`
}
func (x *CRLMetadata) Reset() {
*x = CRLMetadata{}
if protoimpl.UnsafeEnabled {
mi := &file_ca_proto_msgTypes[6]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *CRLMetadata) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*CRLMetadata) ProtoMessage() {}
func (x *CRLMetadata) ProtoReflect() protoreflect.Message {
mi := &file_ca_proto_msgTypes[6]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use CRLMetadata.ProtoReflect.Descriptor instead.
func (*CRLMetadata) Descriptor() ([]byte, []int) {
return file_ca_proto_rawDescGZIP(), []int{6}
}
func (x *CRLMetadata) GetIssuerNameID() int64 {
if x != nil {
return x.IssuerNameID
}
return 0
}
func (x *CRLMetadata) GetThisUpdate() int64 {
if x != nil {
return x.ThisUpdate
}
return 0
}
func (x *CRLMetadata) GetShard() int64 {
if x != nil {
return x.Shard
}
return 0
}
type GenerateCRLResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Chunk []byte `protobuf:"bytes,1,opt,name=chunk,proto3" json:"chunk,omitempty"`
}
func (x *GenerateCRLResponse) Reset() {
*x = GenerateCRLResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_ca_proto_msgTypes[7]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GenerateCRLResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GenerateCRLResponse) ProtoMessage() {}
func (x *GenerateCRLResponse) ProtoReflect() protoreflect.Message {
mi := &file_ca_proto_msgTypes[7]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GenerateCRLResponse.ProtoReflect.Descriptor instead.
func (*GenerateCRLResponse) Descriptor() ([]byte, []int) {
return file_ca_proto_rawDescGZIP(), []int{7}
}
func (x *GenerateCRLResponse) GetChunk() []byte {
if x != nil {
return x.Chunk
}
return nil
}
var File_ca_proto protoreflect.FileDescriptor
var file_ca_proto_rawDesc = []byte{
@ -376,32 +566,59 @@ var file_ca_proto_rawDesc = []byte{
0x08, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x49, 0x44, 0x22, 0x2a, 0x0a, 0x0c, 0x4f, 0x43, 0x53,
0x50, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x1a, 0x0a, 0x08, 0x72, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x08, 0x72, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x32, 0x92, 0x02, 0x0a, 0x14, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66,
0x69, 0x63, 0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x55,
0x0a, 0x13, 0x49, 0x73, 0x73, 0x75, 0x65, 0x50, 0x72, 0x65, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66,
0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x63, 0x61, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65,
0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65,
0x73, 0x74, 0x1a, 0x1f, 0x2e, 0x63, 0x61, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x50, 0x72, 0x65,
0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f,
0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x66, 0x0a, 0x21, 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, 0x65,
0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x50, 0x72, 0x65, 0x63,
0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x63, 0x61, 0x2e,
0x49, 0x73, 0x73, 0x75, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65,
0x46, 0x6f, 0x72, 0x50, 0x72, 0x65, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74,
0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e,
0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a,
0x0c, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x4f, 0x43, 0x53, 0x50, 0x12, 0x17, 0x2e,
0x63, 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, 0x32, 0x4c, 0x0a, 0x0d, 0x4f, 0x43,
0x53, 0x50, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x3b, 0x0a, 0x0c, 0x47,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x76, 0x0a, 0x12, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74,
0x65, 0x43, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x2d, 0x0a, 0x08, 0x6d,
0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0f, 0x2e,
0x63, 0x61, 0x2e, 0x43, 0x52, 0x4c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x48, 0x00,
0x52, 0x08, 0x6d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x26, 0x0a, 0x05, 0x65, 0x6e,
0x74, 0x72, 0x79, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x0e, 0x2e, 0x63, 0x6f, 0x72, 0x65,
0x2e, 0x43, 0x52, 0x4c, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x48, 0x00, 0x52, 0x05, 0x65, 0x6e, 0x74,
0x72, 0x79, 0x42, 0x09, 0x0a, 0x07, 0x70, 0x61, 0x79, 0x6c, 0x6f, 0x61, 0x64, 0x22, 0x67, 0x0a,
0x0b, 0x43, 0x52, 0x4c, 0x4d, 0x65, 0x74, 0x61, 0x64, 0x61, 0x74, 0x61, 0x12, 0x22, 0x0a, 0x0c,
0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x49, 0x44, 0x18, 0x01, 0x20, 0x01,
0x28, 0x03, 0x52, 0x0c, 0x69, 0x73, 0x73, 0x75, 0x65, 0x72, 0x4e, 0x61, 0x6d, 0x65, 0x49, 0x44,
0x12, 0x1e, 0x0a, 0x0a, 0x74, 0x68, 0x69, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65, 0x18, 0x02,
0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x74, 0x68, 0x69, 0x73, 0x55, 0x70, 0x64, 0x61, 0x74, 0x65,
0x12, 0x14, 0x0a, 0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52,
0x05, 0x73, 0x68, 0x61, 0x72, 0x64, 0x22, 0x2b, 0x0a, 0x13, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61,
0x74, 0x65, 0x43, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a,
0x05, 0x63, 0x68, 0x75, 0x6e, 0x6b, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x63, 0x68,
0x75, 0x6e, 0x6b, 0x32, 0xd8, 0x02, 0x0a, 0x14, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
0x61, 0x74, 0x65, 0x41, 0x75, 0x74, 0x68, 0x6f, 0x72, 0x69, 0x74, 0x79, 0x12, 0x55, 0x0a, 0x13,
0x49, 0x73, 0x73, 0x75, 0x65, 0x50, 0x72, 0x65, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63,
0x61, 0x74, 0x65, 0x12, 0x1b, 0x2e, 0x63, 0x61, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, 0x65,
0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74,
0x1a, 0x1f, 0x2e, 0x63, 0x61, 0x2e, 0x49, 0x73, 0x73, 0x75, 0x65, 0x50, 0x72, 0x65, 0x63, 0x65,
0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x00, 0x12, 0x66, 0x0a, 0x21, 0x49, 0x73, 0x73, 0x75, 0x65, 0x43, 0x65, 0x72, 0x74,
0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x46, 0x6f, 0x72, 0x50, 0x72, 0x65, 0x63, 0x65, 0x72,
0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x12, 0x2c, 0x2e, 0x63, 0x61, 0x2e, 0x49, 0x73,
0x73, 0x75, 0x65, 0x43, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x46, 0x6f,
0x72, 0x50, 0x72, 0x65, 0x63, 0x65, 0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x11, 0x2e, 0x63, 0x6f, 0x72, 0x65, 0x2e, 0x43, 0x65,
0x72, 0x74, 0x69, 0x66, 0x69, 0x63, 0x61, 0x74, 0x65, 0x22, 0x00, 0x12, 0x3b, 0x0a, 0x0c, 0x47,
0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x4f, 0x43, 0x53, 0x50, 0x12, 0x17, 0x2e, 0x63, 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, 0x63, 0x61, 0x2f, 0x70, 0x72,
0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x12, 0x44, 0x0a, 0x0b, 0x47, 0x65, 0x6e, 0x65,
0x72, 0x61, 0x74, 0x65, 0x43, 0x52, 0x4c, 0x12, 0x16, 0x2e, 0x63, 0x61, 0x2e, 0x47, 0x65, 0x6e,
0x65, 0x72, 0x61, 0x74, 0x65, 0x43, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a,
0x17, 0x2e, 0x63, 0x61, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x43, 0x52, 0x4c,
0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01, 0x30, 0x01, 0x32, 0x4c,
0x0a, 0x0d, 0x4f, 0x43, 0x53, 0x50, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12,
0x3b, 0x0a, 0x0c, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x4f, 0x43, 0x53, 0x50, 0x12,
0x17, 0x2e, 0x63, 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, 0x32, 0x54, 0x0a, 0x0c,
0x43, 0x52, 0x4c, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x6f, 0x72, 0x12, 0x44, 0x0a, 0x0b,
0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x43, 0x52, 0x4c, 0x12, 0x16, 0x2e, 0x63, 0x61,
0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x65, 0x43, 0x52, 0x4c, 0x52, 0x65, 0x71, 0x75,
0x65, 0x73, 0x74, 0x1a, 0x17, 0x2e, 0x63, 0x61, 0x2e, 0x47, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74,
0x65, 0x43, 0x52, 0x4c, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x22, 0x00, 0x28, 0x01,
0x30, 0x01, 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, 0x63, 0x61, 0x2f, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x62, 0x06, 0x70,
0x72, 0x6f, 0x74, 0x6f, 0x33,
}
var (
@ -416,29 +633,39 @@ func file_ca_proto_rawDescGZIP() []byte {
return file_ca_proto_rawDescData
}
var file_ca_proto_msgTypes = make([]protoimpl.MessageInfo, 5)
var file_ca_proto_msgTypes = make([]protoimpl.MessageInfo, 8)
var file_ca_proto_goTypes = []interface{}{
(*IssueCertificateRequest)(nil), // 0: ca.IssueCertificateRequest
(*IssuePrecertificateResponse)(nil), // 1: ca.IssuePrecertificateResponse
(*IssueCertificateForPrecertificateRequest)(nil), // 2: ca.IssueCertificateForPrecertificateRequest
(*GenerateOCSPRequest)(nil), // 3: ca.GenerateOCSPRequest
(*OCSPResponse)(nil), // 4: ca.OCSPResponse
(*proto.Certificate)(nil), // 5: core.Certificate
(*GenerateCRLRequest)(nil), // 5: ca.GenerateCRLRequest
(*CRLMetadata)(nil), // 6: ca.CRLMetadata
(*GenerateCRLResponse)(nil), // 7: ca.GenerateCRLResponse
(*proto.CRLEntry)(nil), // 8: core.CRLEntry
(*proto.Certificate)(nil), // 9: core.Certificate
}
var file_ca_proto_depIdxs = []int32{
0, // 0: ca.CertificateAuthority.IssuePrecertificate:input_type -> ca.IssueCertificateRequest
2, // 1: ca.CertificateAuthority.IssueCertificateForPrecertificate:input_type -> ca.IssueCertificateForPrecertificateRequest
3, // 2: ca.CertificateAuthority.GenerateOCSP:input_type -> ca.GenerateOCSPRequest
3, // 3: ca.OCSPGenerator.GenerateOCSP:input_type -> ca.GenerateOCSPRequest
1, // 4: ca.CertificateAuthority.IssuePrecertificate:output_type -> ca.IssuePrecertificateResponse
5, // 5: ca.CertificateAuthority.IssueCertificateForPrecertificate:output_type -> core.Certificate
4, // 6: ca.CertificateAuthority.GenerateOCSP:output_type -> ca.OCSPResponse
4, // 7: ca.OCSPGenerator.GenerateOCSP:output_type -> ca.OCSPResponse
4, // [4:8] is the sub-list for method output_type
0, // [0:4] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
6, // 0: ca.GenerateCRLRequest.metadata:type_name -> ca.CRLMetadata
8, // 1: ca.GenerateCRLRequest.entry:type_name -> core.CRLEntry
0, // 2: ca.CertificateAuthority.IssuePrecertificate:input_type -> ca.IssueCertificateRequest
2, // 3: ca.CertificateAuthority.IssueCertificateForPrecertificate:input_type -> ca.IssueCertificateForPrecertificateRequest
3, // 4: ca.CertificateAuthority.GenerateOCSP:input_type -> ca.GenerateOCSPRequest
5, // 5: ca.CertificateAuthority.GenerateCRL:input_type -> ca.GenerateCRLRequest
3, // 6: ca.OCSPGenerator.GenerateOCSP:input_type -> ca.GenerateOCSPRequest
5, // 7: ca.CRLGenerator.GenerateCRL:input_type -> ca.GenerateCRLRequest
1, // 8: ca.CertificateAuthority.IssuePrecertificate:output_type -> ca.IssuePrecertificateResponse
9, // 9: ca.CertificateAuthority.IssueCertificateForPrecertificate:output_type -> core.Certificate
4, // 10: ca.CertificateAuthority.GenerateOCSP:output_type -> ca.OCSPResponse
7, // 11: ca.CertificateAuthority.GenerateCRL:output_type -> ca.GenerateCRLResponse
4, // 12: ca.OCSPGenerator.GenerateOCSP:output_type -> ca.OCSPResponse
7, // 13: ca.CRLGenerator.GenerateCRL:output_type -> ca.GenerateCRLResponse
8, // [8:14] is the sub-list for method output_type
2, // [2:8] is the sub-list for method input_type
2, // [2:2] is the sub-list for extension type_name
2, // [2:2] is the sub-list for extension extendee
0, // [0:2] is the sub-list for field type_name
}
func init() { file_ca_proto_init() }
@ -507,6 +734,46 @@ func file_ca_proto_init() {
return nil
}
}
file_ca_proto_msgTypes[5].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GenerateCRLRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_ca_proto_msgTypes[6].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*CRLMetadata); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_ca_proto_msgTypes[7].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GenerateCRLResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
file_ca_proto_msgTypes[5].OneofWrappers = []interface{}{
(*GenerateCRLRequest_Metadata)(nil),
(*GenerateCRLRequest_Entry)(nil),
}
type x struct{}
out := protoimpl.TypeBuilder{
@ -514,9 +781,9 @@ func file_ca_proto_init() {
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_ca_proto_rawDesc,
NumEnums: 0,
NumMessages: 5,
NumMessages: 8,
NumExtensions: 0,
NumServices: 2,
NumServices: 3,
},
GoTypes: file_ca_proto_goTypes,
DependencyIndexes: file_ca_proto_depIdxs,

View File

@ -10,14 +10,7 @@ service CertificateAuthority {
rpc IssuePrecertificate(IssueCertificateRequest) returns (IssuePrecertificateResponse) {}
rpc IssueCertificateForPrecertificate(IssueCertificateForPrecertificateRequest) returns (core.Certificate) {}
rpc GenerateOCSP(GenerateOCSPRequest) returns (OCSPResponse) {}
}
// OCSPGenerator generates OCSP. We separate this out from
// CertificateAuthority so that we can restrict access to a different subset of
// hosts, so the hosts that need to request OCSP generation don't need to be
// able to request certificate issuance.
service OCSPGenerator {
rpc GenerateOCSP(GenerateOCSPRequest) returns (OCSPResponse) {}
rpc GenerateCRL(stream GenerateCRLRequest) returns (stream GenerateCRLResponse) {}
}
message IssueCertificateRequest {
@ -38,6 +31,14 @@ message IssueCertificateForPrecertificateRequest {
int64 orderID = 4;
}
// OCSPGenerator generates OCSP. We separate this out from
// CertificateAuthority so that we can restrict access to a different subset of
// hosts, so the hosts that need to request OCSP generation don't need to be
// able to request certificate issuance.
service OCSPGenerator {
rpc GenerateOCSP(GenerateOCSPRequest) returns (OCSPResponse) {}
}
// Exactly one of certDER or [serial and issuerID] must be set.
message GenerateOCSPRequest {
string status = 2;
@ -50,3 +51,25 @@ message GenerateOCSPRequest {
message OCSPResponse {
bytes response = 1;
}
// CRLGenerator signs CRLs. It is separated for the same reason as OCSPGenerator.
service CRLGenerator {
rpc GenerateCRL(stream GenerateCRLRequest) returns (stream GenerateCRLResponse) {}
}
message GenerateCRLRequest {
oneof payload {
CRLMetadata metadata = 1;
core.CRLEntry entry = 2;
}
}
message CRLMetadata {
int64 issuerNameID = 1;
int64 thisUpdate = 2; // Unix timestamp (nanoseconds), also used for CRLNumber.
int64 shard = 3;
}
message GenerateCRLResponse {
bytes chunk = 1;
}

View File

@ -26,6 +26,7 @@ type CertificateAuthorityClient interface {
IssuePrecertificate(ctx context.Context, in *IssueCertificateRequest, opts ...grpc.CallOption) (*IssuePrecertificateResponse, error)
IssueCertificateForPrecertificate(ctx context.Context, in *IssueCertificateForPrecertificateRequest, opts ...grpc.CallOption) (*proto.Certificate, error)
GenerateOCSP(ctx context.Context, in *GenerateOCSPRequest, opts ...grpc.CallOption) (*OCSPResponse, error)
GenerateCRL(ctx context.Context, opts ...grpc.CallOption) (CertificateAuthority_GenerateCRLClient, error)
}
type certificateAuthorityClient struct {
@ -63,6 +64,37 @@ func (c *certificateAuthorityClient) GenerateOCSP(ctx context.Context, in *Gener
return out, nil
}
func (c *certificateAuthorityClient) GenerateCRL(ctx context.Context, opts ...grpc.CallOption) (CertificateAuthority_GenerateCRLClient, error) {
stream, err := c.cc.NewStream(ctx, &CertificateAuthority_ServiceDesc.Streams[0], "/ca.CertificateAuthority/GenerateCRL", opts...)
if err != nil {
return nil, err
}
x := &certificateAuthorityGenerateCRLClient{stream}
return x, nil
}
type CertificateAuthority_GenerateCRLClient interface {
Send(*GenerateCRLRequest) error
Recv() (*GenerateCRLResponse, error)
grpc.ClientStream
}
type certificateAuthorityGenerateCRLClient struct {
grpc.ClientStream
}
func (x *certificateAuthorityGenerateCRLClient) Send(m *GenerateCRLRequest) error {
return x.ClientStream.SendMsg(m)
}
func (x *certificateAuthorityGenerateCRLClient) Recv() (*GenerateCRLResponse, error) {
m := new(GenerateCRLResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// CertificateAuthorityServer is the server API for CertificateAuthority service.
// All implementations must embed UnimplementedCertificateAuthorityServer
// for forward compatibility
@ -70,6 +102,7 @@ type CertificateAuthorityServer interface {
IssuePrecertificate(context.Context, *IssueCertificateRequest) (*IssuePrecertificateResponse, error)
IssueCertificateForPrecertificate(context.Context, *IssueCertificateForPrecertificateRequest) (*proto.Certificate, error)
GenerateOCSP(context.Context, *GenerateOCSPRequest) (*OCSPResponse, error)
GenerateCRL(CertificateAuthority_GenerateCRLServer) error
mustEmbedUnimplementedCertificateAuthorityServer()
}
@ -86,6 +119,9 @@ func (UnimplementedCertificateAuthorityServer) IssueCertificateForPrecertificate
func (UnimplementedCertificateAuthorityServer) GenerateOCSP(context.Context, *GenerateOCSPRequest) (*OCSPResponse, error) {
return nil, status.Errorf(codes.Unimplemented, "method GenerateOCSP not implemented")
}
func (UnimplementedCertificateAuthorityServer) GenerateCRL(CertificateAuthority_GenerateCRLServer) error {
return status.Errorf(codes.Unimplemented, "method GenerateCRL not implemented")
}
func (UnimplementedCertificateAuthorityServer) mustEmbedUnimplementedCertificateAuthorityServer() {}
// UnsafeCertificateAuthorityServer may be embedded to opt out of forward compatibility for this service.
@ -153,6 +189,32 @@ func _CertificateAuthority_GenerateOCSP_Handler(srv interface{}, ctx context.Con
return interceptor(ctx, in, info, handler)
}
func _CertificateAuthority_GenerateCRL_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(CertificateAuthorityServer).GenerateCRL(&certificateAuthorityGenerateCRLServer{stream})
}
type CertificateAuthority_GenerateCRLServer interface {
Send(*GenerateCRLResponse) error
Recv() (*GenerateCRLRequest, error)
grpc.ServerStream
}
type certificateAuthorityGenerateCRLServer struct {
grpc.ServerStream
}
func (x *certificateAuthorityGenerateCRLServer) Send(m *GenerateCRLResponse) error {
return x.ServerStream.SendMsg(m)
}
func (x *certificateAuthorityGenerateCRLServer) Recv() (*GenerateCRLRequest, error) {
m := new(GenerateCRLRequest)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// CertificateAuthority_ServiceDesc is the grpc.ServiceDesc for CertificateAuthority service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
@ -173,7 +235,14 @@ var CertificateAuthority_ServiceDesc = grpc.ServiceDesc{
Handler: _CertificateAuthority_GenerateOCSP_Handler,
},
},
Streams: []grpc.StreamDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "GenerateCRL",
Handler: _CertificateAuthority_GenerateCRL_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "ca.proto",
}
@ -262,3 +331,121 @@ var OCSPGenerator_ServiceDesc = grpc.ServiceDesc{
Streams: []grpc.StreamDesc{},
Metadata: "ca.proto",
}
// CRLGeneratorClient is the client API for CRLGenerator service.
//
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
type CRLGeneratorClient interface {
GenerateCRL(ctx context.Context, opts ...grpc.CallOption) (CRLGenerator_GenerateCRLClient, error)
}
type cRLGeneratorClient struct {
cc grpc.ClientConnInterface
}
func NewCRLGeneratorClient(cc grpc.ClientConnInterface) CRLGeneratorClient {
return &cRLGeneratorClient{cc}
}
func (c *cRLGeneratorClient) GenerateCRL(ctx context.Context, opts ...grpc.CallOption) (CRLGenerator_GenerateCRLClient, error) {
stream, err := c.cc.NewStream(ctx, &CRLGenerator_ServiceDesc.Streams[0], "/ca.CRLGenerator/GenerateCRL", opts...)
if err != nil {
return nil, err
}
x := &cRLGeneratorGenerateCRLClient{stream}
return x, nil
}
type CRLGenerator_GenerateCRLClient interface {
Send(*GenerateCRLRequest) error
Recv() (*GenerateCRLResponse, error)
grpc.ClientStream
}
type cRLGeneratorGenerateCRLClient struct {
grpc.ClientStream
}
func (x *cRLGeneratorGenerateCRLClient) Send(m *GenerateCRLRequest) error {
return x.ClientStream.SendMsg(m)
}
func (x *cRLGeneratorGenerateCRLClient) Recv() (*GenerateCRLResponse, error) {
m := new(GenerateCRLResponse)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// CRLGeneratorServer is the server API for CRLGenerator service.
// All implementations must embed UnimplementedCRLGeneratorServer
// for forward compatibility
type CRLGeneratorServer interface {
GenerateCRL(CRLGenerator_GenerateCRLServer) error
mustEmbedUnimplementedCRLGeneratorServer()
}
// UnimplementedCRLGeneratorServer must be embedded to have forward compatible implementations.
type UnimplementedCRLGeneratorServer struct {
}
func (UnimplementedCRLGeneratorServer) GenerateCRL(CRLGenerator_GenerateCRLServer) error {
return status.Errorf(codes.Unimplemented, "method GenerateCRL not implemented")
}
func (UnimplementedCRLGeneratorServer) mustEmbedUnimplementedCRLGeneratorServer() {}
// UnsafeCRLGeneratorServer may be embedded to opt out of forward compatibility for this service.
// Use of this interface is not recommended, as added methods to CRLGeneratorServer will
// result in compilation errors.
type UnsafeCRLGeneratorServer interface {
mustEmbedUnimplementedCRLGeneratorServer()
}
func RegisterCRLGeneratorServer(s grpc.ServiceRegistrar, srv CRLGeneratorServer) {
s.RegisterService(&CRLGenerator_ServiceDesc, srv)
}
func _CRLGenerator_GenerateCRL_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(CRLGeneratorServer).GenerateCRL(&cRLGeneratorGenerateCRLServer{stream})
}
type CRLGenerator_GenerateCRLServer interface {
Send(*GenerateCRLResponse) error
Recv() (*GenerateCRLRequest, error)
grpc.ServerStream
}
type cRLGeneratorGenerateCRLServer struct {
grpc.ServerStream
}
func (x *cRLGeneratorGenerateCRLServer) Send(m *GenerateCRLResponse) error {
return x.ServerStream.SendMsg(m)
}
func (x *cRLGeneratorGenerateCRLServer) Recv() (*GenerateCRLRequest, error) {
m := new(GenerateCRLRequest)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// CRLGenerator_ServiceDesc is the grpc.ServiceDesc for CRLGenerator service.
// It's only intended for direct use with grpc.RegisterService,
// and not to be introspected or modified (even as a copy)
var CRLGenerator_ServiceDesc = grpc.ServiceDesc{
ServiceName: "ca.CRLGenerator",
HandlerType: (*CRLGeneratorServer)(nil),
Methods: []grpc.MethodDesc{},
Streams: []grpc.StreamDesc{
{
StreamName: "GenerateCRL",
Handler: _CRLGenerator_GenerateCRL_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "ca.proto",
}

View File

@ -3,12 +3,14 @@ package notmain
import (
"flag"
"fmt"
"net"
"os"
"sync"
"github.com/beeker1121/goque"
"github.com/honeycombio/beeline-go"
"github.com/prometheus/client_golang/prometheus"
"google.golang.org/grpc"
"google.golang.org/grpc/health"
healthpb "google.golang.org/grpc/health/grpc_health_v1"
@ -34,6 +36,7 @@ type Config struct {
GRPCCA *cmd.GRPCServerConfig
GRPCOCSPGenerator *cmd.GRPCServerConfig
GRPCCRLGenerator *cmd.GRPCServerConfig
SAService *cmd.GRPCClientConfig
@ -56,10 +59,16 @@ type Config struct {
// The maximum number of subjectAltNames in a single certificate
MaxNames int
// LifespanOCSP is how long OCSP responses are valid for; It should be longer
// than the minTimeToExpiry field for the OCSP Updater.
// LifespanOCSP is how long OCSP responses are valid for. It should be
// longer than the minTimeToExpiry field for the OCSP Updater. Per the BRs,
// Section 4.9.10, it MUST NOT be more than 10 days.
LifespanOCSP cmd.ConfigDuration
// LifespanCRL is how long CRLs are valid for. It should be longer than the
// `period` field of the CRL Updater. Per the BRs, Section 4.9.7, it MUST
// NOT be more than 10 days.
LifespanCRL cmd.ConfigDuration
// GoodKey is an embedded config stanza for the goodkey library.
GoodKey goodkey.Config
@ -130,6 +139,7 @@ func loadBoulderIssuers(profileConfig issuance.ProfileConfig, issuerConfigs []is
func main() {
caAddr := flag.String("ca-addr", "", "CA gRPC listen address override")
ocspAddr := flag.String("ocsp-addr", "", "OCSP gRPC listen address override")
crlAddr := flag.String("crl-addr", "", "CRL gRPC listen address override")
debugAddr := flag.String("debug-addr", "", "Debug server address override")
configFile := flag.String("config", "", "File path to the configuration file for this service")
flag.Parse()
@ -151,6 +161,10 @@ func main() {
if *ocspAddr != "" {
c.CA.GRPCOCSPGenerator.Address = *ocspAddr
}
// TODO(#6161): Remove second conditional when we know it always exists.
if *crlAddr != "" && c.CA.GRPCCRLGenerator != nil {
c.CA.GRPCCRLGenerator.Address = *crlAddr
}
if *debugAddr != "" {
c.CA.DebugAddr = *debugAddr
}
@ -261,7 +275,7 @@ func main() {
go ocspi.LogOCSPLoop()
ocspSrv, ocspListener, err := bgrpc.NewServer(c.CA.GRPCOCSPGenerator, tlsConfig, serverMetrics, clk)
cmd.FailOnError(err, "Unable to setup CA gRPC server")
cmd.FailOnError(err, "Unable to setup CA OCSP gRPC server")
capb.RegisterOCSPGeneratorServer(ocspSrv, ocspi)
ocspHealth := health.NewServer()
healthpb.RegisterHealthServer(ocspSrv, ocspHealth)
@ -272,10 +286,40 @@ func main() {
wg.Done()
}()
crli, err := ca.NewCRLImpl(
boulderIssuers,
c.CA.LifespanCRL.Duration,
c.CA.OCSPLogMaxLength,
logger,
)
cmd.FailOnError(err, "Failed to create CRL impl")
// TODO(#6161): Make this unconditional after this config is deployed. We have
// to pre-declare crlListener even though it is only used inside the if block
// so that the other assignments inside the block don't shadow crlSrv and
// crlHealth, leaving them nil.
var crlSrv *grpc.Server
var crlHealth *health.Server
var crlListener net.Listener
if c.CA.GRPCCRLGenerator != nil {
crlSrv, crlListener, err = bgrpc.NewServer(c.CA.GRPCCRLGenerator, tlsConfig, serverMetrics, clk)
cmd.FailOnError(err, "Unable to setup CA CRL gRPC server")
capb.RegisterCRLGeneratorServer(crlSrv, crli)
crlHealth = health.NewServer()
healthpb.RegisterHealthServer(crlSrv, crlHealth)
wg.Add(1)
go func() {
cmd.FailOnError(cmd.FilterShutdownErrors(crlSrv.Serve(crlListener)),
"CRLGenerator gRPC service failed")
wg.Done()
}()
}
cai, err := ca.NewCertificateAuthorityImpl(
sa,
pa,
ocspi,
crli,
boulderIssuers,
ecdsaAllowList,
c.CA.Expiry.Duration,
@ -309,9 +353,17 @@ func main() {
go cmd.CatchSignals(logger, func() {
caHealth.Shutdown()
ocspHealth.Shutdown()
// TODO(#6161): Make this unconditional.
if crlHealth != nil {
crlHealth.Shutdown()
}
ecdsaAllowList.Stop()
caSrv.GracefulStop()
ocspSrv.GracefulStop()
// TODO(#6161): Make this unconditional.
if crlSrv != nil {
crlSrv.GracefulStop()
}
wg.Wait()
ocspi.Stop()
})

View File

@ -83,5 +83,6 @@ func generateKey(session *pkcs11helpers.Session, label string, outputPath string
return nil, fmt.Errorf("Failed to write public key to %q: %s", outputPath, err)
}
log.Printf("Public key written to %q\n", outputPath)
return &keyInfo{key: pubKey, der: der, id: keyID}, nil
}

View File

@ -240,7 +240,8 @@ type keyConfig struct {
PKCS11 PKCS11KeyGenConfig `yaml:"pkcs11"`
Key keyGenConfig `yaml:"key"`
Outputs struct {
PublicKeyPath string `yaml:"public-key-path"`
PublicKeyPath string `yaml:"public-key-path"`
PKCS11ConfigPath string `yaml:"pkcs11-config-path"`
} `yaml:"outputs"`
}
@ -621,6 +622,17 @@ func keyCeremony(configBytes []byte) error {
return err
}
if config.Outputs.PKCS11ConfigPath != "" {
contents := fmt.Sprintf(
`{"module": %q, "tokenLabel": %q, "pin": %q}`,
config.PKCS11.Module, config.PKCS11.StoreLabel, config.PKCS11.PIN,
)
err = writeFile(config.Outputs.PKCS11ConfigPath, []byte(contents))
if err != nil {
return err
}
}
return nil
}

View File

@ -544,9 +544,11 @@ func TestKeyConfigValidate(t *testing.T) {
RSAModLength: 2048,
},
Outputs: struct {
PublicKeyPath string `yaml:"public-key-path"`
PublicKeyPath string `yaml:"public-key-path"`
PKCS11ConfigPath string `yaml:"pkcs11-config-path"`
}{
PublicKeyPath: "path",
PublicKeyPath: "path",
PKCS11ConfigPath: "path.json",
},
},
},

View File

@ -48,3 +48,8 @@ func (ca *MockCA) IssueCertificateForPrecertificate(ctx context.Context, req *ca
func (ca *MockCA) GenerateOCSP(ctx context.Context, req *capb.GenerateOCSPRequest, _ ...grpc.CallOption) (*capb.OCSPResponse, error) {
return nil, nil
}
// GenerateCRL is a mock
func (ca *MockCA) GenerateCRL(ctx context.Context, opts ...grpc.CallOption) (capb.CertificateAuthority_GenerateCRLClient, error) {
return nil, nil
}

View File

@ -9,3 +9,4 @@ key:
ecdsa-curve: P-384
outputs:
public-key-path: /hierarchy/intermediate-signing-pub-ecdsa.pem
pkcs11-config-path: /hierarchy/intermediate-signing-key-ecdsa.pkcs11.json

View File

@ -9,3 +9,4 @@ key:
rsa-mod-length: 2048
outputs:
public-key-path: /hierarchy/intermediate-signing-pub-rsa.pem
pkcs11-config-path: /hierarchy/intermediate-signing-key-rsa.pkcs11.json

View File

@ -24,6 +24,14 @@
"orphan-finder.boulder"
]
},
"grpcCRLGenerator": {
"maxConnectionAge": "30s",
"address": ":9106",
"clientNames": [
"health-checker.boulder",
"crl-updater.boulder"
]
},
"saService": {
"serverAddress": "sa.boulder:9095",
"timeout": "15s"
@ -59,11 +67,22 @@
"ocspURL": "http://127.0.0.1:4002/",
"crlURL": "http://example.com/crl",
"location": {
"configFile": "test/test-ca.key-pkcs11.json",
"configFile": "/hierarchy/intermediate-signing-key-rsa.pkcs11.json",
"certFile": "/hierarchy/intermediate-cert-rsa-a.pem",
"numSessions": 2
}
},
{
"useForRSALeaves": false,
"useForECDSALeaves": true,
"issuerURL": "http://127.0.0.1:4001/aia/issuer/5214744660557630",
"ocspURL": "http://127.0.0.1:4002/",
"location": {
"configFile": "/hierarchy/intermediate-signing-key-ecdsa.pkcs11.json",
"certFile": "/hierarchy/intermediate-cert-ecdsa-a.pem",
"numSessions": 2
}
},
{
"useForRSALeaves": false,
"useForECDSALeaves": false,
@ -71,7 +90,7 @@
"ocspURL": "http://127.0.0.1:4002/",
"crlURL": "http://example.com/crl",
"location": {
"configFile": "test/test-ca.key-pkcs11.json",
"configFile": "/hierarchy/intermediate-signing-key-rsa.pkcs11.json",
"certFile": "/hierarchy/intermediate-cert-rsa-b.pem",
"numSessions": 2
}
@ -84,6 +103,7 @@
"serialPrefix": 255,
"maxNames": 100,
"lifespanOCSP": "96h",
"lifespanCRL": "216h",
"goodkey": {
"weakKeyFile": "test/example-weak-keys.json",
"blockedKeyFile": "test/example-blocked-keys.yaml",

View File

@ -24,6 +24,14 @@
"orphan-finder.boulder"
]
},
"grpcCRLGenerator": {
"maxConnectionAge": "30s",
"address": ":9106",
"clientNames": [
"health-checker.boulder",
"crl-updater.boulder"
]
},
"saService": {
"serverAddress": "sa.boulder:9095",
"timeout": "15s"
@ -59,11 +67,22 @@
"ocspURL": "http://127.0.0.1:4002/",
"crlURL": "http://example.com/crl",
"location": {
"configFile": "test/test-ca.key-pkcs11.json",
"configFile": "/hierarchy/intermediate-signing-key-rsa.pkcs11.json",
"certFile": "/hierarchy/intermediate-cert-rsa-a.pem",
"numSessions": 2
}
},
{
"useForRSALeaves": false,
"useForECDSALeaves": true,
"issuerURL": "http://127.0.0.1:4001/aia/issuer/5214744660557630",
"ocspURL": "http://127.0.0.1:4002/",
"location": {
"configFile": "/hierarchy/intermediate-signing-key-ecdsa.pkcs11.json",
"certFile": "/hierarchy/intermediate-cert-ecdsa-a.pem",
"numSessions": 2
}
},
{
"useForRSALeaves": false,
"useForECDSALeaves": false,
@ -71,7 +90,7 @@
"ocspURL": "http://127.0.0.1:4002/",
"crlURL": "http://example.com/crl",
"location": {
"configFile": "test/test-ca.key-pkcs11.json",
"configFile": "/hierarchy/intermediate-signing-key-rsa.pkcs11.json",
"certFile": "/hierarchy/intermediate-cert-rsa-b.pem",
"numSessions": 2
}
@ -84,6 +103,7 @@
"serialPrefix": 255,
"maxNames": 100,
"lifespanOCSP": "96h",
"lifespanCRL": "216h",
"goodkey": {
"weakKeyFile": "test/example-weak-keys.json",
"blockedKeyFile": "test/example-blocked-keys.yaml",

View File

@ -64,11 +64,11 @@ SERVICES = (
('sd-test-srv', 'boulder-remoteva-a', 'boulder-remoteva-b')),
Service('boulder-ca-a',
8001, 'ca1.boulder:9093',
('./bin/boulder-ca', '--config', os.path.join(config_dir, 'ca-a.json'), '--ca-addr', 'ca1.boulder:9093', '--ocsp-addr', 'ca1.boulder:9096', '--debug-addr', ':8001'),
('./bin/boulder-ca', '--config', os.path.join(config_dir, 'ca-a.json'), '--ca-addr', 'ca1.boulder:9093', '--ocsp-addr', 'ca1.boulder:9096', '--crl-addr', 'ca1.boulder:9196', '--debug-addr', ':8001'),
('sd-test-srv', 'boulder-sa-1', 'boulder-sa-2')),
Service('boulder-ca-b',
8101, 'ca2.boulder:9093',
('./bin/boulder-ca', '--config', os.path.join(config_dir, 'ca-b.json'), '--ca-addr', 'ca2.boulder:9093', '--ocsp-addr', 'ca2.boulder:9096', '--debug-addr', ':8101'),
('./bin/boulder-ca', '--config', os.path.join(config_dir, 'ca-b.json'), '--ca-addr', 'ca2.boulder:9093', '--ocsp-addr', 'ca2.boulder:9096', '--crl-addr', 'ca2.boulder:9196', '--debug-addr', ':8101'),
('sd-test-srv', 'boulder-sa-1', 'boulder-sa-2')),
Service('akamai-test-srv',
6789, None,