github.com/letsencrypt/boulder@v0.20251208.0/ca/crl_test.go (about)

     1  package ca
     2  
     3  import (
     4  	"crypto/x509"
     5  	"fmt"
     6  	"io"
     7  	"testing"
     8  	"time"
     9  
    10  	"google.golang.org/grpc"
    11  	"google.golang.org/protobuf/types/known/timestamppb"
    12  
    13  	capb "github.com/letsencrypt/boulder/ca/proto"
    14  	"github.com/letsencrypt/boulder/config"
    15  	corepb "github.com/letsencrypt/boulder/core/proto"
    16  	"github.com/letsencrypt/boulder/issuance"
    17  	"github.com/letsencrypt/boulder/test"
    18  )
    19  
    20  type mockGenerateCRLBidiStream struct {
    21  	grpc.ServerStream
    22  	input  <-chan *capb.GenerateCRLRequest
    23  	output chan<- *capb.GenerateCRLResponse
    24  }
    25  
    26  func (s mockGenerateCRLBidiStream) Recv() (*capb.GenerateCRLRequest, error) {
    27  	next, ok := <-s.input
    28  	if !ok {
    29  		return nil, io.EOF
    30  	}
    31  	return next, nil
    32  }
    33  
    34  func (s mockGenerateCRLBidiStream) Send(entry *capb.GenerateCRLResponse) error {
    35  	s.output <- entry
    36  	return nil
    37  }
    38  
    39  func TestGenerateCRL(t *testing.T) {
    40  	t.Parallel()
    41  	cargs := newCAArgs(t)
    42  	crli, err := NewCRLImpl(
    43  		cargs.issuers,
    44  		issuance.CRLProfileConfig{
    45  			ValidityInterval: config.Duration{Duration: 216 * time.Hour},
    46  			MaxBackdate:      config.Duration{Duration: time.Hour},
    47  		},
    48  		100,
    49  		cargs.logger,
    50  		cargs.metrics,
    51  	)
    52  	test.AssertNotError(t, err, "Failed to create crl impl")
    53  	errs := make(chan error, 1)
    54  
    55  	// Test that we get an error when no metadata is sent.
    56  	ins := make(chan *capb.GenerateCRLRequest)
    57  	go func() {
    58  		errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: nil})
    59  	}()
    60  	close(ins)
    61  	err = <-errs
    62  	test.AssertError(t, err, "can't generate CRL with no metadata")
    63  	test.AssertContains(t, err.Error(), "no crl metadata received")
    64  
    65  	// Test that we get an error when incomplete metadata is sent.
    66  	ins = make(chan *capb.GenerateCRLRequest)
    67  	go func() {
    68  		errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: nil})
    69  	}()
    70  	ins <- &capb.GenerateCRLRequest{
    71  		Payload: &capb.GenerateCRLRequest_Metadata{
    72  			Metadata: &capb.CRLMetadata{},
    73  		},
    74  	}
    75  	close(ins)
    76  	err = <-errs
    77  	test.AssertError(t, err, "can't generate CRL with incomplete metadata")
    78  	test.AssertContains(t, err.Error(), "got incomplete metadata message")
    79  
    80  	// Test that we get an error when unrecognized metadata is sent.
    81  	ins = make(chan *capb.GenerateCRLRequest)
    82  	go func() {
    83  		errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: nil})
    84  	}()
    85  	now := cargs.clk.Now()
    86  	ins <- &capb.GenerateCRLRequest{
    87  		Payload: &capb.GenerateCRLRequest_Metadata{
    88  			Metadata: &capb.CRLMetadata{
    89  				IssuerNameID: 1,
    90  				ThisUpdate:   timestamppb.New(now),
    91  				ShardIdx:     1,
    92  			},
    93  		},
    94  	}
    95  	close(ins)
    96  	err = <-errs
    97  	test.AssertError(t, err, "can't generate CRL with bad metadata")
    98  	test.AssertContains(t, err.Error(), "got unrecognized IssuerNameID")
    99  
   100  	// Test that we get an error when two metadata are sent.
   101  	ins = make(chan *capb.GenerateCRLRequest)
   102  	go func() {
   103  		errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: nil})
   104  	}()
   105  	ins <- &capb.GenerateCRLRequest{
   106  		Payload: &capb.GenerateCRLRequest_Metadata{
   107  			Metadata: &capb.CRLMetadata{
   108  				IssuerNameID: int64(cargs.issuers[0].NameID()),
   109  				ThisUpdate:   timestamppb.New(now),
   110  				ShardIdx:     1,
   111  			},
   112  		},
   113  	}
   114  	ins <- &capb.GenerateCRLRequest{
   115  		Payload: &capb.GenerateCRLRequest_Metadata{
   116  			Metadata: &capb.CRLMetadata{
   117  				IssuerNameID: int64(cargs.issuers[0].NameID()),
   118  				ThisUpdate:   timestamppb.New(now),
   119  				ShardIdx:     1,
   120  			},
   121  		},
   122  	}
   123  	close(ins)
   124  	err = <-errs
   125  	fmt.Println("done waiting for error")
   126  	test.AssertError(t, err, "can't generate CRL with duplicate metadata")
   127  	test.AssertContains(t, err.Error(), "got more than one metadata message")
   128  
   129  	// Test that we get an error when an entry has a bad serial.
   130  	ins = make(chan *capb.GenerateCRLRequest)
   131  	go func() {
   132  		errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: nil})
   133  	}()
   134  	ins <- &capb.GenerateCRLRequest{
   135  		Payload: &capb.GenerateCRLRequest_Entry{
   136  			Entry: &corepb.CRLEntry{
   137  				Serial:    "123",
   138  				Reason:    1,
   139  				RevokedAt: timestamppb.New(now),
   140  			},
   141  		},
   142  	}
   143  	close(ins)
   144  	err = <-errs
   145  	test.AssertError(t, err, "can't generate CRL with bad serials")
   146  	test.AssertContains(t, err.Error(), "invalid serial number")
   147  
   148  	// Test that we get an error when an entry has a bad revocation time.
   149  	ins = make(chan *capb.GenerateCRLRequest)
   150  	go func() {
   151  		errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: nil})
   152  	}()
   153  
   154  	ins <- &capb.GenerateCRLRequest{
   155  		Payload: &capb.GenerateCRLRequest_Entry{
   156  			Entry: &corepb.CRLEntry{
   157  				Serial:    "deadbeefdeadbeefdeadbeefdeadbeefdead",
   158  				Reason:    1,
   159  				RevokedAt: nil,
   160  			},
   161  		},
   162  	}
   163  	close(ins)
   164  	err = <-errs
   165  	test.AssertError(t, err, "can't generate CRL with bad serials")
   166  	test.AssertContains(t, err.Error(), "got empty or zero revocation timestamp")
   167  
   168  	// Test that generating an empty CRL works.
   169  	ins = make(chan *capb.GenerateCRLRequest)
   170  	outs := make(chan *capb.GenerateCRLResponse)
   171  	go func() {
   172  		errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: outs})
   173  		close(outs)
   174  	}()
   175  	crlBytes := make([]byte, 0)
   176  	done := make(chan struct{})
   177  	go func() {
   178  		for resp := range outs {
   179  			crlBytes = append(crlBytes, resp.Chunk...)
   180  		}
   181  		close(done)
   182  	}()
   183  	ins <- &capb.GenerateCRLRequest{
   184  		Payload: &capb.GenerateCRLRequest_Metadata{
   185  			Metadata: &capb.CRLMetadata{
   186  				IssuerNameID: int64(cargs.issuers[0].NameID()),
   187  				ThisUpdate:   timestamppb.New(now),
   188  				ShardIdx:     1,
   189  			},
   190  		},
   191  	}
   192  	close(ins)
   193  	err = <-errs
   194  	<-done
   195  	test.AssertNotError(t, err, "generating empty CRL should work")
   196  	test.Assert(t, len(crlBytes) > 0, "should have gotten some CRL bytes")
   197  	crl, err := x509.ParseRevocationList(crlBytes)
   198  	test.AssertNotError(t, err, "should be able to parse empty CRL")
   199  	test.AssertEquals(t, len(crl.RevokedCertificateEntries), 0)
   200  	err = crl.CheckSignatureFrom(cargs.issuers[0].Cert.Certificate)
   201  	test.AssertEquals(t, crl.ThisUpdate, now)
   202  	test.AssertEquals(t, crl.ThisUpdate, timestamppb.New(now).AsTime())
   203  	test.AssertNotError(t, err, "CRL signature should validate")
   204  
   205  	// Test that generating a CRL with some entries works.
   206  	ins = make(chan *capb.GenerateCRLRequest)
   207  	outs = make(chan *capb.GenerateCRLResponse)
   208  	go func() {
   209  		errs <- crli.GenerateCRL(mockGenerateCRLBidiStream{input: ins, output: outs})
   210  		close(outs)
   211  	}()
   212  	crlBytes = make([]byte, 0)
   213  	done = make(chan struct{})
   214  	go func() {
   215  		for resp := range outs {
   216  			crlBytes = append(crlBytes, resp.Chunk...)
   217  		}
   218  		close(done)
   219  	}()
   220  	ins <- &capb.GenerateCRLRequest{
   221  		Payload: &capb.GenerateCRLRequest_Metadata{
   222  			Metadata: &capb.CRLMetadata{
   223  				IssuerNameID: int64(cargs.issuers[0].NameID()),
   224  				ThisUpdate:   timestamppb.New(now),
   225  				ShardIdx:     1,
   226  			},
   227  		},
   228  	}
   229  	ins <- &capb.GenerateCRLRequest{
   230  		Payload: &capb.GenerateCRLRequest_Entry{
   231  			Entry: &corepb.CRLEntry{
   232  				Serial:    "000000000000000000000000000000000000",
   233  				RevokedAt: timestamppb.New(now),
   234  				// Reason 0, Unspecified, is omitted.
   235  			},
   236  		},
   237  	}
   238  	ins <- &capb.GenerateCRLRequest{
   239  		Payload: &capb.GenerateCRLRequest_Entry{
   240  			Entry: &corepb.CRLEntry{
   241  				Serial:    "111111111111111111111111111111111111",
   242  				Reason:    1, // keyCompromise
   243  				RevokedAt: timestamppb.New(now),
   244  			},
   245  		},
   246  	}
   247  	ins <- &capb.GenerateCRLRequest{
   248  		Payload: &capb.GenerateCRLRequest_Entry{
   249  			Entry: &corepb.CRLEntry{
   250  				Serial:    "444444444444444444444444444444444444",
   251  				Reason:    4, // superseded
   252  				RevokedAt: timestamppb.New(now),
   253  			},
   254  		},
   255  	}
   256  	ins <- &capb.GenerateCRLRequest{
   257  		Payload: &capb.GenerateCRLRequest_Entry{
   258  			Entry: &corepb.CRLEntry{
   259  				Serial:    "555555555555555555555555555555555555",
   260  				Reason:    5, // cessationOfOperation
   261  				RevokedAt: timestamppb.New(now),
   262  			},
   263  		},
   264  	}
   265  	ins <- &capb.GenerateCRLRequest{
   266  		Payload: &capb.GenerateCRLRequest_Entry{
   267  			Entry: &corepb.CRLEntry{
   268  				Serial:    "999999999999999999999999999999999999",
   269  				Reason:    9, // privilegeWithdrawn
   270  				RevokedAt: timestamppb.New(now),
   271  			},
   272  		},
   273  	}
   274  	close(ins)
   275  	err = <-errs
   276  	<-done
   277  	test.AssertNotError(t, err, "generating empty CRL should work")
   278  	test.Assert(t, len(crlBytes) > 0, "should have gotten some CRL bytes")
   279  	crl, err = x509.ParseRevocationList(crlBytes)
   280  	test.AssertNotError(t, err, "should be able to parse empty CRL")
   281  	test.AssertEquals(t, len(crl.RevokedCertificateEntries), 5)
   282  	err = crl.CheckSignatureFrom(cargs.issuers[0].Cert.Certificate)
   283  	test.AssertNotError(t, err, "CRL signature should validate")
   284  }