Skip to content

Commit 584702b

Browse files
author
Daniel McCarney
authored
WFE2: Implement badRevocationReason problem type. (letsencrypt#4252)
Previously we were returning a Malformed problem type where RFC 8555 mandates the use of badRevocationReason and encourages including the allowed reasons in the problem detail.
1 parent 65086c6 commit 584702b

File tree

5 files changed

+55
-4
lines changed

5 files changed

+55
-4
lines changed

probs/probs.go

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const (
2626
OrderNotReadyProblem = ProblemType("orderNotReady")
2727
BadSignatureAlgorithmProblem = ProblemType("badSignatureAlgorithm")
2828
BadPublicKeyProblem = ProblemType("badPublicKey")
29+
BadRevocationReasonProblem = ProblemType("badRevocationReason")
2930

3031
V1ErrorNS = "urn:acme:error:"
3132
V2ErrorNS = "urn:ietf:params:acme:error:"
@@ -92,7 +93,8 @@ func ProblemDetailsToStatusCode(prob *ProblemDetails) int {
9293
BadNonceProblem,
9394
InvalidEmailProblem,
9495
RejectedIdentifierProblem,
95-
AccountDoesNotExistProblem:
96+
AccountDoesNotExistProblem,
97+
BadRevocationReasonProblem:
9698
return http.StatusBadRequest
9799
case ServerInternalProblem:
98100
return http.StatusInternalServerError
@@ -320,3 +322,13 @@ func OrderNotReady(detail string, a ...interface{}) *ProblemDetails {
320322
HTTPStatus: http.StatusForbidden,
321323
}
322324
}
325+
326+
// BadRevocationReason returns a ProblemDetails representing
327+
// a BadRevocationReasonProblem
328+
func BadRevocationReason(detail string, a ...interface{}) *ProblemDetails {
329+
return &ProblemDetails{
330+
Type: BadRevocationReasonProblem,
331+
Detail: fmt.Sprintf(detail, a...),
332+
HTTPStatus: http.StatusBadRequest,
333+
}
334+
}

probs/probs_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func TestProblemDetailsToStatusCode(t *testing.T) {
3636
{&ProblemDetails{Type: "foo", HTTPStatus: 200}, 200},
3737
{&ProblemDetails{Type: ConnectionProblem, HTTPStatus: 200}, 200},
3838
{&ProblemDetails{Type: AccountDoesNotExistProblem}, http.StatusBadRequest},
39+
{&ProblemDetails{Type: BadRevocationReasonProblem}, http.StatusBadRequest},
3940
}
4041

4142
for _, c := range testCases {
@@ -64,6 +65,7 @@ func TestProblemDetailsConvenience(t *testing.T) {
6465
{TLSError("TLS error detail"), TLSProblem, http.StatusBadRequest, "TLS error detail"},
6566
{RejectedIdentifier("rejected identifier detail"), RejectedIdentifierProblem, http.StatusBadRequest, "rejected identifier detail"},
6667
{AccountDoesNotExist("no account detail"), AccountDoesNotExistProblem, http.StatusBadRequest, "no account detail"},
68+
{BadRevocationReason("only reason xxx is supported"), BadRevocationReasonProblem, http.StatusBadRequest, "only reason xxx is supported"},
6769
}
6870

6971
for _, c := range testCases {

revocation/reasons.go

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
package revocation
22

3+
import (
4+
"fmt"
5+
"sort"
6+
"strings"
7+
)
8+
39
// Reason is used to specify a certificate revocation reason
410
type Reason int
511

@@ -44,3 +50,26 @@ var UserAllowedReasons = map[Reason]struct{}{
4450
Superseded: {}, // superseded
4551
CessationOfOperation: {}, // cessationOfOperation
4652
}
53+
54+
// UserAllowedReasonsMessage creates a string describing a list of user allowed
55+
// revocation reasons. This is useful when a revocation is rejected because it
56+
// is not a valid user supplied reason and the allowed values must be
57+
// communicated.
58+
func UserAllowedReasonsMessage() string {
59+
// Build a slice of ints from the allowed reason codes.
60+
// We want a slice because iterating `UserAllowedReasons` will change order
61+
// and make the message unpredictable and cumbersome for unit testing.
62+
// We use []ints instead of []Reason to use `sort.Ints` without fuss.
63+
var allowed []int
64+
for reason, _ := range UserAllowedReasons {
65+
allowed = append(allowed, int(reason))
66+
}
67+
sort.Ints(allowed)
68+
69+
var reasonStrings []string
70+
for _, reason := range allowed {
71+
reasonStrings = append(reasonStrings, fmt.Sprintf("%s (%d)",
72+
ReasonToString[Reason(reason)], reason))
73+
}
74+
return strings.Join(reasonStrings, ", ")
75+
}

wfe2/wfe.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -749,7 +749,15 @@ func (wfe *WebFrontEndImpl) processRevocation(
749749
reason := revocation.Reason(0)
750750
if revokeRequest.Reason != nil && wfe.AcceptRevocationReason {
751751
if _, present := revocation.UserAllowedReasons[*revokeRequest.Reason]; !present {
752-
return probs.Malformed("unsupported revocation reason code provided")
752+
reasonStr, ok := revocation.ReasonToString[revocation.Reason(*revokeRequest.Reason)]
753+
if !ok {
754+
reasonStr = "unknown"
755+
}
756+
return probs.BadRevocationReason(
757+
"unsupported revocation reason code provided: %s (%d). Supported reasons: %s",
758+
reasonStr,
759+
*revokeRequest.Reason,
760+
revocation.UserAllowedReasonsMessage())
753761
}
754762
reason = *revokeRequest.Reason
755763
}

wfe2/wfe_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2631,13 +2631,13 @@ func TestRevokeCertificateReasons(t *testing.T) {
26312631
Name: "Unsupported reason",
26322632
Reason: &reason2,
26332633
ExpectedHTTPCode: http.StatusBadRequest,
2634-
ExpectedBody: `{"type":"` + probs.V2ErrorNS + `malformed","detail":"unsupported revocation reason code provided","status":400}`,
2634+
ExpectedBody: `{"type":"` + probs.V2ErrorNS + `badRevocationReason","detail":"unsupported revocation reason code provided: cACompromise (2). Supported reasons: unspecified (0), keyCompromise (1), affiliationChanged (3), superseded (4), cessationOfOperation (5)","status":400}`,
26352635
},
26362636
{
26372637
Name: "Non-existent reason",
26382638
Reason: &reason100,
26392639
ExpectedHTTPCode: http.StatusBadRequest,
2640-
ExpectedBody: `{"type":"` + probs.V2ErrorNS + `malformed","detail":"unsupported revocation reason code provided","status":400}`,
2640+
ExpectedBody: `{"type":"` + probs.V2ErrorNS + `badRevocationReason","detail":"unsupported revocation reason code provided: unknown (100). Supported reasons: unspecified (0), keyCompromise (1), affiliationChanged (3), superseded (4), cessationOfOperation (5)","status":400}`,
26412641
},
26422642
}
26432643

0 commit comments

Comments
 (0)