boulder/ca/crl_test.go

272 lines
7.8 KiB
Go

package ca
import (
"crypto/x509"
"fmt"
"io"
"testing"
"google.golang.org/grpc"
"google.golang.org/protobuf/types/known/timestamppb"
capb "github.com/letsencrypt/boulder/ca/proto"
corepb "github.com/letsencrypt/boulder/core/proto"
"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) {
t.Parallel()
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})
}()
now := testCtx.fc.Now()
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Metadata{
Metadata: &capb.CRLMetadata{
IssuerNameID: 1,
ThisUpdate: timestamppb.New(now),
ShardIdx: 1,
},
},
}
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].NameID()),
ThisUpdate: timestamppb.New(now),
ShardIdx: 1,
},
},
}
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Metadata{
Metadata: &capb.CRLMetadata{
IssuerNameID: int64(testCtx.boulderIssuers[0].NameID()),
ThisUpdate: timestamppb.New(now),
ShardIdx: 1,
},
},
}
close(ins)
err = <-errs
fmt.Println("done waiting for error")
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: timestamppb.New(now),
},
},
}
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: nil,
},
},
}
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].NameID()),
ThisUpdate: timestamppb.New(now),
ShardIdx: 1,
},
},
}
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.ParseRevocationList(crlBytes)
test.AssertNotError(t, err, "should be able to parse empty CRL")
test.AssertEquals(t, len(crl.RevokedCertificateEntries), 0)
err = crl.CheckSignatureFrom(testCtx.boulderIssuers[0].Cert.Certificate)
test.AssertEquals(t, crl.ThisUpdate, now)
test.AssertEquals(t, crl.ThisUpdate, timestamppb.New(now).AsTime())
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].NameID()),
ThisUpdate: timestamppb.New(now),
ShardIdx: 1,
},
},
}
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Entry{
Entry: &corepb.CRLEntry{
Serial: "000000000000000000000000000000000000",
RevokedAt: timestamppb.New(now),
// Reason 0, Unspecified, is omitted.
},
},
}
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Entry{
Entry: &corepb.CRLEntry{
Serial: "111111111111111111111111111111111111",
Reason: 1, // keyCompromise
RevokedAt: timestamppb.New(now),
},
},
}
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Entry{
Entry: &corepb.CRLEntry{
Serial: "444444444444444444444444444444444444",
Reason: 4, // superseded
RevokedAt: timestamppb.New(now),
},
},
}
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Entry{
Entry: &corepb.CRLEntry{
Serial: "555555555555555555555555555555555555",
Reason: 5, // cessationOfOperation
RevokedAt: timestamppb.New(now),
},
},
}
ins <- &capb.GenerateCRLRequest{
Payload: &capb.GenerateCRLRequest_Entry{
Entry: &corepb.CRLEntry{
Serial: "999999999999999999999999999999999999",
Reason: 9, // privilegeWithdrawn
RevokedAt: timestamppb.New(now),
},
},
}
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.ParseRevocationList(crlBytes)
test.AssertNotError(t, err, "should be able to parse empty CRL")
test.AssertEquals(t, len(crl.RevokedCertificateEntries), 5)
err = crl.CheckSignatureFrom(testCtx.boulderIssuers[0].Cert.Certificate)
test.AssertNotError(t, err, "CRL signature should validate")
}