Skip to content

Commit 86079c5

Browse files
authored
Improve error handling in admin block-key and revoke-cert (#8647)
Within the admin tool itself, multiple parallel goroutines blocking SPKI hashes were all writing to the same `err` variable, resulting in a potential data race. Fix this by giving each goroutine its own local error. Within ra.AdministrativelyRevokeCertificate, a failure in the initial sa.RevokeCertificate call would properly fall back to attempting sa.UpdateRevokedCertificate, but would not then continue on to calling sa.AddBlockedKey. Restructure this method to have the same control flow as ra.RevokeCertByKey, which properly ensures that sa.AddBlockedKey is called no matter whether the revocation succeeds or fails.
1 parent 219190c commit 86079c5

3 files changed

Lines changed: 56 additions & 11 deletions

File tree

cmd/admin/key.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,7 +218,7 @@ func (a *admin) blockSPKIHashes(ctx context.Context, spkiHashes [][]byte, commen
218218
for range parallelism {
219219
wg.Go(func() {
220220
for spkiHash := range work {
221-
err = a.blockSPKIHash(ctx, spkiHash, u, comment)
221+
err := a.blockSPKIHash(ctx, spkiHash, u, comment)
222222
if err != nil {
223223
errCount.Add(1)
224224
if errors.Is(err, berrors.AlreadyRevoked) {

ra/ra.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -2030,23 +2030,21 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte
20302030
shard = req.CrlShard
20312031
}
20322032

2033-
_, err = ra.SA.RevokeCertificate(ctx, &sapb.RevokeCertificateRequest{
2033+
// We revoke the cert before adding it to the blocked keys list, to avoid a
2034+
// race between this and the bad-key-revoker. But we don't check the error
2035+
// from this operation until after we add the key to the blocked keys list,
2036+
// since that addition needs to happen no matter what.
2037+
_, revokeErr := ra.SA.RevokeCertificate(ctx, &sapb.RevokeCertificateRequest{
20342038
Serial: req.Serial,
20352039
Reason: int64(reasonCode),
20362040
Date: timestamppb.New(ra.clk.Now()),
20372041
IssuerID: int64(issuerID),
20382042
ShardIdx: shard,
20392043
})
2040-
if err != nil {
2041-
if reasonCode == revocation.KeyCompromise && errors.Is(err, berrors.AlreadyRevoked) {
2042-
err = ra.updateRevocationForKeyCompromise(ctx, req.Serial, issuerID)
2043-
if err != nil {
2044-
return nil, err
2045-
}
2046-
}
2047-
return nil, err
2048-
}
20492044

2045+
// Failing to add the key to the blocked keys list is a worse failure than
2046+
// failing to revoke in the first place, because it means that
2047+
// bad-key-revoker won't revoke the cert anyway.
20502048
if reasonCode == revocation.KeyCompromise && !req.SkipBlockKey {
20512049
if cert == nil {
20522050
return nil, errors.New("revoking for key compromise requires providing the certificate's DER")
@@ -2057,6 +2055,18 @@ func (ra *RegistrationAuthorityImpl) AdministrativelyRevokeCertificate(ctx conte
20572055
}
20582056
}
20592057

2058+
// Check the error returned from sa.RevokeCertificate itself.
2059+
err = revokeErr
2060+
if errors.Is(err, berrors.AlreadyRevoked) && reasonCode == revocation.KeyCompromise {
2061+
err = ra.updateRevocationForKeyCompromise(ctx, req.Serial, issuerID)
2062+
if err != nil {
2063+
return nil, err
2064+
}
2065+
return &emptypb.Empty{}, nil
2066+
} else if err != nil {
2067+
return nil, err
2068+
}
2069+
20602070
return &emptypb.Empty{}, nil
20612071
}
20622072

ra/ra_test.go

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3956,6 +3956,41 @@ func TestAdministrativelyRevokeCertificate(t *testing.T) {
39563956
test.AssertError(t, err, "AdministrativelyRevokeCertificate should have failed with just serial for keyCompromise")
39573957
}
39583958

3959+
func TestAdministrativelyRevokeCertificateReRevokeKeyCompromiseBlocksKey(t *testing.T) {
3960+
_, _, ra, _, clk, _, cleanUp := initAuthorities(t)
3961+
defer cleanUp()
3962+
3963+
serial, cert := test.ThrowAwayCert(t, clk)
3964+
cert.IsCA = true
3965+
ic, err := issuance.NewCertificate(cert)
3966+
test.AssertNotError(t, err, "failed to create issuer cert")
3967+
ra.issuersByNameID = map[issuance.NameID]*issuance.Certificate{
3968+
ic.NameID(): ic,
3969+
}
3970+
mockSA := newMockSARevocation(cert)
3971+
ra.SA = mockSA
3972+
3973+
// First, revoke for a non-keyCompromise reason. This should succeed but not block the key.
3974+
_, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{
3975+
Serial: serial,
3976+
Code: int64(revocation.Unspecified),
3977+
AdminName: "root",
3978+
})
3979+
test.AssertNotError(t, err, "initial administrative revocation failed")
3980+
test.AssertEquals(t, len(mockSA.blocked), 0)
3981+
test.AssertEquals(t, mockSA.revoked[serial].RevokedReason, int64(revocation.Unspecified))
3982+
3983+
// Now, re-revoke for keyCompromise. This should both update the reason AND block the key.
3984+
_, err = ra.AdministrativelyRevokeCertificate(context.Background(), &rapb.AdministrativelyRevokeCertificateRequest{
3985+
Serial: serial,
3986+
Code: int64(revocation.KeyCompromise),
3987+
AdminName: "root",
3988+
})
3989+
test.AssertNotError(t, err, "keyCompromise re-revocation should have succeeded")
3990+
test.AssertEquals(t, len(mockSA.blocked), 1)
3991+
test.AssertEquals(t, mockSA.revoked[serial].RevokedReason, int64(revocation.KeyCompromise))
3992+
}
3993+
39593994
// An authority that returns an error from NewOrderAndAuthzs if the
39603995
// "ReplacesSerial" field of the request is empty.
39613996
type mockNewOrderMustBeReplacementAuthority struct {

0 commit comments

Comments
 (0)