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 }