github.com/letsencrypt/boulder@v0.20251208.0/test/integration/revocation_test.go (about) 1 //go:build integration 2 3 package integration 4 5 import ( 6 "crypto" 7 "crypto/ecdsa" 8 "crypto/elliptic" 9 "crypto/rand" 10 "crypto/x509" 11 "encoding/hex" 12 "encoding/json" 13 "encoding/pem" 14 "fmt" 15 "io" 16 "net/http" 17 "os" 18 "os/exec" 19 "path" 20 "strings" 21 "sync" 22 "testing" 23 "time" 24 25 "github.com/eggsampler/acme/v3" 26 27 "github.com/letsencrypt/boulder/core" 28 "github.com/letsencrypt/boulder/crl/idp" 29 "github.com/letsencrypt/boulder/revocation" 30 "github.com/letsencrypt/boulder/test" 31 ) 32 33 // isPrecert returns true if the provided cert has an extension with the OID 34 // equal to OIDExtensionCTPoison. 35 func isPrecert(cert *x509.Certificate) bool { 36 for _, ext := range cert.Extensions { 37 if ext.Id.Equal(OIDExtensionCTPoison) { 38 return true 39 } 40 } 41 return false 42 } 43 44 // getALLCRLs fetches and parses each certificate for each configured CA. 45 // Returns a map from issuer SKID (hex) to a list of that issuer's CRLs. 46 func getAllCRLs(t *testing.T) map[string][]*x509.RevocationList { 47 t.Helper() 48 b, err := os.ReadFile(path.Join(os.Getenv("BOULDER_CONFIG_DIR"), "ca.json")) 49 if err != nil { 50 t.Fatalf("reading CA config: %s", err) 51 } 52 53 var conf struct { 54 CA struct { 55 Issuance struct { 56 Issuers []struct { 57 CRLURLBase string 58 Location struct { 59 CertFile string 60 } 61 } 62 } 63 } 64 } 65 66 err = json.Unmarshal(b, &conf) 67 if err != nil { 68 t.Fatalf("unmarshaling CA config: %s", err) 69 } 70 71 ret := make(map[string][]*x509.RevocationList) 72 73 for _, issuer := range conf.CA.Issuance.Issuers { 74 issuerPEMBytes, err := os.ReadFile(issuer.Location.CertFile) 75 if err != nil { 76 t.Fatalf("reading CRL issuer: %s", err) 77 } 78 79 block, _ := pem.Decode(issuerPEMBytes) 80 issuerCert, err := x509.ParseCertificate(block.Bytes) 81 if err != nil { 82 t.Fatalf("parsing CRL issuer: %s", err) 83 } 84 85 issuerSKID := hex.EncodeToString(issuerCert.SubjectKeyId) 86 87 // 10 is the number of shards configured in test/config*/crl-updater.json 88 for i := range 10 { 89 crlURL := fmt.Sprintf("%s%d.crl", issuer.CRLURLBase, i+1) 90 list := getCRL(t, crlURL, issuerCert) 91 92 ret[issuerSKID] = append(ret[issuerSKID], list) 93 } 94 } 95 return ret 96 } 97 98 // getCRL fetches a CRL, parses it, verifies that it has the correct IDP, 99 // and checks the signature (if an issuer was provided). 100 func getCRL(t *testing.T, crlURL string, issuerCert *x509.Certificate) *x509.RevocationList { 101 t.Helper() 102 resp, err := http.Get(crlURL) 103 if err != nil { 104 t.Fatalf("getting CRL from %s: %s", crlURL, err) 105 } 106 if resp.StatusCode != http.StatusOK { 107 t.Fatalf("fetching %s: status code %d", crlURL, resp.StatusCode) 108 } 109 body, err := io.ReadAll(resp.Body) 110 if err != nil { 111 t.Fatalf("reading CRL from %s: %s", crlURL, err) 112 } 113 resp.Body.Close() 114 115 list, err := x509.ParseRevocationList(body) 116 if err != nil { 117 t.Fatalf("parsing CRL from %s: %s (bytes: %x)", crlURL, err, body) 118 } 119 120 if issuerCert != nil { 121 err = list.CheckSignatureFrom(issuerCert) 122 if err != nil { 123 t.Errorf("checking CRL signature on %s from %s: %s", 124 crlURL, issuerCert.Subject, err) 125 } 126 } 127 128 idpURIs, err := idp.GetIDPURIs(list.Extensions) 129 if err != nil { 130 t.Fatalf("getting IDP URIs: %s", err) 131 } 132 if len(idpURIs) != 1 { 133 t.Errorf("CRL at %s: expected 1 IDP URI, got %s", crlURL, idpURIs) 134 } 135 if idpURIs[0] != crlURL { 136 t.Errorf("fetched CRL from %s, got IDP of %s (should be same)", crlURL, idpURIs[0]) 137 } 138 return list 139 } 140 141 // waitAndCheckRevoked ensures that the given certificate appears on the correct 142 // CRL with the desired reason. It is willing to repeatedly regenerate CRLs up 143 // to four times, and wait up to 5 seconds, before reporting failure. 144 // 145 // The issuer argument is optional: it is used to verify the signature on the 146 // fetched CRL, but is not always available in our tests (e.g. if the finalize 147 // call purposefully failed so no chain file was provided). 148 func waitAndCheckRevoked(t *testing.T, cert *x509.Certificate, issuer *x509.Certificate, wantReason revocation.Reason) { 149 t.Helper() 150 151 if len(cert.CRLDistributionPoints) != 1 { 152 t.Errorf("expected certificate to have one CRLDistributionPoints field") 153 } 154 crlURL := cert.CRLDistributionPoints[0] 155 156 for try := range 4 { 157 time.Sleep(core.RetryBackoff(try, time.Second, 2*time.Second, 1.5)) 158 159 // These steps can terminate the loop early, but that's okay, because 160 // failing to generate or fetch CRLs is a more fundamental error than 161 // whatever behavior the test is actually looking for. 162 runUpdater(t, path.Join(os.Getenv("BOULDER_CONFIG_DIR"), "crl-updater.json")) 163 164 list := getCRL(t, crlURL, issuer) 165 var reasons []revocation.Reason 166 for _, entry := range list.RevokedCertificateEntries { 167 if entry.SerialNumber.Cmp(cert.SerialNumber) == 0 { 168 reasons = append(reasons, revocation.Reason(entry.ReasonCode)) 169 } 170 } 171 172 if len(reasons) == 1 && reasons[0] == wantReason { 173 // Success, the cert was revoked for the correct reason. 174 return 175 } else if len(reasons) == 1 && reasons[0] != wantReason { 176 // We're okay terminating the loop early because an incorrect revocation 177 // reason should never happen. 178 t.Fatalf("found %x revoked with reason %d, but want reason %d", cert.SerialNumber, reasons[0], wantReason) 179 } else if len(reasons) > 1 { 180 // We're okay terminating the loop early because multiple entries for the 181 // same cert should never happen. 182 t.Fatalf("found multiple CRL entries for %x", cert.SerialNumber) 183 } 184 } 185 186 t.Errorf("no CRL entry found for %x", cert.SerialNumber) 187 } 188 189 func checkUnrevoked(t *testing.T, revocations map[string][]*x509.RevocationList, cert *x509.Certificate) { 190 t.Helper() 191 for _, singleIssuerCRLs := range revocations { 192 for _, crl := range singleIssuerCRLs { 193 for _, entry := range crl.RevokedCertificateEntries { 194 if entry.SerialNumber == cert.SerialNumber { 195 t.Errorf("expected %x to be unrevoked, but found it on a CRL", cert.SerialNumber) 196 } 197 } 198 } 199 } 200 } 201 202 func checkRevoked(t *testing.T, revocations map[string][]*x509.RevocationList, cert *x509.Certificate, expectedReason revocation.Reason) { 203 t.Helper() 204 akid := hex.EncodeToString(cert.AuthorityKeyId) 205 if len(revocations[akid]) == 0 { 206 t.Errorf("no CRLs found for authorityKeyID %s", akid) 207 } 208 var matchingCRLs []string 209 var count int 210 for _, list := range revocations[akid] { 211 for _, entry := range list.RevokedCertificateEntries { 212 count++ 213 if entry.SerialNumber.Cmp(cert.SerialNumber) == 0 { 214 idpURIs, err := idp.GetIDPURIs(list.Extensions) 215 if err != nil { 216 t.Errorf("getting IDP URIs: %s", err) 217 } 218 idpURI := idpURIs[0] 219 if revocation.Reason(entry.ReasonCode) != expectedReason { 220 t.Errorf("revoked certificate %x in CRL %s: revocation reason %d, want %d", cert.SerialNumber, idpURI, entry.ReasonCode, expectedReason) 221 } 222 matchingCRLs = append(matchingCRLs, idpURI) 223 } 224 } 225 } 226 if len(matchingCRLs) == 0 { 227 t.Errorf("searching for %x in CRLs: no entry on combined CRLs of length %d", cert.SerialNumber, count) 228 } 229 230 // If the cert has a CRLDP, it must be listed on the CRL served at that URL. 231 if len(cert.CRLDistributionPoints) > 0 { 232 expectedCRLDP := cert.CRLDistributionPoints[0] 233 found := false 234 for _, crl := range matchingCRLs { 235 if crl == expectedCRLDP { 236 found = true 237 } 238 } 239 if !found { 240 t.Errorf("revoked certificate %x: seen on CRLs %s, want to see on CRL %s", cert.SerialNumber, matchingCRLs, expectedCRLDP) 241 } 242 } 243 } 244 245 // TestRevocation tests that a certificate can be revoked using all of the 246 // RFC 8555 revocation authentication mechanisms. It does so for both certs and 247 // precerts (with no corresponding final cert), and for both the Unspecified and 248 // keyCompromise revocation reasons. 249 func TestRevocation(t *testing.T) { 250 type authMethod string 251 var ( 252 byAccount authMethod = "byAccount" 253 byAuth authMethod = "byAuth" 254 byKey authMethod = "byKey" 255 byAdmin authMethod = "byAdmin" 256 ) 257 258 type certKind string 259 var ( 260 finalcert certKind = "cert" 261 precert certKind = "precert" 262 ) 263 264 type testCase struct { 265 method authMethod 266 reason revocation.Reason 267 kind certKind 268 } 269 270 issueAndRevoke := func(tc testCase) *x509.Certificate { 271 issueClient, err := makeClient() 272 test.AssertNotError(t, err, "creating acme client") 273 274 certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 275 test.AssertNotError(t, err, "creating random cert key") 276 277 domain := random_domain() 278 279 // Try to issue a certificate for the name. 280 var cert *x509.Certificate 281 switch tc.kind { 282 case finalcert: 283 res, err := authAndIssue(issueClient, certKey, []acme.Identifier{{Type: "dns", Value: domain}}, true, "") 284 test.AssertNotError(t, err, "authAndIssue failed") 285 cert = res.certs[0] 286 287 case precert: 288 // Make sure the ct-test-srv will reject generating SCTs for the domain, 289 // so we only get a precert and no final cert. 290 err := ctAddRejectHost(domain) 291 test.AssertNotError(t, err, "adding ct-test-srv reject host") 292 293 _, err = authAndIssue(issueClient, certKey, []acme.Identifier{{Type: "dns", Value: domain}}, true, "") 294 test.AssertError(t, err, "expected error from authAndIssue, was nil") 295 if !strings.Contains(err.Error(), "urn:ietf:params:acme:error:serverInternal") || 296 !strings.Contains(err.Error(), "SCT embedding") { 297 t.Fatal(err) 298 } 299 300 // Instead recover the precertificate from CT. 301 cert, err = ctFindRejection([]string{domain}) 302 if err != nil || cert == nil { 303 t.Fatalf("couldn't find rejected precert for %q", domain) 304 } 305 // And make sure the cert we found is in fact a precert. 306 if !isPrecert(cert) { 307 t.Fatal("precert was missing poison extension") 308 } 309 310 default: 311 t.Fatalf("unrecognized cert kind %q", tc.kind) 312 } 313 314 // Set up the account and key that we'll use to revoke the cert. 315 switch tc.method { 316 case byAccount: 317 // When revoking by account, use the same client and key as were used 318 // for the original issuance. 319 err = issueClient.RevokeCertificate( 320 issueClient.Account, 321 cert, 322 issueClient.PrivateKey, 323 int(tc.reason), 324 ) 325 test.AssertNotError(t, err, "revocation should have succeeded") 326 327 case byAuth: 328 // When revoking by auth, create a brand new client, authorize it for 329 // the same domain, and use that account and key for revocation. Ignore 330 // errors from authAndIssue because all we need is the auth, not the 331 // issuance. 332 newClient, err := makeClient() 333 test.AssertNotError(t, err, "creating second acme client") 334 _, _ = authAndIssue(newClient, certKey, []acme.Identifier{{Type: "dns", Value: domain}}, true, "") 335 336 err = newClient.RevokeCertificate( 337 newClient.Account, 338 cert, 339 newClient.PrivateKey, 340 int(tc.reason), 341 ) 342 test.AssertNotError(t, err, "revocation should have succeeded") 343 344 case byKey: 345 // When revoking by key, create a brand new client and use it with 346 // the cert's key for revocation. 347 newClient, err := makeClient() 348 test.AssertNotError(t, err, "creating second acme client") 349 err = newClient.RevokeCertificate( 350 newClient.Account, 351 cert, 352 certKey, 353 int(tc.reason), 354 ) 355 test.AssertNotError(t, err, "revocation should have succeeded") 356 357 case byAdmin: 358 // Invoke the admin tool to perform the revocation via gRPC, rather than 359 // using the external-facing ACME API. 360 config := fmt.Sprintf("%s/%s", os.Getenv("BOULDER_CONFIG_DIR"), "admin.json") 361 cmd := exec.Command( 362 "./bin/admin", 363 "-config", config, 364 "-dry-run=false", 365 "revoke-cert", 366 "-serial", core.SerialToString(cert.SerialNumber), 367 "-reason", tc.reason.String()) 368 output, err := cmd.CombinedOutput() 369 t.Logf("admin revoke-cert output: %s\n", string(output)) 370 test.AssertNotError(t, err, "revocation should have succeeded") 371 372 default: 373 t.Fatalf("unrecognized revocation method %q", tc.method) 374 } 375 376 return cert 377 } 378 379 // revocationCheck represents a deferred that a specific certificate is revoked. 380 // 381 // We defer these checks for performance reasons: we want to run crl-updater once, 382 // after all certificates have been revoked. 383 type revocationCheck func(t *testing.T, allCRLs map[string][]*x509.RevocationList) 384 var revocationChecks []revocationCheck 385 var rcMu sync.Mutex 386 var wg sync.WaitGroup 387 388 for _, kind := range []certKind{precert, finalcert} { 389 for _, reason := range []revocation.Reason{revocation.Unspecified, revocation.KeyCompromise, revocation.Superseded} { 390 for _, method := range []authMethod{byAccount, byAuth, byKey, byAdmin} { 391 wg.Add(1) 392 go func() { 393 defer wg.Done() 394 cert := issueAndRevoke(testCase{ 395 method: method, 396 reason: reason, 397 kind: kind, 398 // We do not expect any of these revocation requests to error. 399 // The ones done byAccount will succeed as requested, but will not 400 // result in the key being blocked for future issuance. 401 // The ones done byAuth will succeed, but will be overwritten to have 402 // reason code 5 (cessationOfOperation). 403 // The ones done byKey will succeed, but will be overwritten to have 404 // reason code 1 (keyCompromise), and will block the key. 405 }) 406 407 // If the request was made by demonstrating control over the 408 // names, the reason should be overwritten to CessationOfOperation (5), 409 // and if the request was made by key, then the reason should be set to 410 // KeyCompromise (1). 411 expectedReason := reason 412 switch method { 413 case byAuth: 414 expectedReason = revocation.CessationOfOperation 415 case byKey: 416 expectedReason = revocation.KeyCompromise 417 default: 418 } 419 420 check := func(t *testing.T, allCRLs map[string][]*x509.RevocationList) { 421 checkRevoked(t, allCRLs, cert, expectedReason) 422 } 423 424 rcMu.Lock() 425 revocationChecks = append(revocationChecks, check) 426 rcMu.Unlock() 427 }() 428 } 429 } 430 } 431 432 wg.Wait() 433 434 runUpdater(t, path.Join(os.Getenv("BOULDER_CONFIG_DIR"), "crl-updater.json")) 435 allCRLs := getAllCRLs(t) 436 437 for _, check := range revocationChecks { 438 check(t, allCRLs) 439 } 440 } 441 442 // TestReRevocation verifies that a certificate can have its revocation 443 // information updated only when both of the following are true: 444 // a) The certificate was not initially revoked for reason keyCompromise; and 445 // b) The second request is authenticated using the cert's keypair. 446 // In which case the revocation reason (but not revocation date) will be 447 // updated to be keyCompromise. 448 func TestReRevocation(t *testing.T) { 449 type authMethod string 450 var ( 451 byAccount authMethod = "byAccount" 452 byKey authMethod = "byKey" 453 ) 454 455 type testCase struct { 456 method1 authMethod 457 reason1 revocation.Reason 458 method2 authMethod 459 reason2 revocation.Reason 460 expectError bool 461 } 462 463 testCases := []testCase{ 464 {method1: byAccount, reason1: revocation.Unspecified, method2: byAccount, reason2: revocation.Unspecified, expectError: true}, 465 {method1: byAccount, reason1: revocation.KeyCompromise, method2: byAccount, reason2: revocation.KeyCompromise, expectError: true}, 466 {method1: byAccount, reason1: revocation.Unspecified, method2: byKey, reason2: revocation.KeyCompromise, expectError: false}, 467 {method1: byAccount, reason1: revocation.KeyCompromise, method2: byKey, reason2: revocation.KeyCompromise, expectError: true}, 468 {method1: byKey, reason1: revocation.KeyCompromise, method2: byKey, reason2: revocation.KeyCompromise, expectError: true}, 469 } 470 471 for i, tc := range testCases { 472 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 473 issueClient, err := makeClient() 474 test.AssertNotError(t, err, "creating acme client") 475 476 certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 477 test.AssertNotError(t, err, "creating random cert key") 478 479 // Try to issue a certificate for the name. 480 res, err := authAndIssue(issueClient, certKey, []acme.Identifier{{Type: "dns", Value: random_domain()}}, true, "") 481 test.AssertNotError(t, err, "authAndIssue failed") 482 cert := res.certs[0] 483 issuer := res.certs[1] 484 485 // Set up the account and key that we'll use to revoke the cert. 486 var revokeClient *client 487 var revokeKey crypto.Signer 488 switch tc.method1 { 489 case byAccount: 490 // When revoking by account, use the same client and key as were used 491 // for the original issuance. 492 revokeClient = issueClient 493 revokeKey = revokeClient.PrivateKey 494 495 case byKey: 496 // When revoking by key, create a brand new client and use it with 497 // the cert's key for revocation. 498 revokeClient, err = makeClient() 499 test.AssertNotError(t, err, "creating second acme client") 500 revokeKey = certKey 501 502 default: 503 t.Fatalf("unrecognized revocation method %q", tc.method1) 504 } 505 506 // Revoke the cert using the specified key and client. 507 err = revokeClient.RevokeCertificate( 508 revokeClient.Account, 509 cert, 510 revokeKey, 511 int(tc.reason1), 512 ) 513 test.AssertNotError(t, err, "initial revocation should have succeeded") 514 515 // Check the CRL for the certificate again. It should now be 516 // revoked. 517 waitAndCheckRevoked(t, cert, issuer, tc.reason1) 518 519 // Set up the account and key that we'll use to *re*-revoke the cert. 520 switch tc.method2 { 521 case byAccount: 522 // When revoking by account, use the same client and key as were used 523 // for the original issuance. 524 revokeClient = issueClient 525 revokeKey = revokeClient.PrivateKey 526 527 case byKey: 528 // When revoking by key, create a brand new client and use it with 529 // the cert's key for revocation. 530 revokeClient, err = makeClient() 531 test.AssertNotError(t, err, "creating second acme client") 532 revokeKey = certKey 533 534 default: 535 t.Fatalf("unrecognized revocation method %q", tc.method2) 536 } 537 538 // Re-revoke the cert using the specified key and client. 539 err = revokeClient.RevokeCertificate( 540 revokeClient.Account, 541 cert, 542 revokeKey, 543 int(tc.reason2), 544 ) 545 546 switch tc.expectError { 547 case true: 548 test.AssertError(t, err, "second revocation should have failed") 549 550 // Check the CRL for the certificate again. It should still be 551 // revoked, with the same reason. 552 waitAndCheckRevoked(t, cert, issuer, tc.reason1) 553 554 case false: 555 test.AssertNotError(t, err, "second revocation should have succeeded") 556 557 // Check the CRL for the certificate again. It should now be 558 // revoked with reason keyCompromise. 559 waitAndCheckRevoked(t, cert, issuer, tc.reason2) 560 } 561 }) 562 } 563 } 564 565 func TestRevokeWithKeyCompromiseBlocksKey(t *testing.T) { 566 type authMethod string 567 var ( 568 byAccount authMethod = "byAccount" 569 byKey authMethod = "byKey" 570 ) 571 572 // Test keyCompromise revocation both when revoking by certificate key and 573 // revoking by subscriber key. Both should work, although with slightly 574 // different behavior. 575 for _, method := range []authMethod{byKey, byAccount} { 576 c, err := makeClient("mailto:example@letsencrypt.org") 577 test.AssertNotError(t, err, "creating acme client") 578 579 certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 580 test.AssertNotError(t, err, "failed to generate cert key") 581 582 res, err := authAndIssue(c, certKey, []acme.Identifier{{Type: "dns", Value: random_domain()}}, true, "") 583 test.AssertNotError(t, err, "authAndIssue failed") 584 cert := res.certs[0] 585 issuer := res.certs[1] 586 587 // Revoke the cert with reason keyCompromise, either authenticated via the 588 // issuing account, or via the certificate key itself. 589 switch method { 590 case byAccount: 591 err = c.RevokeCertificate(c.Account, cert, c.PrivateKey, int(revocation.KeyCompromise)) 592 case byKey: 593 err = c.RevokeCertificate(acme.Account{}, cert, certKey, int(revocation.KeyCompromise)) 594 } 595 test.AssertNotError(t, err, "failed to revoke certificate") 596 597 // Check the CRL. It should be revoked with reason = 1 (keyCompromise). 598 waitAndCheckRevoked(t, cert, issuer, revocation.KeyCompromise) 599 600 // Attempt to create a new account using the compromised key. This should 601 // work when the key was just *reported* as compromised, but fail when 602 // the compromise was demonstrated/proven. 603 _, err = c.NewAccount(certKey, false, true) 604 switch method { 605 case byAccount: 606 test.AssertNotError(t, err, "NewAccount failed with a non-blocklisted key") 607 case byKey: 608 test.AssertError(t, err, "NewAccount didn't fail with a blocklisted key") 609 test.AssertEquals(t, err.Error(), `acme: error code 400 "urn:ietf:params:acme:error:badPublicKey": Unable to validate JWS :: invalid request signing key: public key is forbidden`) 610 } 611 } 612 } 613 614 func TestBadKeyRevoker(t *testing.T) { 615 revokerClient, err := makeClient() 616 test.AssertNotError(t, err, "creating acme client") 617 revokeeClient, err := makeClient() 618 test.AssertNotError(t, err, "creating acme client") 619 neutralClient, err := makeClient() 620 test.AssertNotError(t, err, "creating acme client") 621 622 certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 623 test.AssertNotError(t, err, "failed to generate cert key") 624 625 // Issue a cert from the revokee client, which we'll revoke soon 626 toBeRevoked, err := authAndIssue(revokeeClient, certKey, []acme.Identifier{{Type: "dns", Value: random_domain()}}, true, "") 627 test.AssertNotError(t, err, "authAndIssue failed") 628 t.Logf("Generated to-be-revoked cert with serial %x", toBeRevoked.certs[0].SerialNumber) 629 630 // Issue two more certs from two more accounts, one of which we'll use to 631 // revoke the original cert. 632 bundles := []*issuanceResult{} 633 for _, c := range []*client{revokerClient, neutralClient} { 634 bundle, err := authAndIssue(c, certKey, []acme.Identifier{{Type: "dns", Value: random_domain()}}, true, "") 635 test.AssertNotError(t, err, "authAndIssue failed") 636 t.Logf("TestBadKeyRevoker: Issued cert with serial %x", bundle.certs[0].SerialNumber) 637 bundles = append(bundles, bundle) 638 } 639 640 // Sign the revocation request using the certificate key, so we treat it as 641 // a demonstration of compromise and cascade the revocation. 642 err = revokerClient.RevokeCertificate( 643 acme.Account{}, 644 toBeRevoked.certs[0], 645 certKey, 646 int(revocation.KeyCompromise), 647 ) 648 test.AssertNotError(t, err, "failed to revoke certificate") 649 650 waitAndCheckRevoked(t, toBeRevoked.certs[0], toBeRevoked.certs[1], revocation.KeyCompromise) 651 652 for _, bundle := range bundles { 653 waitAndCheckRevoked(t, bundle.certs[0], bundle.certs[1], revocation.KeyCompromise) 654 } 655 } 656 657 func TestBadKeyRevokerByAccount(t *testing.T) { 658 revokeClient, err := makeClient() 659 test.AssertNotError(t, err, "creating acme client") 660 neutralClient, err := makeClient() 661 test.AssertNotError(t, err, "creating acme client") 662 663 certKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) 664 test.AssertNotError(t, err, "failed to generate cert key") 665 666 // Issue a cert from the revoke client, which we'll revoke soon 667 toBeRevoked, err := authAndIssue(revokeClient, certKey, []acme.Identifier{{Type: "dns", Value: random_domain()}}, true, "") 668 test.AssertNotError(t, err, "authAndIssue failed") 669 t.Logf("Generated to-be-revoked cert with serial %x", toBeRevoked.certs[0].SerialNumber) 670 671 // Issue two more certs, one from the original account and one from an 672 // unrelated account. We don't use separatze revoker/revokee accounts here 673 // because you can only revoke *your own* certs when signing the request with 674 // your account key. 675 bundles := []*issuanceResult{} 676 for _, c := range []*client{revokeClient, neutralClient} { 677 bundle, err := authAndIssue(c, certKey, []acme.Identifier{{Type: "dns", Value: random_domain()}}, true, "") 678 test.AssertNotError(t, err, "authAndIssue failed") 679 t.Logf("TestBadKeyRevokerByAccount: Issued cert with serial %x", bundle.certs[0].SerialNumber) 680 bundles = append(bundles, bundle) 681 } 682 683 // Sign the revocation request using the revokeClient's account key, so we 684 // don't treat it as a demonstration of compromise and don't cascade it. 685 err = revokeClient.RevokeCertificate( 686 revokeClient.Account, 687 toBeRevoked.certs[0], 688 revokeClient.PrivateKey, 689 int(revocation.KeyCompromise), 690 ) 691 test.AssertNotError(t, err, "failed to revoke certificate") 692 693 waitAndCheckRevoked(t, toBeRevoked.certs[0], toBeRevoked.certs[1], revocation.KeyCompromise) 694 695 allCRLs := getAllCRLs(t) 696 for _, bundle := range bundles { 697 checkUnrevoked(t, allCRLs, bundle.certs[0]) 698 } 699 }