github.com/letsencrypt/boulder@v0.20251208.0/cmd/cert-checker/main_test.go (about) 1 package notmain 2 3 import ( 4 "context" 5 "crypto" 6 "crypto/ecdsa" 7 "crypto/elliptic" 8 "crypto/rand" 9 "crypto/rsa" 10 "crypto/x509" 11 "crypto/x509/pkix" 12 "database/sql" 13 "encoding/asn1" 14 "encoding/pem" 15 "errors" 16 "log" 17 "math/big" 18 mrand "math/rand/v2" 19 "os" 20 "slices" 21 "strings" 22 "sync" 23 "testing" 24 "time" 25 26 "github.com/jmhodges/clock" 27 "google.golang.org/protobuf/types/known/timestamppb" 28 29 "github.com/letsencrypt/boulder/core" 30 corepb "github.com/letsencrypt/boulder/core/proto" 31 "github.com/letsencrypt/boulder/ctpolicy/loglist" 32 "github.com/letsencrypt/boulder/goodkey" 33 "github.com/letsencrypt/boulder/goodkey/sagoodkey" 34 "github.com/letsencrypt/boulder/identifier" 35 "github.com/letsencrypt/boulder/linter" 36 blog "github.com/letsencrypt/boulder/log" 37 "github.com/letsencrypt/boulder/metrics" 38 "github.com/letsencrypt/boulder/policy" 39 "github.com/letsencrypt/boulder/sa" 40 sapb "github.com/letsencrypt/boulder/sa/proto" 41 "github.com/letsencrypt/boulder/sa/satest" 42 "github.com/letsencrypt/boulder/test" 43 isa "github.com/letsencrypt/boulder/test/inmem/sa" 44 "github.com/letsencrypt/boulder/test/vars" 45 ) 46 47 var ( 48 testValidityDuration = 24 * 90 * time.Hour 49 testValidityDurations = map[time.Duration]bool{testValidityDuration: true} 50 pa *policy.AuthorityImpl 51 kp goodkey.KeyPolicy 52 ) 53 54 func init() { 55 var err error 56 pa, err = policy.New( 57 map[identifier.IdentifierType]bool{identifier.TypeDNS: true, identifier.TypeIP: true}, 58 map[core.AcmeChallenge]bool{}, 59 blog.NewMock()) 60 if err != nil { 61 log.Fatal(err) 62 } 63 err = pa.LoadIdentPolicyFile("../../test/ident-policy.yaml") 64 if err != nil { 65 log.Fatal(err) 66 } 67 kp, err = sagoodkey.NewPolicy(nil, nil) 68 if err != nil { 69 log.Fatal(err) 70 } 71 } 72 73 func BenchmarkCheckCert(b *testing.B) { 74 checker := newChecker(nil, clock.New(), pa, kp, time.Hour, testValidityDurations, nil, blog.NewMock()) 75 testKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 76 expiry := time.Now().AddDate(0, 0, 1) 77 serial := big.NewInt(1337) 78 rawCert := x509.Certificate{ 79 Subject: pkix.Name{ 80 CommonName: "example.com", 81 }, 82 NotAfter: expiry, 83 DNSNames: []string{"example-a.com"}, 84 SerialNumber: serial, 85 } 86 certDer, _ := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey) 87 cert := &corepb.Certificate{ 88 Serial: core.SerialToString(serial), 89 Digest: core.Fingerprint256(certDer), 90 Der: certDer, 91 Issued: timestamppb.New(time.Now()), 92 Expires: timestamppb.New(expiry), 93 } 94 95 for b.Loop() { 96 checker.checkCert(context.Background(), cert) 97 } 98 } 99 100 func TestCheckWildcardCert(t *testing.T) { 101 saDbMap, err := sa.DBMapForTest(vars.DBConnSA) 102 test.AssertNotError(t, err, "Couldn't connect to database") 103 saCleanup := test.ResetBoulderTestDatabase(t) 104 defer func() { 105 saCleanup() 106 }() 107 108 testKey, _ := rsa.GenerateKey(rand.Reader, 2048) 109 fc := clock.NewFake() 110 checker := newChecker(saDbMap, fc, pa, kp, time.Hour, testValidityDurations, nil, blog.NewMock()) 111 issued := checker.clock.Now().Add(-time.Minute) 112 goodExpiry := issued.Add(testValidityDuration - time.Second) 113 serial := big.NewInt(1337) 114 115 wildcardCert := x509.Certificate{ 116 Subject: pkix.Name{ 117 CommonName: "*.example.com", 118 }, 119 NotBefore: issued, 120 NotAfter: goodExpiry, 121 DNSNames: []string{"*.example.com"}, 122 SerialNumber: serial, 123 BasicConstraintsValid: true, 124 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, 125 KeyUsage: x509.KeyUsageDigitalSignature, 126 OCSPServer: []string{"http://example.com/ocsp"}, 127 IssuingCertificateURL: []string{"http://example.com/cert"}, 128 } 129 wildcardCertDer, err := x509.CreateCertificate(rand.Reader, &wildcardCert, &wildcardCert, &testKey.PublicKey, testKey) 130 test.AssertNotError(t, err, "Couldn't create certificate") 131 parsed, err := x509.ParseCertificate(wildcardCertDer) 132 test.AssertNotError(t, err, "Couldn't parse created certificate") 133 cert := &corepb.Certificate{ 134 Serial: core.SerialToString(serial), 135 Digest: core.Fingerprint256(wildcardCertDer), 136 Expires: timestamppb.New(parsed.NotAfter), 137 Issued: timestamppb.New(parsed.NotBefore), 138 Der: wildcardCertDer, 139 } 140 _, problems := checker.checkCert(context.Background(), cert) 141 for _, p := range problems { 142 t.Error(p) 143 } 144 } 145 146 func TestCheckCertReturnsSANs(t *testing.T) { 147 saDbMap, err := sa.DBMapForTest(vars.DBConnSA) 148 test.AssertNotError(t, err, "Couldn't connect to database") 149 saCleanup := test.ResetBoulderTestDatabase(t) 150 defer func() { 151 saCleanup() 152 }() 153 checker := newChecker(saDbMap, clock.NewFake(), pa, kp, time.Hour, testValidityDurations, nil, blog.NewMock()) 154 155 certPEM, err := os.ReadFile("testdata/quite_invalid.pem") 156 if err != nil { 157 t.Fatal(err) 158 } 159 160 block, _ := pem.Decode(certPEM) 161 if block == nil { 162 t.Fatal("failed to parse cert PEM") 163 } 164 165 cert := &corepb.Certificate{ 166 Serial: "00000000000", 167 Digest: core.Fingerprint256(block.Bytes), 168 Expires: timestamppb.New(time.Now().Add(time.Hour)), 169 Issued: timestamppb.New(time.Now()), 170 Der: block.Bytes, 171 } 172 173 names, problems := checker.checkCert(context.Background(), cert) 174 if !slices.Equal(names, []string{"quite_invalid.com", "al--so--wr--ong.com", "127.0.0.1"}) { 175 t.Errorf("didn't get expected DNS names. other problems: %s", strings.Join(problems, "\n")) 176 } 177 } 178 179 type keyGen interface { 180 genKey() (crypto.Signer, error) 181 } 182 183 type ecP256Generator struct{} 184 185 func (*ecP256Generator) genKey() (crypto.Signer, error) { 186 return ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 187 } 188 189 type rsa2048Generator struct{} 190 191 func (*rsa2048Generator) genKey() (crypto.Signer, error) { 192 return rsa.GenerateKey(rand.Reader, 2048) 193 } 194 195 func TestCheckCert(t *testing.T) { 196 saDbMap, err := sa.DBMapForTest(vars.DBConnSA) 197 test.AssertNotError(t, err, "Couldn't connect to database") 198 saCleanup := test.ResetBoulderTestDatabase(t) 199 defer func() { 200 saCleanup() 201 }() 202 203 testCases := []struct { 204 name string 205 key keyGen 206 }{ 207 { 208 name: "RSA 2048 key", 209 key: &rsa2048Generator{}, 210 }, 211 { 212 name: "ECDSA P256 key", 213 key: &ecP256Generator{}, 214 }, 215 } 216 for _, tc := range testCases { 217 t.Run(tc.name, func(t *testing.T) { 218 testKey, _ := tc.key.genKey() 219 220 checker := newChecker(saDbMap, clock.NewFake(), pa, kp, time.Hour, testValidityDurations, nil, blog.NewMock()) 221 222 // Create a RFC 7633 OCSP Must Staple Extension. 223 // OID 1.3.6.1.5.5.7.1.24 224 ocspMustStaple := pkix.Extension{ 225 Id: asn1.ObjectIdentifier{1, 3, 6, 1, 5, 5, 7, 1, 24}, 226 Critical: false, 227 Value: []uint8{0x30, 0x3, 0x2, 0x1, 0x5}, 228 } 229 230 // Create a made up PKIX extension 231 imaginaryExtension := pkix.Extension{ 232 Id: asn1.ObjectIdentifier{1, 3, 3, 7}, 233 Critical: false, 234 Value: []uint8{0xC0, 0xFF, 0xEE}, 235 } 236 237 issued := checker.clock.Now().Add(-time.Minute) 238 goodExpiry := issued.Add(testValidityDuration - time.Second) 239 serial := big.NewInt(1337) 240 longName := "eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeexample.com" 241 rawCert := x509.Certificate{ 242 Subject: pkix.Name{ 243 CommonName: longName, 244 }, 245 NotBefore: issued, 246 NotAfter: goodExpiry.AddDate(0, 0, 1), // Period too long 247 DNSNames: []string{ 248 "example-a.com", 249 "foodnotbombs.mil", 250 // `dev-myqnapcloud.com` is included because it is an exact private 251 // entry on the public suffix list 252 "dev-myqnapcloud.com", 253 // don't include longName in the SANs, so the unique CN gets flagged 254 }, 255 SerialNumber: serial, 256 BasicConstraintsValid: false, 257 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageClientAuth}, 258 KeyUsage: x509.KeyUsageDigitalSignature, 259 OCSPServer: []string{"http://example.com/ocsp"}, 260 IssuingCertificateURL: []string{"http://example.com/cert"}, 261 ExtraExtensions: []pkix.Extension{ocspMustStaple, imaginaryExtension}, 262 } 263 brokenCertDer, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, testKey.Public(), testKey) 264 test.AssertNotError(t, err, "Couldn't create certificate") 265 // Problems 266 // Digest doesn't match 267 // Serial doesn't match 268 // Expiry doesn't match 269 // Issued doesn't match 270 cert := &corepb.Certificate{ 271 Serial: "8485f2687eba29ad455ae4e31c8679206fec", 272 Der: brokenCertDer, 273 Issued: timestamppb.New(issued.Add(12 * time.Hour)), 274 Expires: timestamppb.New(goodExpiry.AddDate(0, 0, 2)), // Expiration doesn't match 275 } 276 277 _, problems := checker.checkCert(context.Background(), cert) 278 279 problemsMap := map[string]int{ 280 "Stored digest doesn't match certificate digest": 1, 281 "Stored serial doesn't match certificate serial": 1, 282 "Stored expiration doesn't match certificate NotAfter": 1, 283 "Certificate doesn't have basic constraints set": 1, 284 "Certificate has unacceptable validity period": 1, 285 "Stored issuance date is outside of 6 hour window of certificate NotBefore": 1, 286 "Certificate has incorrect key usage extensions": 1, 287 "Certificate has common name >64 characters long (65)": 1, 288 "Certificate contains an unexpected extension: 1.3.3.7": 1, 289 "Certificate Common Name does not appear in Subject Alternative Names: \"eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeexample.com\" !< [example-a.com foodnotbombs.mil dev-myqnapcloud.com]": 1, 290 } 291 for _, p := range problems { 292 _, ok := problemsMap[p] 293 if !ok { 294 t.Errorf("Found unexpected problem '%s'.", p) 295 } 296 delete(problemsMap, p) 297 } 298 for k := range problemsMap { 299 t.Errorf("Expected problem but didn't find '%s' in problems: %q.", k, problems) 300 } 301 302 // Same settings as above, but the stored serial number in the DB is invalid. 303 cert.Serial = "not valid" 304 _, problems = checker.checkCert(context.Background(), cert) 305 foundInvalidSerialProblem := false 306 for _, p := range problems { 307 if p == "Stored serial is invalid" { 308 foundInvalidSerialProblem = true 309 } 310 } 311 test.Assert(t, foundInvalidSerialProblem, "Invalid certificate serial number in DB did not trigger problem.") 312 313 // Fix the problems 314 rawCert.Subject.CommonName = "example-a.com" 315 rawCert.DNSNames = []string{"example-a.com"} 316 rawCert.NotAfter = goodExpiry 317 rawCert.BasicConstraintsValid = true 318 rawCert.ExtraExtensions = []pkix.Extension{ocspMustStaple} 319 rawCert.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} 320 goodCertDer, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, testKey.Public(), testKey) 321 test.AssertNotError(t, err, "Couldn't create certificate") 322 parsed, err := x509.ParseCertificate(goodCertDer) 323 test.AssertNotError(t, err, "Couldn't parse created certificate") 324 cert.Serial = core.SerialToString(serial) 325 cert.Digest = core.Fingerprint256(goodCertDer) 326 cert.Der = goodCertDer 327 cert.Expires = timestamppb.New(parsed.NotAfter) 328 cert.Issued = timestamppb.New(parsed.NotBefore) 329 _, problems = checker.checkCert(context.Background(), cert) 330 test.AssertEquals(t, len(problems), 0) 331 }) 332 } 333 } 334 335 func TestGetAndProcessCerts(t *testing.T) { 336 saDbMap, err := sa.DBMapForTest(vars.DBConnSA) 337 test.AssertNotError(t, err, "Couldn't connect to database") 338 fc := clock.NewFake() 339 fc.Set(fc.Now().Add(time.Hour)) 340 341 checker := newChecker(saDbMap, fc, pa, kp, time.Hour, testValidityDurations, nil, blog.NewMock()) 342 sa, err := sa.NewSQLStorageAuthority(saDbMap, saDbMap, nil, 1, 0, fc, blog.NewMock(), metrics.NoopRegisterer) 343 test.AssertNotError(t, err, "Couldn't create SA to insert certificates") 344 saCleanUp := test.ResetBoulderTestDatabase(t) 345 defer func() { 346 saCleanUp() 347 }() 348 349 testKey, _ := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 350 // Problems 351 // Expiry period is too long 352 rawCert := x509.Certificate{ 353 Subject: pkix.Name{ 354 CommonName: "not-blacklisted.com", 355 }, 356 NotBefore: fc.Now(), 357 NotAfter: fc.Now().Add(999999 * time.Hour), 358 BasicConstraintsValid: true, 359 DNSNames: []string{"not-blacklisted.com"}, 360 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, 361 } 362 reg := satest.CreateWorkingRegistration(t, isa.SA{Impl: sa}) 363 test.AssertNotError(t, err, "Couldn't create registration") 364 for range 5 { 365 rawCert.SerialNumber = big.NewInt(mrand.Int64()) 366 certDER, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey) 367 test.AssertNotError(t, err, "Couldn't create certificate") 368 _, err = sa.AddCertificate(context.Background(), &sapb.AddCertificateRequest{ 369 Der: certDER, 370 RegID: reg.Id, 371 Issued: timestamppb.New(fc.Now()), 372 }) 373 test.AssertNotError(t, err, "Couldn't add certificate") 374 } 375 376 batchSize = 2 377 err = checker.getCerts(context.Background()) 378 test.AssertNotError(t, err, "Failed to retrieve certificates") 379 test.AssertEquals(t, len(checker.certs), 5) 380 wg := new(sync.WaitGroup) 381 wg.Add(1) 382 checker.processCerts(context.Background(), wg, false) 383 test.AssertEquals(t, checker.issuedReport.BadCerts, int64(5)) 384 test.AssertEquals(t, len(checker.issuedReport.Entries), 5) 385 } 386 387 // mismatchedCountDB is a certDB implementation for `getCerts` that returns one 388 // high value when asked how many rows there are, and then returns nothing when 389 // asked for the actual rows. 390 type mismatchedCountDB struct{} 391 392 // `getCerts` calls `SelectInt` first to determine how many rows there are 393 // matching the `getCertsCountQuery` criteria. For this mock we return 394 // a non-zero number 395 func (db mismatchedCountDB) SelectNullInt(_ context.Context, _ string, _ ...any) (sql.NullInt64, error) { 396 return sql.NullInt64{ 397 Int64: 99999, 398 Valid: true, 399 }, 400 nil 401 } 402 403 // `getCerts` then calls `Select` to retrieve the Certificate rows. We pull 404 // a dastardly switch-a-roo here and return an empty set 405 func (db mismatchedCountDB) Select(_ context.Context, output any, _ string, _ ...any) ([]any, error) { 406 return nil, nil 407 } 408 409 func (db mismatchedCountDB) SelectOne(_ context.Context, _ any, _ string, _ ...any) error { 410 return errors.New("unimplemented") 411 } 412 413 /* 414 * In Boulder #2004[0] we identified that there is a race in `getCerts` 415 * between the first call to `SelectOne` to identify how many rows there are, 416 * and the subsequent call to `Select` to get the actual rows in batches. This 417 * manifests in an index out of range panic where the cert checker thinks there 418 * are more rows than there are and indexes into an empty set of certificates to 419 * update the lastSerial field of the query `args`. This has been fixed by 420 * adding a len() check in the inner `getCerts` loop that processes the certs 421 * one batch at a time. 422 * 423 * TestGetCertsEmptyResults tests the fix remains in place by using a mock that 424 * exploits this corner case deliberately. The `mismatchedCountDB` mock (defined 425 * above) will return a high count for the `SelectOne` call, but an empty slice 426 * for the `Select` call. Without the fix in place this reliably produced the 427 * "index out of range" panic from #2004. With the fix in place the test passes. 428 * 429 * 0: https://github.com/letsencrypt/boulder/issues/2004 430 */ 431 func TestGetCertsEmptyResults(t *testing.T) { 432 saDbMap, err := sa.DBMapForTest(vars.DBConnSA) 433 test.AssertNotError(t, err, "Couldn't connect to database") 434 checker := newChecker(saDbMap, clock.NewFake(), pa, kp, time.Hour, testValidityDurations, nil, blog.NewMock()) 435 checker.dbMap = mismatchedCountDB{} 436 437 batchSize = 3 438 err = checker.getCerts(context.Background()) 439 test.AssertNotError(t, err, "Failed to retrieve certificates") 440 } 441 442 // emptyDB is a certDB object with methods used for testing that 'null' 443 // responses received from the database are handled properly. 444 type emptyDB struct { 445 certDB 446 } 447 448 // SelectNullInt is a method that returns a false sql.NullInt64 struct to 449 // mock a null DB response 450 func (db emptyDB) SelectNullInt(_ context.Context, _ string, _ ...any) (sql.NullInt64, error) { 451 return sql.NullInt64{Valid: false}, 452 nil 453 } 454 455 // TestGetCertsNullResults tests that a null response from the database will 456 // be handled properly. It uses the emptyDB above to mock the response 457 // expected if the DB finds no certificates to match the SELECT query and 458 // should return an error. 459 func TestGetCertsNullResults(t *testing.T) { 460 checker := newChecker(emptyDB{}, clock.NewFake(), pa, kp, time.Hour, testValidityDurations, nil, blog.NewMock()) 461 462 err := checker.getCerts(context.Background()) 463 test.AssertError(t, err, "Should have gotten error from empty DB") 464 if !strings.Contains(err.Error(), "no rows found for certificates issued between") { 465 t.Errorf("expected error to contain 'no rows found for certificates issued between', got '%s'", err.Error()) 466 } 467 } 468 469 // lateDB is a certDB object that helps with TestGetCertsLate. 470 // It pretends to contain a single cert issued at the given time. 471 type lateDB struct { 472 issuedTime time.Time 473 selectedACert bool 474 } 475 476 // SelectNullInt is a method that returns a false sql.NullInt64 struct to 477 // mock a null DB response 478 func (db *lateDB) SelectNullInt(_ context.Context, _ string, args ...any) (sql.NullInt64, error) { 479 args2 := args[0].(map[string]any) 480 begin := args2["begin"].(time.Time) 481 end := args2["end"].(time.Time) 482 if begin.Compare(db.issuedTime) < 0 && end.Compare(db.issuedTime) > 0 { 483 return sql.NullInt64{Int64: 23, Valid: true}, nil 484 } 485 return sql.NullInt64{Valid: false}, nil 486 } 487 488 func (db *lateDB) Select(_ context.Context, output any, _ string, args ...any) ([]any, error) { 489 db.selectedACert = true 490 // For expediency we respond with an empty list of certificates; the checker will treat this as if it's 491 // reached the end of the list of certificates to process. 492 return nil, nil 493 } 494 495 func (db *lateDB) SelectOne(_ context.Context, _ any, _ string, _ ...any) error { 496 return nil 497 } 498 499 // TestGetCertsLate checks for correct behavior when certificates exist only late in the provided window. 500 func TestGetCertsLate(t *testing.T) { 501 clk := clock.NewFake() 502 db := &lateDB{issuedTime: clk.Now().Add(-time.Hour)} 503 checkPeriod := 24 * time.Hour 504 checker := newChecker(db, clk, pa, kp, checkPeriod, testValidityDurations, nil, blog.NewMock()) 505 506 err := checker.getCerts(context.Background()) 507 test.AssertNotError(t, err, "getting certs") 508 509 if !db.selectedACert { 510 t.Errorf("checker never selected a certificate after getting a MIN(id)") 511 } 512 } 513 514 func TestSaveReport(t *testing.T) { 515 r := report{ 516 begin: time.Time{}, 517 end: time.Time{}, 518 GoodCerts: 2, 519 BadCerts: 1, 520 Entries: map[string]reportEntry{ 521 "020000000000004b475da49b91da5c17": { 522 Valid: true, 523 }, 524 "020000000000004d1613e581432cba7e": { 525 Valid: true, 526 }, 527 "020000000000004e402bc21035c6634a": { 528 Valid: false, 529 Problems: []string{"None really..."}, 530 }, 531 }, 532 } 533 534 err := r.dump() 535 test.AssertNotError(t, err, "Failed to dump results") 536 } 537 538 func TestIsForbiddenDomain(t *testing.T) { 539 // Note: These testcases are not an exhaustive representation of domains 540 // Boulder won't issue for, but are instead testing the defense-in-depth 541 // `isForbiddenDomain` function called *after* the PA has vetted the name 542 // against the complex identifier policy file. 543 testcases := []struct { 544 Name string 545 Expected bool 546 }{ 547 /* Expected to be forbidden test cases */ 548 // Whitespace only 549 {Name: "", Expected: true}, 550 {Name: " ", Expected: true}, 551 // Anything .local 552 {Name: "yokel.local", Expected: true}, 553 {Name: "off.on.remote.local", Expected: true}, 554 {Name: ".local", Expected: true}, 555 // Localhost is verboten 556 {Name: "localhost", Expected: true}, 557 // Anything .localhost 558 {Name: ".localhost", Expected: true}, 559 {Name: "local.localhost", Expected: true}, 560 {Name: "extremely.local.localhost", Expected: true}, 561 562 /* Expected to be allowed test cases */ 563 {Name: "ok.computer.com", Expected: false}, 564 {Name: "ok.millionaires", Expected: false}, 565 {Name: "ok.milly", Expected: false}, 566 {Name: "ok", Expected: false}, 567 {Name: "nearby.locals", Expected: false}, 568 {Name: "yocalhost", Expected: false}, 569 {Name: "jokes.yocalhost", Expected: false}, 570 } 571 572 for _, tc := range testcases { 573 result, _ := isForbiddenDomain(tc.Name) 574 test.AssertEquals(t, result, tc.Expected) 575 } 576 } 577 578 func TestIgnoredLint(t *testing.T) { 579 saDbMap, err := sa.DBMapForTest(vars.DBConnSA) 580 test.AssertNotError(t, err, "Couldn't connect to database") 581 saCleanup := test.ResetBoulderTestDatabase(t) 582 defer func() { 583 saCleanup() 584 }() 585 586 err = loglist.InitLintList("../../test/ct-test-srv/log_list.json") 587 test.AssertNotError(t, err, "failed to load ct log list") 588 testKey, _ := rsa.GenerateKey(rand.Reader, 2048) 589 checker := newChecker(saDbMap, clock.NewFake(), pa, kp, time.Hour, testValidityDurations, nil, blog.NewMock()) 590 serial := big.NewInt(1337) 591 592 x509OID, err := x509.OIDFromInts([]uint64{1, 2, 3}) 593 test.AssertNotError(t, err, "failed to create x509.OID") 594 595 template := &x509.Certificate{ 596 Subject: pkix.Name{ 597 CommonName: "CPU's Cool CA", 598 }, 599 SerialNumber: serial, 600 NotBefore: time.Now(), 601 NotAfter: time.Now().Add(testValidityDuration - time.Second), 602 KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign, 603 ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth}, 604 Policies: []x509.OID{x509OID}, 605 BasicConstraintsValid: true, 606 IsCA: true, 607 IssuingCertificateURL: []string{"http://aia.example.org"}, 608 SubjectKeyId: []byte("foobar"), 609 } 610 611 // Create a self-signed issuer certificate to use 612 issuerDer, err := x509.CreateCertificate(rand.Reader, template, template, testKey.Public(), testKey) 613 test.AssertNotError(t, err, "failed to create self-signed issuer cert") 614 issuerCert, err := x509.ParseCertificate(issuerDer) 615 test.AssertNotError(t, err, "failed to parse self-signed issuer cert") 616 617 // Reconfigure the template for an EE cert with a Subj. CN 618 serial = big.NewInt(1338) 619 template.SerialNumber = serial 620 template.Subject.CommonName = "zombo.com" 621 template.DNSNames = []string{"zombo.com"} 622 template.KeyUsage = x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment 623 template.ExtKeyUsage = []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth, x509.ExtKeyUsageClientAuth} 624 template.IsCA = false 625 626 subjectCertDer, err := x509.CreateCertificate(rand.Reader, template, issuerCert, testKey.Public(), testKey) 627 test.AssertNotError(t, err, "failed to create EE cert") 628 subjectCert, err := x509.ParseCertificate(subjectCertDer) 629 test.AssertNotError(t, err, "failed to parse EE cert") 630 631 cert := &corepb.Certificate{ 632 Serial: core.SerialToString(serial), 633 Der: subjectCertDer, 634 Digest: core.Fingerprint256(subjectCertDer), 635 Issued: timestamppb.New(subjectCert.NotBefore), 636 Expires: timestamppb.New(subjectCert.NotAfter), 637 } 638 639 // Without any ignored lints we expect several errors and warnings about SCTs, 640 // the common name, and the subject key identifier extension. 641 expectedProblems := []string{ 642 "zlint warn: w_subject_common_name_included", 643 "zlint warn: w_ext_subject_key_identifier_not_recommended_subscriber", 644 "zlint info: w_ct_sct_policy_count_unsatisfied Certificate had 0 embedded SCTs. Browser policy may require 2 for this certificate.", 645 "zlint error: e_scts_from_same_operator Certificate had too few embedded SCTs; browser policy requires 2.", 646 } 647 slices.Sort(expectedProblems) 648 649 // Check the certificate with a nil ignore map. This should return the 650 // expected zlint problems. 651 _, problems := checker.checkCert(context.Background(), cert) 652 slices.Sort(problems) 653 test.AssertDeepEquals(t, problems, expectedProblems) 654 655 // Check the certificate again with an ignore map that excludes the affected 656 // lints. This should return no problems. 657 lints, err := linter.NewRegistry([]string{ 658 "w_subject_common_name_included", 659 "w_ext_subject_key_identifier_not_recommended_subscriber", 660 "w_ct_sct_policy_count_unsatisfied", 661 "e_scts_from_same_operator", 662 }) 663 test.AssertNotError(t, err, "creating test lint registry") 664 checker.lints = lints 665 _, problems = checker.checkCert(context.Background(), cert) 666 test.AssertEquals(t, len(problems), 0) 667 } 668 669 func TestPrecertCorrespond(t *testing.T) { 670 checker := newChecker(nil, clock.New(), pa, kp, time.Hour, testValidityDurations, nil, blog.NewMock()) 671 checker.getPrecert = func(_ context.Context, _ string) ([]byte, error) { 672 return []byte("hello"), nil 673 } 674 testKey, _ := rsa.GenerateKey(rand.Reader, 2048) 675 expiry := time.Now().AddDate(0, 0, 1) 676 serial := big.NewInt(1337) 677 rawCert := x509.Certificate{ 678 Subject: pkix.Name{ 679 CommonName: "example.com", 680 }, 681 NotAfter: expiry, 682 DNSNames: []string{"example-a.com"}, 683 SerialNumber: serial, 684 } 685 certDer, _ := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey) 686 cert := &corepb.Certificate{ 687 Serial: core.SerialToString(serial), 688 Digest: core.Fingerprint256(certDer), 689 Der: certDer, 690 Issued: timestamppb.New(time.Now()), 691 Expires: timestamppb.New(expiry), 692 } 693 _, problems := checker.checkCert(context.Background(), cert) 694 if len(problems) == 0 { 695 t.Errorf("expected precert correspondence problem") 696 } 697 // Ensure that at least one of the problems was related to checking correspondence 698 for _, p := range problems { 699 if strings.Contains(p, "does not correspond to precert") { 700 return 701 } 702 } 703 t.Fatalf("expected precert correspondence problem, but got: %v", problems) 704 }