Skip to content

Commit dab8a71

Browse files
authored
Use new RA methods from WFE revocation path (#5983)
Simplify the WFE `RevokeCertificate` API method in three ways: - Remove most of the logic checking if the requester is authorized to revoke the certificate in question (based on who is making the request, what authorizations they have, and what reason they're requesting). That checking is now done by the RA. Instead, simply verify that the JWS is authenticated. - Remove the hard-to-read `authorizedToRevoke` callbacks, and make the `revokeCertBySubscriberKey` (nee `revokeCertByKeyID`) and `revokeCertByCertKey` (nee `revokeCertByJWK`) helpers much more straight-line in their execution logic. - Call the RA's new `RevokeCertByApplicant` and `RevokeCertByKey` gRPC methods, rather than the deprecated `RevokeCertificateWithReg`. This change, without any flag flips, should be invisible to the end-user. It will slightly change some of our log message formats. However, by now relying on the new RA gRPC revocation methods, this change allows us to change our revocation policies by enabling the `AllowDoubleRevocation` and `MozRevocationReasons` feature flags, which affect the behavior of those new helpers. Fixes #5936
1 parent 6271a88 commit dab8a71

File tree

15 files changed

+705
-398
lines changed

15 files changed

+705
-398
lines changed

akamai/cache-client.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ func (cpc *CachePurgeClient) authedRequest(endpoint string, body v3PurgeRequest)
279279
}
280280

281281
cpc.log.AuditInfof("Purge request sent successfully (ID %s) (body %s). Purge expected in %ds",
282-
purgeInfo.PurgeID, purgeInfo.EstimatedSeconds, reqBody)
282+
purgeInfo.PurgeID, reqBody, purgeInfo.EstimatedSeconds)
283283
return nil
284284
}
285285

errors/errors.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,5 +146,5 @@ func AlreadyRevokedError(msg string, args ...interface{}) error {
146146
}
147147

148148
func BadRevocationReasonError(reason int64) error {
149-
return New(AlreadyRevoked, "disallowed revocation reason: %d", reason)
149+
return New(BadRevocationReason, "disallowed revocation reason: %d", reason)
150150
}

features/featureflag_string.go

Lines changed: 4 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

features/features.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,11 @@ const (
5959
GetAuthzUseIndex
6060
// Check the failed authorization limit before doing authz reuse.
6161
CheckFailedAuthorizationsFirst
62+
// AllowReRevocation causes the RA to allow the revocation reason of an
63+
// already-revoked certificate to be updated to `keyCompromise` from any
64+
// other reason if that compromise is demonstrated by making the second
65+
// revocation request signed by the certificate keypair.
66+
AllowReRevocation
6267
// MozRevocationReasons causes the RA to enforce the following upcoming
6368
// Mozilla policies regarding revocation:
6469
// - A subscriber can request that their certificate be revoked with reason
@@ -97,6 +102,7 @@ var features = map[FeatureFlag]bool{
97102
GetAuthzReadOnly: false,
98103
GetAuthzUseIndex: false,
99104
CheckFailedAuthorizationsFirst: false,
105+
AllowReRevocation: false,
100106
MozRevocationReasons: false,
101107
}
102108

ra/ra.go

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2111,7 +2111,17 @@ func (ra *RegistrationAuthorityImpl) RevokeCertByKey(ctx context.Context, req *r
21112111
// to the blocked keys list is a worse failure than failing to revoke in the
21122112
// first place, because it means that bad-key-revoker won't revoke the cert
21132113
// anyway.
2114-
if reason == ocsp.KeyCompromise {
2114+
var shouldBlock bool
2115+
if features.Enabled(features.AllowReRevocation) {
2116+
// If we're allowing re-revocation, then block the key for all keyCompromise
2117+
// requests, no matter whether the revocation itself succeeded or failed.
2118+
shouldBlock = reason == ocsp.KeyCompromise
2119+
} else {
2120+
// Otherwise, only block the key if the revocation above succeeded, or
2121+
// failed for a reason other than "already revoked".
2122+
shouldBlock = (reason == ocsp.KeyCompromise && !errors.Is(revokeErr, berrors.AlreadyRevoked))
2123+
}
2124+
if shouldBlock {
21152125
var digest core.Sha256Digest
21162126
digest, err = core.KeyDigest(cert.PublicKey)
21172127
if err != nil {
@@ -2132,7 +2142,12 @@ func (ra *RegistrationAuthorityImpl) RevokeCertByKey(ctx context.Context, req *r
21322142
// than keyCompromise.
21332143
err = revokeErr
21342144
if err != nil {
2135-
if !errors.Is(err, berrors.AlreadyRevoked) || reason != ocsp.KeyCompromise {
2145+
// Immediately error out, rather than trying re-revocation, if the error was
2146+
// anything other than AlreadyRevoked, if the requested reason is anything
2147+
// other than keyCompromise, or if we're not yet using the new logic.
2148+
if !errors.Is(err, berrors.AlreadyRevoked) ||
2149+
reason != ocsp.KeyCompromise ||
2150+
!features.Enabled(features.AllowReRevocation) {
21362151
return nil, err
21372152
}
21382153
err = ra.updateRevocationForKeyCompromise(ctx, cert.SerialNumber, int64(issuerID))

ra/ra_test.go

Lines changed: 27 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3872,6 +3872,19 @@ func TestRevokeCertByKey(t *testing.T) {
38723872
test.AssertEquals(t, len(mockSA.blocked), 0)
38733873
test.AssertEquals(t, mockSA.revoked[core.SerialToString(cert.SerialNumber)], int64(ocsp.Unspecified))
38743874

3875+
// Re-revoking for any reason should fail, because it isn't enabled.
3876+
_, err = ra.RevokeCertByKey(context.Background(), &rapb.RevokeCertByKeyRequest{
3877+
Cert: cert.Raw,
3878+
Code: ocsp.KeyCompromise,
3879+
})
3880+
test.AssertError(t, err, "should have failed")
3881+
3882+
// Enable re-revocation.
3883+
_ = features.Set(map[string]bool{
3884+
features.MozRevocationReasons.String(): false,
3885+
features.AllowReRevocation.String(): true,
3886+
})
3887+
38753888
// Re-revoking for the same reason should fail.
38763889
_, err = ra.RevokeCertByKey(context.Background(), &rapb.RevokeCertByKeyRequest{
38773890
Cert: cert.Raw,
@@ -3950,13 +3963,26 @@ func TestRevokeCertByKey_Moz(t *testing.T) {
39503963
test.AssertEquals(t, len(mockSA.blocked[0].Comment), 0)
39513964
test.AssertEquals(t, mockSA.revoked[core.SerialToString(cert.SerialNumber)], int64(ocsp.KeyCompromise))
39523965

3966+
// Re-revoking should fail, because re-revocation is not allowed.
3967+
_, err = ra.RevokeCertByKey(context.Background(), &rapb.RevokeCertByKeyRequest{
3968+
Cert: cert.Raw,
3969+
})
3970+
test.AssertError(t, err, "should have failed")
3971+
3972+
// Enable re-revocation.
3973+
_ = features.Set(map[string]bool{
3974+
features.MozRevocationReasons.String(): true,
3975+
features.AllowReRevocation.String(): true,
3976+
})
3977+
39533978
// Re-revoking should fail, because it is already revoked for keyCompromise.
39543979
_, err = ra.RevokeCertByKey(context.Background(), &rapb.RevokeCertByKeyRequest{
39553980
Cert: cert.Raw,
39563981
})
39573982
test.AssertError(t, err, "should have failed")
39583983

3959-
// Reset, revoke for some other reason, and try again.
3984+
// Reset and have the Subscriber revoke for a different reason.
3985+
// Then re-revoking using the key should work.
39603986
mockSA.revoked = make(map[string]int64)
39613987
_, err = ra.RevokeCertByApplicant(context.Background(), &rapb.RevokeCertByApplicantRequest{
39623988
Cert: cert.Raw,
@@ -3968,10 +3994,6 @@ func TestRevokeCertByKey_Moz(t *testing.T) {
39683994
Cert: cert.Raw,
39693995
})
39703996
test.AssertNotError(t, err, "should have succeeded")
3971-
_, err = ra.RevokeCertByKey(context.Background(), &rapb.RevokeCertByKeyRequest{
3972-
Cert: cert.Raw,
3973-
})
3974-
test.AssertError(t, err, "should have failed")
39753997
}
39763998

39773999
func TestAdministrativelyRevokeCertificate(t *testing.T) {

test/config-next/ra.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
"StoreRevokerInfo": true,
5959
"RestrictRSAKeySizes": true,
6060
"StreamlineOrderAndAuthzs": true,
61+
"AllowReRevocation": true,
6162
"MozRevocationReasons": true
6263
},
6364
"CTLogGroups2": [

test/helpers.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ def ocsp_verify(cert_file, issuer_file, ocsp_response):
9696
raise(Exception("OCSP verify failure"))
9797
return output
9898

99-
def verify_ocsp(cert_file, issuer_file, url, status="revoked"):
99+
def verify_ocsp(cert_file, issuer_file, url, status="revoked", reason=None):
100100
ocsp_request = make_ocsp_req(cert_file, issuer_file)
101101
responses = fetch_ocsp(ocsp_request, url)
102102

@@ -114,6 +114,10 @@ def verify_ocsp(cert_file, issuer_file, url, status="revoked"):
114114
if not re.search("%s: %s" % (cert_file, status), verify_output):
115115
print(verify_output)
116116
raise(Exception("OCSP response wasn't '%s'" % status))
117+
if reason is not None:
118+
if not re.search("Reason: %s" % reason, verify_output):
119+
print(verify_output)
120+
raise(Exception("OCSP response wasn't '%s'" % reason))
117121
return verify_output
118122

119123
def reset_akamai_purges():

0 commit comments

Comments
 (0)