github.com/letsencrypt/boulder@v0.20251208.0/ca/ca_test.go (about) 1 package ca 2 3 import ( 4 "bytes" 5 "context" 6 "crypto/ecdsa" 7 "crypto/elliptic" 8 "crypto/rand" 9 "crypto/x509" 10 "crypto/x509/pkix" 11 "encoding/asn1" 12 "errors" 13 "fmt" 14 "math/big" 15 "os" 16 "strings" 17 "testing" 18 "time" 19 20 ct "github.com/google/certificate-transparency-go" 21 cttls "github.com/google/certificate-transparency-go/tls" 22 ctx509 "github.com/google/certificate-transparency-go/x509" 23 "github.com/jmhodges/clock" 24 "github.com/miekg/pkcs11" 25 "github.com/prometheus/client_golang/prometheus" 26 "google.golang.org/grpc" 27 "google.golang.org/protobuf/types/known/emptypb" 28 29 capb "github.com/letsencrypt/boulder/ca/proto" 30 "github.com/letsencrypt/boulder/config" 31 "github.com/letsencrypt/boulder/core" 32 corepb "github.com/letsencrypt/boulder/core/proto" 33 berrors "github.com/letsencrypt/boulder/errors" 34 "github.com/letsencrypt/boulder/features" 35 "github.com/letsencrypt/boulder/goodkey" 36 "github.com/letsencrypt/boulder/identifier" 37 "github.com/letsencrypt/boulder/issuance" 38 blog "github.com/letsencrypt/boulder/log" 39 "github.com/letsencrypt/boulder/must" 40 "github.com/letsencrypt/boulder/policy" 41 rapb "github.com/letsencrypt/boulder/ra/proto" 42 sapb "github.com/letsencrypt/boulder/sa/proto" 43 "github.com/letsencrypt/boulder/test" 44 ) 45 46 var ( 47 // * Random public key 48 // * CN = not-example.com 49 // * DNSNames = not-example.com, www.not-example.com 50 CNandSANCSR = mustRead("./testdata/cn_and_san.der.csr") 51 52 // CSR generated by Go: 53 // * Random public key 54 // * CN = not-example.com 55 // * Includes an extensionRequest attribute for a well-formed TLS Feature extension 56 MustStapleCSR = mustRead("./testdata/must_staple.der.csr") 57 58 // CSR generated by Go: 59 // * Random public key 60 // * CN = not-example.com 61 // * Includes an extensionRequest attribute for an unknown extension with an 62 // empty value. That extension's OID, 2.25.123456789, is on the UUID arc. 63 // It isn't a real randomly-generated UUID because Go represents the 64 // components of the OID as 32-bit integers, which aren't large enough to 65 // hold a real 128-bit UUID; this doesn't matter as far as what we're 66 // testing here is concerned. 67 UnsupportedExtensionCSR = mustRead("./testdata/unsupported_extension.der.csr") 68 69 // CSR generated by Go: 70 // * Random public key 71 // * CN = not-example.com 72 // * Includes an extensionRequest attribute for the CT poison extension 73 // with a valid NULL value. 74 CTPoisonExtensionCSR = mustRead("./testdata/ct_poison_extension.der.csr") 75 76 // CSR generated by Go: 77 // * Random public key 78 // * CN = not-example.com 79 // * Includes an extensionRequest attribute for the CT poison extension 80 // with an invalid empty value. 81 CTPoisonExtensionEmptyCSR = mustRead("./testdata/ct_poison_extension_empty.der.csr") 82 83 // CSR generated by Go: 84 // * Random ECDSA public key. 85 // * CN = [none] 86 // * DNSNames = example.com, example2.com 87 ECDSACSR = mustRead("./testdata/ecdsa.der.csr") 88 89 // OIDExtensionCTPoison is defined in RFC 6962 s3.1. 90 OIDExtensionCTPoison = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 3} 91 92 // OIDExtensionSCTList is defined in RFC 6962 s3.3. 93 OIDExtensionSCTList = asn1.ObjectIdentifier{1, 3, 6, 1, 4, 1, 11129, 2, 4, 2} 94 ) 95 96 func mustRead(path string) []byte { 97 return must.Do(os.ReadFile(path)) 98 } 99 100 // caArgs is a container for all of the arguments to 101 // NewCertificateAuthorityImpl. It exists so that tests can easily build a 102 // default certificateAuthorityImpl, but can also easily customize that object 103 // to exercise various behaviors. The expected usage flow is: 104 // 105 // cargs := newTestCA(t) 106 // cargs.foo = someOverride 107 // ca := cargs.make() 108 // 109 // Its fields should remain identical to the NewCertificateAuthorityImpl args. 110 type caArgs struct { 111 sa sapb.StorageAuthorityCertificateClient 112 sctService rapb.SCTProviderClient 113 pa core.PolicyAuthority 114 issuers []*issuance.Issuer 115 profiles map[string]*issuance.Profile 116 serialPrefix byte 117 maxNames int 118 keyPolicy goodkey.KeyPolicy 119 logger *blog.Mock 120 metrics *caMetrics 121 clk clock.FakeClock 122 } 123 124 // newCAArgs returns a caArgs populated with reasonable default values for testing. 125 func newCAArgs(t *testing.T) *caArgs { 126 features.Reset() 127 128 fc := clock.NewFake() 129 fc.Add(1 * time.Hour) 130 131 pa, err := policy.New(map[identifier.IdentifierType]bool{"dns": true}, nil, blog.NewMock()) 132 test.AssertNotError(t, err, "Couldn't create PA") 133 err = pa.LoadIdentPolicyFile("../test/ident-policy.yaml") 134 test.AssertNotError(t, err, "Couldn't set identifier policy") 135 136 legacy, err := issuance.NewProfile(issuance.ProfileConfig{ 137 MaxValidityPeriod: config.Duration{Duration: time.Hour * 24 * 90}, 138 MaxValidityBackdate: config.Duration{Duration: time.Hour}, 139 IgnoredLints: []string{"w_subject_common_name_included"}, 140 }) 141 test.AssertNotError(t, err, "Loading test profile") 142 modern, err := issuance.NewProfile(issuance.ProfileConfig{ 143 OmitCommonName: true, 144 OmitKeyEncipherment: true, 145 OmitClientAuth: true, 146 OmitSKID: true, 147 MaxValidityPeriod: config.Duration{Duration: time.Hour * 24 * 6}, 148 MaxValidityBackdate: config.Duration{Duration: time.Hour}, 149 IgnoredLints: []string{"w_ext_subject_key_identifier_missing_sub_cert"}, 150 }) 151 test.AssertNotError(t, err, "Loading test profile") 152 profiles := map[string]*issuance.Profile{ 153 "legacy": legacy, 154 "modern": modern, 155 } 156 157 issuers := make([]*issuance.Issuer, 4) 158 for i, name := range []string{"int-r3", "int-r4", "int-e1", "int-e2"} { 159 issuers[i], err = issuance.LoadIssuer(issuance.IssuerConfig{ 160 Active: true, 161 IssuerURL: fmt.Sprintf("http://not-example.com/i/%s", name), 162 CRLURLBase: fmt.Sprintf("http://not-example.com/c/%s/", name), 163 CRLShards: 10, 164 Location: issuance.IssuerLoc{ 165 File: fmt.Sprintf("../test/hierarchy/%s.key.pem", name), 166 CertFile: fmt.Sprintf("../test/hierarchy/%s.cert.pem", name), 167 }, 168 Profiles: []string{"legacy", "modern"}, 169 }, fc) 170 test.AssertNotError(t, err, "Couldn't load test issuer") 171 } 172 173 keyPolicy, err := goodkey.NewPolicy(nil, nil) 174 test.AssertNotError(t, err, "Failed to create test keypolicy") 175 176 signatureCount := prometheus.NewCounterVec( 177 prometheus.CounterOpts{ 178 Name: "signatures", 179 Help: "Number of signatures", 180 }, 181 []string{"purpose", "issuer"}) 182 signErrorCount := prometheus.NewCounterVec(prometheus.CounterOpts{ 183 Name: "signature_errors", 184 Help: "A counter of signature errors labelled by error type", 185 }, []string{"type"}) 186 lintErrorCount := prometheus.NewCounter( 187 prometheus.CounterOpts{ 188 Name: "lint_errors", 189 Help: "Number of issuances that were halted by linting errors", 190 }) 191 certificatesCount := prometheus.NewCounterVec( 192 prometheus.CounterOpts{ 193 Name: "certificates", 194 Help: "Number of certificates issued", 195 }, []string{"profile"}) 196 cametrics := &caMetrics{signatureCount, signErrorCount, lintErrorCount, certificatesCount} 197 198 return &caArgs{ 199 sa: &mockSA{}, 200 sctService: &mockSCTService{}, 201 pa: pa, 202 issuers: issuers, 203 profiles: profiles, 204 serialPrefix: 0x11, 205 maxNames: 2, 206 keyPolicy: keyPolicy, 207 logger: blog.NewMock(), 208 metrics: cametrics, 209 clk: fc, 210 } 211 } 212 213 // make passes all of the caArgs' fields to the NewCertificateAuthorityImpl 214 // constructor and returns the result. 215 func (c *caArgs) make() (*certificateAuthorityImpl, error) { 216 return NewCertificateAuthorityImpl( 217 c.sa, c.sctService, c.pa, c.issuers, c.profiles, c.serialPrefix, 218 c.maxNames, c.keyPolicy, c.logger, c.metrics, c.clk) 219 } 220 221 type mockSA struct{} 222 223 func (m *mockSA) AddSerial(ctx context.Context, req *sapb.AddSerialRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) { 224 return &emptypb.Empty{}, nil 225 } 226 227 func (m *mockSA) AddPrecertificate(ctx context.Context, req *sapb.AddCertificateRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) { 228 return &emptypb.Empty{}, nil 229 } 230 231 func (m *mockSA) AddCertificate(ctx context.Context, req *sapb.AddCertificateRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) { 232 return &emptypb.Empty{}, nil 233 } 234 235 func (m *mockSA) GetCertificate(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) { 236 return nil, berrors.NotFoundError("cannot find the cert") 237 } 238 239 func (m *mockSA) GetLintPrecertificate(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) { 240 return nil, berrors.NotFoundError("cannot find the precert") 241 } 242 243 type mockSCTService struct{} 244 245 func (m mockSCTService) GetSCTs(ctx context.Context, sctRequest *rapb.SCTRequest, _ ...grpc.CallOption) (*rapb.SCTResponse, error) { 246 sct := ct.SignedCertificateTimestamp{ 247 SCTVersion: 0, 248 Timestamp: 2020, 249 Signature: ct.DigitallySigned{ 250 Signature: []byte{0}, 251 }, 252 } 253 254 sctBytes, err := cttls.Marshal(sct) 255 if err != nil { 256 return nil, err 257 } 258 259 return &rapb.SCTResponse{SctDER: [][]byte{sctBytes}}, nil 260 } 261 262 func TestNewCertificateAuthorityImpl_BadSerialPrefix(t *testing.T) { 263 t.Parallel() 264 cargs := newCAArgs(t) 265 266 cargs.serialPrefix = 0x00 267 _, err := cargs.make() 268 if err == nil { 269 t.Errorf("NewCertificateAuthorityImpl(serialPrefix=0x00) succeeded, but want error") 270 } 271 272 cargs.serialPrefix = 0x80 273 _, err = cargs.make() 274 if err == nil { 275 t.Errorf("NewCertificateAuthorityImpl(serialPrefix=0x80) succeeded, but want error") 276 } 277 } 278 279 func TestNewCertificateAuthorityImpl_InsufficientIssuers(t *testing.T) { 280 t.Parallel() 281 cargs := newCAArgs(t) 282 origIssuers := cargs.issuers 283 284 for _, tc := range []struct { 285 name string 286 issuers []*issuance.Issuer 287 wantErr string 288 }{ 289 { 290 name: "no issuers", 291 issuers: nil, 292 wantErr: "at least one issuer", 293 }, 294 { 295 name: "ecdsa only", 296 issuers: origIssuers[2:], 297 wantErr: "no RSA issuers configured", 298 }, 299 { 300 name: "rsa only", 301 issuers: origIssuers[:2], 302 wantErr: "no ECDSA issuers configured", 303 }, 304 } { 305 t.Run(tc.name, func(t *testing.T) { 306 cargs.issuers = tc.issuers 307 _, err := cargs.make() 308 if err == nil { 309 t.Fatalf("NewCertificateAuthorityImpl(%s) succeeded, but want error", tc.name) 310 } 311 312 if !strings.Contains(err.Error(), tc.wantErr) { 313 t.Fatalf("NewCertificateAuthorityImpl(%s) = %q, but want %q", tc.name, err, tc.wantErr) 314 } 315 }) 316 } 317 } 318 319 func TestNewCertificateAuthorityImpl_InsufficientProfiles(t *testing.T) { 320 t.Parallel() 321 cargs := newCAArgs(t) 322 cargs.profiles = nil 323 324 _, err := cargs.make() 325 if err == nil { 326 t.Fatalf("NewCertificateAuthorityImpl(profiles=nil) succeeded, but want error") 327 } 328 329 wantErr := "at least one certificate profile" 330 if !strings.Contains(err.Error(), wantErr) { 331 t.Fatalf("NewCertificateAuthorityImpl(profiles=nil) = %q, but want %q", err, wantErr) 332 } 333 } 334 335 // recordingSA keeps track of the serial, precertificate, and certificate which 336 // are written to it. We use recordingSA only for the _HappyPath test because 337 // it's a pain to mitigate the data-races inherent in writing to it from many 338 // parallel subtests. 339 type recordingSA struct { 340 serial *sapb.AddSerialRequest 341 precertificate *sapb.AddCertificateRequest 342 certificate *sapb.AddCertificateRequest 343 } 344 345 func (m *recordingSA) AddSerial(ctx context.Context, req *sapb.AddSerialRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) { 346 m.serial = req 347 return &emptypb.Empty{}, nil 348 } 349 350 func (m *recordingSA) AddPrecertificate(ctx context.Context, req *sapb.AddCertificateRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) { 351 m.precertificate = req 352 return &emptypb.Empty{}, nil 353 } 354 355 func (m *recordingSA) AddCertificate(ctx context.Context, req *sapb.AddCertificateRequest, _ ...grpc.CallOption) (*emptypb.Empty, error) { 356 m.certificate = req 357 return &emptypb.Empty{}, nil 358 } 359 360 func (m *recordingSA) GetCertificate(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) { 361 return nil, berrors.NotFoundError("cannot find the cert") 362 } 363 364 func (m *recordingSA) GetLintPrecertificate(ctx context.Context, req *sapb.Serial, _ ...grpc.CallOption) (*corepb.Certificate, error) { 365 return nil, berrors.NotFoundError("cannot find the precert") 366 } 367 368 func findExtension(extensions []pkix.Extension, id asn1.ObjectIdentifier) *pkix.Extension { 369 for _, ext := range extensions { 370 if ext.Id.Equal(id) { 371 return &ext 372 } 373 } 374 return nil 375 } 376 377 // deserializeSCTList deserializes a list of SCTs. 378 // Forked from github.com/cloudflare/cfssl/helpers 379 func deserializeSCTList(sctListExtensionValue []byte) ([]ct.SignedCertificateTimestamp, error) { 380 var serializedSCTList []byte 381 _, err := asn1.Unmarshal(sctListExtensionValue, &serializedSCTList) 382 if err != nil { 383 return nil, err 384 } 385 386 var sctList ctx509.SignedCertificateTimestampList 387 rest, err := cttls.Unmarshal(serializedSCTList, &sctList) 388 if err != nil { 389 return nil, err 390 } 391 if len(rest) != 0 { 392 return nil, errors.New("serialized SCT list contained trailing garbage") 393 } 394 list := make([]ct.SignedCertificateTimestamp, len(sctList.SCTList)) 395 for i, serializedSCT := range sctList.SCTList { 396 var sct ct.SignedCertificateTimestamp 397 rest, err := cttls.Unmarshal(serializedSCT.Val, &sct) 398 if err != nil { 399 return nil, err 400 } 401 if len(rest) != 0 { 402 return nil, errors.New("serialized SCT contained trailing garbage") 403 } 404 list[i] = sct 405 } 406 return list, nil 407 } 408 409 func TestIssueCertificate_HappyPath(t *testing.T) { 410 for _, profile := range []string{"legacy", "modern"} { 411 for _, tc := range []struct { 412 name string 413 csr []byte 414 }{ 415 { 416 name: "RSA", 417 csr: CNandSANCSR, 418 }, 419 { 420 name: "ECDSA", 421 csr: ECDSACSR, 422 }, 423 { 424 name: "unrecognized extension", 425 csr: UnsupportedExtensionCSR, 426 }, 427 { 428 name: "poison extension", 429 csr: CTPoisonExtensionCSR, 430 }, 431 { 432 name: "malformed poison extension", 433 csr: CTPoisonExtensionEmptyCSR, 434 }, 435 { 436 // Rejection of CSRs that request Must-Staple happens in the RA. 437 name: "must staple extension", 438 csr: mustRead("./testdata/must_staple.der.csr"), 439 }, 440 } { 441 t.Run(tc.name+"/"+profile, func(t *testing.T) { 442 t.Parallel() 443 444 // Use our own CA for each of these parallel subtests, because we plan 445 // to inspect the serial, precert, and final cert stored in the mock SA. 446 // Also so we can assert that certain metrics have specific values. 447 cargs := newCAArgs(t) 448 sa := &recordingSA{} 449 cargs.sa = sa 450 ca, err := cargs.make() 451 if err != nil { 452 t.Fatalf("making test ca: %s", err) 453 } 454 455 res, err := ca.IssueCertificate(t.Context(), &capb.IssueCertificateRequest{ 456 RegistrationID: 1, OrderID: 1, 457 Csr: tc.csr, CertProfileName: profile, 458 }) 459 if err != nil { 460 t.Fatalf("IssueCertificate(%s) = %q, but want success", tc.name, err) 461 } 462 463 test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "precertificate", "status": "success"}, 1) 464 test.AssertMetricWithLabelsEquals(t, ca.metrics.signatureCount, prometheus.Labels{"purpose": "certificate", "status": "success"}, 1) 465 466 if sa.serial.RegID != 1 { 467 t.Errorf("want serial to be associated with acct %d, but got %d", 1, sa.serial.RegID) 468 } 469 470 storedPrecert, err := x509.ParseCertificate(sa.precertificate.Der) 471 if err != nil { 472 t.Fatalf("parsing precert: %s", err) 473 } 474 475 poisonExtension := findExtension(storedPrecert.Extensions, OIDExtensionCTPoison) 476 if poisonExtension == nil { 477 t.Fatal("failed to find ctpoison extension") 478 } 479 480 if !poisonExtension.Critical { 481 t.Error("precertificate ctpoison extension must be critical") 482 } 483 484 if !bytes.Equal(poisonExtension.Value, []byte{0x05, 0x00}) { // ASN.1 DER NULL 485 t.Errorf("precertificate poison extension has value %x, but want %x", poisonExtension.Value, []byte{0x05, 0x00}) 486 } 487 488 storedCert, err := x509.ParseCertificate(sa.certificate.Der) 489 if err != nil { 490 t.Fatalf("parsing cert: %s", err) 491 } 492 493 sctExtension := findExtension(storedCert.Extensions, OIDExtensionSCTList) 494 if sctExtension == nil { 495 t.Fatal("failed to find sctList extension") 496 } 497 498 if sctExtension.Critical { 499 t.Error("sctList extension must not be critical") 500 } 501 502 sctList, err := deserializeSCTList(sctExtension.Value) 503 if err != nil { 504 t.Fatalf("parsing sctList extension: %s", err) 505 } 506 507 if len(sctList) != 1 { 508 t.Errorf("got %d SCTs, but want 1", len(sctList)) 509 } 510 511 cert, err := x509.ParseCertificate(res.DER) 512 if err != nil { 513 t.Fatalf("parsing returned cert: %s", err) 514 } 515 516 if (sa.serial.Serial != core.SerialToString(storedPrecert.SerialNumber)) || 517 (sa.serial.Serial != core.SerialToString(storedCert.SerialNumber)) || 518 (sa.serial.Serial != core.SerialToString(cert.SerialNumber)) { 519 t.Errorf("expected all serials to match") 520 } 521 522 if !bytes.Equal(res.DER, sa.certificate.Der) { 523 t.Errorf("Expected stored and returned cert to be identical") 524 } 525 }) 526 } 527 } 528 } 529 530 func TestIssueCertificate_BadCSR(t *testing.T) { 531 t.Parallel() 532 533 ca, err := newCAArgs(t).make() 534 if err != nil { 535 t.Fatalf("making test ca: %s", err) 536 } 537 538 for _, tc := range []struct { 539 name string 540 csrPath string 541 }{ 542 { 543 name: "no names", 544 csrPath: "./testdata/no_names.der.csr", 545 }, 546 { 547 name: "too many names", 548 csrPath: "./testdata/too_many_names.der.csr", 549 }, 550 { 551 name: "short key", 552 csrPath: "./testdata/short_key.der.csr", 553 }, 554 { 555 name: "bad key algorithm", 556 csrPath: "./testdata/bad_algorithm.der.csr", 557 }, 558 { 559 name: "invalid signature", 560 csrPath: "./testdata/invalid_signature.der.csr", 561 }, 562 } { 563 t.Run(tc.name, func(t *testing.T) { 564 t.Parallel() 565 566 _, err := ca.IssueCertificate(t.Context(), &capb.IssueCertificateRequest{ 567 RegistrationID: 1, OrderID: 1, 568 Csr: mustRead(tc.csrPath), CertProfileName: "legacy", 569 }) 570 if err == nil { 571 t.Fatalf("IssueCertificate(%q) succeeded, but want error", tc.csrPath) 572 } 573 if !errors.Is(err, berrors.BadCSR) { 574 t.Fatalf("IssueCertificate(%q) = %T, but want %T", tc.csrPath, err, berrors.BadCSR) 575 } 576 }) 577 } 578 } 579 580 func TestIssueCertificate_ValidPastIssuer(t *testing.T) { 581 t.Parallel() 582 cargs := newCAArgs(t) 583 584 // Limit ourselves to only having one ECDSA issuer, just in case they have 585 // different notAfter dates. 586 cargs.issuers = cargs.issuers[:3] 587 588 // Jump to a time just moments before the test issuer expire. 589 future := cargs.issuers[2].Cert.Certificate.NotAfter.Add(-1 * time.Hour) 590 cargs.clk.Set(future) 591 592 ca, err := cargs.make() 593 if err != nil { 594 t.Fatalf("making test ca: %s", err) 595 } 596 597 _, err = ca.IssueCertificate(t.Context(), &capb.IssueCertificateRequest{ 598 RegistrationID: 1, OrderID: 1, 599 Csr: ECDSACSR, CertProfileName: "legacy", 600 }) 601 if err == nil { 602 t.Fatalf("IssueCertificate(notAfter > issuer.notAfter) succeeded, but want error") 603 } 604 if !errors.Is(err, berrors.InternalServer) { 605 t.Fatalf("IssueCertificate(notAfter > issuer.notAfter) = %T, but want %T", err, berrors.InternalServer) 606 } 607 } 608 609 func TestIssueCertificate_InvalidProfile(t *testing.T) { 610 t.Parallel() 611 612 ca, err := newCAArgs(t).make() 613 if err != nil { 614 t.Fatalf("making test ca: %s", err) 615 } 616 617 for _, tc := range []struct { 618 name string 619 profile string 620 wantErr string 621 }{ 622 { 623 name: "no profile", 624 profile: "", 625 wantErr: "Incomplete issue certificate request", 626 }, 627 { 628 name: "unrecognized profile", 629 profile: "doesnotexist", 630 wantErr: "incapable of using a profile named", 631 }, 632 { 633 name: "invalid profile name", 634 profile: "🤓", 635 wantErr: "incapable of using a profile named", 636 }, 637 } { 638 t.Run(tc.name, func(t *testing.T) { 639 t.Parallel() 640 641 _, err := ca.IssueCertificate(t.Context(), &capb.IssueCertificateRequest{ 642 RegistrationID: 1, OrderID: 1, 643 Csr: ECDSACSR, CertProfileName: tc.profile, 644 }) 645 if err == nil { 646 t.Fatalf("IssueCertificate(profile=%q) succeeded, but want error", tc.profile) 647 } 648 if !strings.Contains(err.Error(), tc.wantErr) { 649 t.Fatalf("IssueCertificate(profile=%q) = %q, but want %q", tc.profile, err, tc.wantErr) 650 } 651 }) 652 } 653 } 654 655 func TestIssueCertificate_ProfileSelection(t *testing.T) { 656 t.Parallel() 657 658 ca, err := newCAArgs(t).make() 659 if err != nil { 660 t.Fatalf("making test ca: %s", err) 661 } 662 663 for _, tc := range []struct { 664 profile string 665 wantValidity time.Duration 666 }{ 667 { 668 profile: "legacy", 669 wantValidity: 90 * 24 * time.Hour, 670 }, 671 { 672 profile: "modern", 673 wantValidity: 6 * 24 * time.Hour, 674 }, 675 } { 676 t.Run(tc.profile, func(t *testing.T) { 677 t.Parallel() 678 679 res, err := ca.IssueCertificate(t.Context(), &capb.IssueCertificateRequest{ 680 RegistrationID: 1, OrderID: 1, 681 Csr: ECDSACSR, CertProfileName: tc.profile, 682 }) 683 if err != nil { 684 t.Fatalf("IssueCertificate(profile=%q) = %q, but want success", tc.profile, err) 685 } 686 687 cert, err := x509.ParseCertificate(res.DER) 688 if err != nil { 689 t.Fatalf("parsing certificate: %s", err) 690 } 691 692 // We use the validity period as a proxy for detecting whether the correct 693 // profile was selected and used, since we know that the validity period 694 // differs between the two test profiles. 695 validity := cert.NotAfter.Add(time.Second).Sub(cert.NotBefore) 696 if validity != tc.wantValidity { 697 t.Errorf("IssueCertificate(profile=%q) = validity %d, but want %d", tc.profile, validity, tc.wantValidity) 698 } 699 }) 700 } 701 } 702 703 func TestIssueCertificate_IssuerSelection(t *testing.T) { 704 t.Parallel() 705 cargs := newCAArgs(t) 706 origIssuers := cargs.issuers 707 708 ca, err := cargs.make() 709 if err != nil { 710 t.Fatalf("making test ca: %s", err) 711 } 712 713 for _, tc := range []struct { 714 name string 715 csr []byte 716 wantIssuers []*issuance.Issuer 717 wantKUs x509.KeyUsage 718 }{ 719 { 720 name: "ECDSA", 721 csr: ECDSACSR, 722 wantIssuers: origIssuers[2:], 723 wantKUs: x509.KeyUsageDigitalSignature, 724 }, 725 { 726 name: "RSA", 727 csr: CNandSANCSR, 728 wantIssuers: origIssuers[:2], 729 wantKUs: x509.KeyUsageDigitalSignature | x509.KeyUsageKeyEncipherment, 730 }, 731 } { 732 t.Run(tc.name, func(t *testing.T) { 733 t.Parallel() 734 735 res, err := ca.IssueCertificate(t.Context(), &capb.IssueCertificateRequest{ 736 RegistrationID: 1, OrderID: 1, 737 Csr: tc.csr, CertProfileName: "legacy", 738 }) 739 if err != nil { 740 t.Fatalf("IssueCertificate(csr=%q) = %q, but want success", tc.name, err) 741 } 742 743 cert, err := x509.ParseCertificate(res.DER) 744 if err != nil { 745 t.Fatalf("parsing certificate: %s", err) 746 } 747 748 if cert.KeyUsage != tc.wantKUs { 749 t.Errorf("IssueCertificate(csr=%q) has KU %v, but want %v", tc.name, cert.KeyUsage, tc.wantKUs) 750 } 751 752 validated := false 753 for _, issuer := range tc.wantIssuers { 754 err = cert.CheckSignatureFrom(issuer.Cert.Certificate) 755 if err == nil { 756 validated = true 757 break 758 } 759 } 760 if !validated { 761 t.Errorf("IssueCertificate(csr=%q) issued from unexpected issuer %q", tc.name, cert.Issuer.CommonName) 762 } 763 }) 764 } 765 } 766 767 func TestIssueCertificate_UnpredictableIssuance(t *testing.T) { 768 ca, err := newCAArgs(t).make() 769 if err != nil { 770 t.Fatalf("creating test ca: %s", err) 771 } 772 773 // Issue the same (ECDSA-keyed) certificate 20 times. At least one issuance 774 // should come from each of the two active ECDSA issuers (int-e1 and int-e2). 775 // With 20 trials, the probability that all 20 issuances come from the same 776 // issuer is 0.5 ^ 20 = 9.5e-7 ~= 1e-6 = 1 in a million, so we do not consider 777 // this test to be flaky. 778 seenE1 := false 779 seenE2 := false 780 for range 20 { 781 res, err := ca.IssueCertificate(t.Context(), &capb.IssueCertificateRequest{ 782 RegistrationID: 1, OrderID: 1, 783 Csr: ECDSACSR, CertProfileName: "legacy", 784 }) 785 if err != nil { 786 t.Fatalf("issuing certificate: %s", err) 787 } 788 789 cert, err := x509.ParseCertificate(res.DER) 790 if err != nil { 791 t.Fatalf("parsing certificate: %s", err) 792 } 793 794 if strings.Contains(cert.Issuer.CommonName, "E1") { 795 seenE1 = true 796 } else if strings.Contains(cert.Issuer.CommonName, "E2") { 797 seenE2 = true 798 } else { 799 t.Fatalf("Issued certificate from unexpected issuer") 800 } 801 } 802 803 if !seenE1 { 804 t.Error("Expected at least one issuance from active issuer E1") 805 } 806 if !seenE2 { 807 t.Error("Expected at least one issuance from active issuer E2") 808 } 809 } 810 811 // TestPickIssuer tests the various deterministic cases, ensuring that the 812 // function properly respects the issuers' key algorithms and profiles. The test 813 // cases here are somewhat tightly coupled to the profiles populated by 814 // newCAArgs; this full coverage is to ensure that pickIssuer doesn't have an 815 // off-by-one error or similar bug lurking in it. 816 // 817 // The non-deterministic case is covered by TestIssueCertificate_UnpredictableIssuance. 818 func TestPickIssuer(t *testing.T) { 819 t.Parallel() 820 821 ca, err := newCAArgs(t).make() 822 if err != nil { 823 t.Fatalf("creating test ca: %s", err) 824 } 825 826 for _, tc := range []struct { 827 name string 828 profile string 829 keyAlg x509.PublicKeyAlgorithm 830 wantErr bool 831 }{ 832 { 833 name: "unrecognized profile", 834 profile: "doesnotexist", 835 keyAlg: x509.ECDSA, 836 wantErr: true, 837 }, 838 { 839 name: "unrecognized key algorithm", 840 profile: "modern", 841 keyAlg: x509.Ed25519, 842 wantErr: true, 843 }, 844 { 845 name: "recognized/legacy+ecdsa", 846 profile: "legacy", 847 keyAlg: x509.ECDSA, 848 }, 849 { 850 name: "recognized/legacy+rsa", 851 profile: "legacy", 852 keyAlg: x509.RSA, 853 }, 854 { 855 name: "recognized/modern+ecdsa", 856 profile: "modern", 857 keyAlg: x509.ECDSA, 858 }, 859 { 860 name: "recognized/modern+rsa", 861 profile: "modern", 862 keyAlg: x509.RSA, 863 }, 864 } { 865 t.Run(tc.name, func(t *testing.T) { 866 _, err := ca.pickIssuer(tc.profile, tc.keyAlg) 867 if err == nil && tc.wantErr { 868 t.Errorf("pickIssuer(%s, %s) = success, but want error", tc.profile, tc.keyAlg) 869 } else if err != nil && !tc.wantErr { 870 t.Errorf("pickIssuer(%s, %s) = %s, but want success", tc.profile, tc.keyAlg, err) 871 } 872 }) 873 } 874 } 875 876 func TestPickIssuer_Inactive(t *testing.T) { 877 t.Parallel() 878 cargs := newCAArgs(t) 879 880 // Load our own set of issuers, but with half of them inactive. 881 var issuers []*issuance.Issuer 882 for i, name := range []string{"int-r3", "int-r4", "int-e1", "int-e2"} { 883 issuer, err := issuance.LoadIssuer(issuance.IssuerConfig{ 884 Active: i%2 == 0, 885 IssuerURL: fmt.Sprintf("http://not-example.com/i/%s", name), 886 CRLURLBase: fmt.Sprintf("http://not-example.com/c/%s/", name), 887 CRLShards: 10, 888 Location: issuance.IssuerLoc{ 889 File: fmt.Sprintf("../test/hierarchy/%s.key.pem", name), 890 CertFile: fmt.Sprintf("../test/hierarchy/%s.cert.pem", name), 891 }, 892 Profiles: []string{"legacy", "modern"}, 893 }, cargs.clk) 894 if err != nil { 895 t.Fatalf("loading test issuer: %s", err) 896 } 897 issuers = append(issuers, issuer) 898 } 899 cargs.issuers = issuers 900 901 ca, err := cargs.make() 902 if err != nil { 903 t.Fatalf("creating test ca: %s", err) 904 } 905 906 // Calling pickIssuer should never return one of the inactive issuers. 907 for range 20 { 908 issuer, err := ca.pickIssuer("modern", x509.ECDSA) 909 if err != nil { 910 t.Fatalf("pickIssuer(modern, ECDSA) = %s, but want success", err) 911 } 912 if strings.Contains(issuer.Name(), "E2") { 913 t.Errorf("pickIssuer(modern, ECDSA) = E2, but only want E1") 914 } 915 } 916 for range 20 { 917 issuer, err := ca.pickIssuer("modern", x509.RSA) 918 if err != nil { 919 t.Fatalf("pickIssuer(modern, RSA) = %s, but want success", err) 920 } 921 if strings.Contains(issuer.Name(), "R4") { 922 t.Errorf("pickIssuer(modern, RSA) = R4, but only want R3") 923 } 924 } 925 } 926 927 func TestNoteSignError(t *testing.T) { 928 testCtx := newCAArgs(t) 929 metrics := testCtx.metrics 930 931 err := fmt.Errorf("wrapped non-signing error: %w", errors.New("oops")) 932 metrics.noteSignError(err) 933 test.AssertMetricWithLabelsEquals(t, metrics.signErrorCount, prometheus.Labels{"type": "HSM"}, 0) 934 935 err = fmt.Errorf("wrapped signing error: %w", pkcs11.Error(5)) 936 metrics.noteSignError(err) 937 test.AssertMetricWithLabelsEquals(t, metrics.signErrorCount, prometheus.Labels{"type": "HSM"}, 1) 938 } 939 940 func TestGenerateSKID(t *testing.T) { 941 t.Parallel() 942 key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 943 test.AssertNotError(t, err, "Error generating key") 944 945 sha256skid, err := generateSKID(key.Public()) 946 test.AssertNotError(t, err, "Error generating SKID") 947 test.AssertEquals(t, len(sha256skid), 20) 948 test.AssertEquals(t, cap(sha256skid), 20) 949 features.Reset() 950 } 951 952 func TestVerifyTBSCertIsDeterministic(t *testing.T) { 953 t.Parallel() 954 955 // Create first keypair and cert 956 testKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 957 test.AssertNotError(t, err, "unable to generate ECDSA private key") 958 template := &x509.Certificate{ 959 NotAfter: time.Now().Add(1 * time.Hour), 960 DNSNames: []string{"example.com"}, 961 SerialNumber: big.NewInt(1), 962 } 963 certDer1, err := x509.CreateCertificate(rand.Reader, template, template, &testKey.PublicKey, testKey) 964 test.AssertNotError(t, err, "unable to create certificate") 965 966 // Create second keypair and cert 967 testKey2, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 968 test.AssertNotError(t, err, "unable to generate ECDSA private key") 969 template2 := &x509.Certificate{ 970 NotAfter: time.Now().Add(2 * time.Hour), 971 DNSNames: []string{"example.net"}, 972 SerialNumber: big.NewInt(2), 973 } 974 certDer2, err := x509.CreateCertificate(rand.Reader, template2, template2, &testKey2.PublicKey, testKey2) 975 test.AssertNotError(t, err, "unable to create certificate") 976 977 testCases := []struct { 978 name string 979 lintCertBytes []byte 980 leafCertBytes []byte 981 errorSubstr string 982 }{ 983 { 984 name: "Both nil", 985 lintCertBytes: nil, 986 leafCertBytes: nil, 987 errorSubstr: "were nil", 988 }, 989 { 990 name: "Missing a value, invalid input", 991 lintCertBytes: nil, 992 leafCertBytes: []byte{0x6, 0x6, 0x6}, 993 errorSubstr: "were nil", 994 }, 995 { 996 name: "Missing a value, valid input", 997 lintCertBytes: nil, 998 leafCertBytes: certDer1, 999 errorSubstr: "were nil", 1000 }, 1001 { 1002 name: "Mismatched bytes, invalid input", 1003 lintCertBytes: []byte{0x6, 0x6, 0x6}, 1004 leafCertBytes: []byte{0x1, 0x2, 0x3}, 1005 errorSubstr: "malformed certificate", 1006 }, 1007 { 1008 name: "Mismatched bytes, invalider input", 1009 lintCertBytes: certDer1, 1010 leafCertBytes: []byte{0x1, 0x2, 0x3}, 1011 errorSubstr: "malformed certificate", 1012 }, 1013 { 1014 // This case is an example of when a linting cert's DER bytes are 1015 // mismatched compared to then precert or final cert created from 1016 // that linting cert's DER bytes. 1017 name: "Mismatched bytes, valid input", 1018 lintCertBytes: certDer1, 1019 leafCertBytes: certDer2, 1020 errorSubstr: "mismatch between", 1021 }, 1022 { 1023 // Take this with a grain of salt since this test is not actually 1024 // creating a linting certificate and performing two 1025 // x509.CreateCertificate() calls like 1026 // ca.IssueCertificateForPrecertificate and 1027 // ca.issuePrecertificateInner do. However, we're still going to 1028 // verify the equality. 1029 name: "Valid", 1030 lintCertBytes: certDer1, 1031 leafCertBytes: certDer1, 1032 }, 1033 } 1034 1035 for _, testCase := range testCases { 1036 t.Run(testCase.name, func(t *testing.T) { 1037 t.Parallel() 1038 err := tbsCertIsDeterministic(testCase.lintCertBytes, testCase.leafCertBytes) 1039 if testCase.errorSubstr != "" { 1040 test.AssertError(t, err, "your lack of errors is disturbing") 1041 test.AssertContains(t, err.Error(), testCase.errorSubstr) 1042 } else { 1043 test.AssertNotError(t, err, "unexpected error") 1044 } 1045 }) 1046 } 1047 }