use ProblemDetails inside of wfe
This uses ProblemDetails throughout the wfe. This is the last step in allowing the backend services to pass ProblemDetails from RPCs through to the user. Updates #1153. Fixes #1161.
This commit is contained in:
parent
a63a88f7a7
commit
6b0e53b8e0
47
core/util.go
47
core/util.go
|
|
@ -28,6 +28,7 @@ import (
|
|||
"io/ioutil"
|
||||
"math/big"
|
||||
mrand "math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
|
@ -35,6 +36,7 @@ import (
|
|||
|
||||
jose "github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
blog "github.com/letsencrypt/boulder/log"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
)
|
||||
|
||||
// Package Variables Variables
|
||||
|
|
@ -123,6 +125,51 @@ func (e TooManyRPCRequestsError) Error() string { return string(e) }
|
|||
func (e ServiceUnavailableError) Error() string { return string(e) }
|
||||
func (e BadNonceError) Error() string { return string(e) }
|
||||
|
||||
// statusTooManyRequests is the HTTP status code meant for rate limiting
|
||||
// errors. It's not currently in the net/http library so we add it here.
|
||||
const statusTooManyRequests = 429
|
||||
|
||||
// ProblemDetailsForError turns an error into a ProblemDetails with the special
|
||||
// case of returning the same error back if its already a ProblemDetails. If the
|
||||
// error is of an type unknown to ProblemDetailsForError, it will return a
|
||||
// ServerInternal ProblemDetails.
|
||||
func ProblemDetailsForError(err error, msg string) *probs.ProblemDetails {
|
||||
switch e := err.(type) {
|
||||
case *probs.ProblemDetails:
|
||||
return e
|
||||
case MalformedRequestError, SyntaxError:
|
||||
return probs.Malformed(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case NotSupportedError:
|
||||
return &probs.ProblemDetails{
|
||||
Type: probs.ServerInternalProblem,
|
||||
Detail: fmt.Sprintf("%s :: %s", msg, err),
|
||||
HTTPStatus: http.StatusNotImplemented,
|
||||
}
|
||||
case UnauthorizedError:
|
||||
return probs.Unauthorized(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case NotFoundError:
|
||||
return probs.NotFound(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case LengthRequiredError:
|
||||
prob := probs.Malformed("missing Content-Length header")
|
||||
prob.HTTPStatus = http.StatusLengthRequired
|
||||
return prob
|
||||
case SignatureValidationError:
|
||||
return probs.Malformed(fmt.Sprintf("%s :: %s", msg, err))
|
||||
case RateLimitedError:
|
||||
return &probs.ProblemDetails{
|
||||
Type: probs.RateLimitedProblem,
|
||||
Detail: fmt.Sprintf("%s :: %s", msg, err),
|
||||
HTTPStatus: statusTooManyRequests,
|
||||
}
|
||||
case BadNonceError:
|
||||
return probs.BadNonce(fmt.Sprintf("%s :: %s", msg, err))
|
||||
default:
|
||||
// Internal server error messages may include sensitive data, so we do
|
||||
// not include it.
|
||||
return probs.ServerInternal(msg)
|
||||
}
|
||||
}
|
||||
|
||||
// Base64 functions
|
||||
|
||||
func pad(x string) string {
|
||||
|
|
|
|||
|
|
@ -11,10 +11,12 @@ import (
|
|||
"math"
|
||||
"math/big"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
"testing"
|
||||
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
|
|
@ -128,3 +130,39 @@ func TestUnmarshalAcmeURL(t *testing.T) {
|
|||
t.Errorf("Expected error parsing ':', but got nil err.")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProblemDetailsFromError(t *testing.T) {
|
||||
testCases := []struct {
|
||||
err error
|
||||
statusCode int
|
||||
problem probs.ProblemType
|
||||
}{
|
||||
{InternalServerError("foo"), 500, probs.ServerInternalProblem},
|
||||
{NotSupportedError("foo"), 501, probs.ServerInternalProblem},
|
||||
{MalformedRequestError("foo"), 400, probs.MalformedProblem},
|
||||
{UnauthorizedError("foo"), 403, probs.UnauthorizedProblem},
|
||||
{NotFoundError("foo"), 404, probs.MalformedProblem},
|
||||
{SyntaxError("foo"), 400, probs.MalformedProblem},
|
||||
{SignatureValidationError("foo"), 400, probs.MalformedProblem},
|
||||
{RateLimitedError("foo"), 429, probs.RateLimitedProblem},
|
||||
{LengthRequiredError("foo"), 411, probs.MalformedProblem},
|
||||
{BadNonceError("foo"), 400, probs.BadNonceProblem},
|
||||
}
|
||||
for _, c := range testCases {
|
||||
p := ProblemDetailsForError(c.err, "k")
|
||||
if p.HTTPStatus != c.statusCode {
|
||||
t.Errorf("Incorrect status code for %s. Expected %d, got %d", reflect.TypeOf(c.err).Name(), c.statusCode, p.HTTPStatus)
|
||||
}
|
||||
if probs.ProblemType(p.Type) != c.problem {
|
||||
t.Errorf("Expected problem urn %#v, got %#v", c.problem, p.Type)
|
||||
}
|
||||
}
|
||||
|
||||
expected := &probs.ProblemDetails{
|
||||
Type: probs.MalformedProblem,
|
||||
HTTPStatus: 200,
|
||||
Detail: "gotcha",
|
||||
}
|
||||
p := ProblemDetailsForError(expected, "k")
|
||||
test.AssertDeepEquals(t, expected, p)
|
||||
}
|
||||
|
|
|
|||
121
probs/probs.go
121
probs/probs.go
|
|
@ -1,6 +1,9 @@
|
|||
package probs
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// Error types that can be used in ACME payloads
|
||||
const (
|
||||
|
|
@ -20,11 +23,121 @@ type ProblemType string
|
|||
// ProblemDetails objects represent problem documents
|
||||
// https://tools.ietf.org/html/draft-ietf-appsawg-http-problem-00
|
||||
type ProblemDetails struct {
|
||||
Type ProblemType `json:"type,omitempty"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
HTTPStatus int `json:"status,omitempty"`
|
||||
Type ProblemType `json:"type,omitempty"`
|
||||
Detail string `json:"detail,omitempty"`
|
||||
// HTTPStatus is the HTTP status code the ProblemDetails should probably be sent
|
||||
// as.
|
||||
HTTPStatus int `json:"status,omitempty"`
|
||||
}
|
||||
|
||||
func (pd *ProblemDetails) Error() string {
|
||||
return fmt.Sprintf("%s :: %s", pd.Type, pd.Detail)
|
||||
}
|
||||
|
||||
// statusTooManyRequests is the HTTP status code meant for rate limiting
|
||||
// errors. It's not currently in the net/http library so we add it here.
|
||||
const statusTooManyRequests = 429
|
||||
|
||||
// ProblemDetailsToStatusCode inspects the given ProblemDetails to figure out
|
||||
// what HTTP status code it should represent. It should only be used by the WFE
|
||||
// but is included in this package because of its reliance on ProblemTypes.
|
||||
func ProblemDetailsToStatusCode(prob *ProblemDetails) int {
|
||||
if prob.HTTPStatus != 0 {
|
||||
return prob.HTTPStatus
|
||||
}
|
||||
switch prob.Type {
|
||||
case ConnectionProblem, MalformedProblem, TLSProblem, UnknownHostProblem, BadNonceProblem:
|
||||
return http.StatusBadRequest
|
||||
case ServerInternalProblem:
|
||||
return http.StatusInternalServerError
|
||||
case UnauthorizedProblem:
|
||||
return http.StatusForbidden
|
||||
case RateLimitedProblem:
|
||||
return statusTooManyRequests
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
// BadNonce returns a ProblemDetails with a BadNonceProblem and a 400 Bad
|
||||
// Request status code.
|
||||
func BadNonce(detail string) *ProblemDetails {
|
||||
return &ProblemDetails{
|
||||
Type: BadNonceProblem,
|
||||
Detail: detail,
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
// Conflict returns a ProblemDetails with a MalformedProblem and a 409 Conflict
|
||||
// status code.
|
||||
func Conflict(detail string) *ProblemDetails {
|
||||
return &ProblemDetails{
|
||||
Type: MalformedProblem,
|
||||
Detail: detail,
|
||||
HTTPStatus: http.StatusConflict,
|
||||
}
|
||||
}
|
||||
|
||||
// Malformed returns a ProblemDetails with a MalformedProblem and a 400 Bad
|
||||
// Request status code.
|
||||
func Malformed(detail string, args ...interface{}) *ProblemDetails {
|
||||
if len(args) > 0 {
|
||||
detail = fmt.Sprintf(detail, args...)
|
||||
}
|
||||
return &ProblemDetails{
|
||||
Type: MalformedProblem,
|
||||
Detail: detail,
|
||||
HTTPStatus: http.StatusBadRequest,
|
||||
}
|
||||
}
|
||||
|
||||
// NotFound returns a ProblemDetails with a MalformedProblem and a 404 Not Found
|
||||
// status code.
|
||||
func NotFound(detail string) *ProblemDetails {
|
||||
return &ProblemDetails{
|
||||
Type: MalformedProblem,
|
||||
Detail: detail,
|
||||
HTTPStatus: http.StatusNotFound,
|
||||
}
|
||||
}
|
||||
|
||||
// ServerInternal returns a ProblemDetails with a ServerInternalProblem and a
|
||||
// 500 Internal Server Failure status code.
|
||||
func ServerInternal(detail string) *ProblemDetails {
|
||||
return &ProblemDetails{
|
||||
Type: ServerInternalProblem,
|
||||
Detail: detail,
|
||||
HTTPStatus: http.StatusInternalServerError,
|
||||
}
|
||||
}
|
||||
|
||||
// Unauthorized returns a ProblemDetails with an UnauthorizedProblem and a 403
|
||||
// Forbidden status code.
|
||||
func Unauthorized(detail string) *ProblemDetails {
|
||||
return &ProblemDetails{
|
||||
Type: UnauthorizedProblem,
|
||||
Detail: detail,
|
||||
HTTPStatus: http.StatusForbidden,
|
||||
}
|
||||
}
|
||||
|
||||
// MethodNotAllowed returns a ProblemDetails representing a disallowed HTTP
|
||||
// method error.
|
||||
func MethodNotAllowed() *ProblemDetails {
|
||||
return &ProblemDetails{
|
||||
Type: MalformedProblem,
|
||||
Detail: "Method not allowed",
|
||||
HTTPStatus: http.StatusMethodNotAllowed,
|
||||
}
|
||||
}
|
||||
|
||||
// ContentLengthRequired returns a ProblemDetails representing a missing
|
||||
// Content-Length header error
|
||||
func ContentLengthRequired() *ProblemDetails {
|
||||
return &ProblemDetails{
|
||||
Type: MalformedProblem,
|
||||
Detail: "missing Content-Length header",
|
||||
HTTPStatus: http.StatusLengthRequired,
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -8,7 +8,9 @@ import (
|
|||
|
||||
func TestProblemDetails(t *testing.T) {
|
||||
pd := &ProblemDetails{
|
||||
Type: MalformedProblem,
|
||||
Detail: "Wat? o.O"}
|
||||
Type: MalformedProblem,
|
||||
Detail: "Wat? o.O",
|
||||
HTTPStatus: 403,
|
||||
}
|
||||
test.AssertEquals(t, pd.Error(), "urn:acme:error:malformed :: Wat? o.O")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ import (
|
|||
|
||||
func TestRejectsNone(t *testing.T) {
|
||||
wfe, _ := setupWFE(t)
|
||||
_, _, _, err := wfe.verifyPOST(newRequestEvent(), makePostRequest(`
|
||||
_, _, _, prob := wfe.verifyPOST(newRequestEvent(), makePostRequest(`
|
||||
{
|
||||
"header": {
|
||||
"alg": "none",
|
||||
|
|
@ -24,17 +24,17 @@ func TestRejectsNone(t *testing.T) {
|
|||
"signature": ""
|
||||
}
|
||||
`), true, "foo")
|
||||
if err == nil {
|
||||
if prob == nil {
|
||||
t.Fatalf("verifyPOST did not reject JWS with alg: 'none'")
|
||||
}
|
||||
if err.Error() != "algorithm 'none' in JWS header not acceptable" {
|
||||
t.Fatalf("verifyPOST rejected JWS with alg: 'none', but for wrong reason: %s", err)
|
||||
if prob.Detail != "algorithm 'none' in JWS header not acceptable" {
|
||||
t.Fatalf("verifyPOST rejected JWS with alg: 'none', but for wrong reason: %#v", prob)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRejectsHS256(t *testing.T) {
|
||||
wfe, _ := setupWFE(t)
|
||||
_, _, _, err := wfe.verifyPOST(newRequestEvent(), makePostRequest(`
|
||||
_, _, _, prob := wfe.verifyPOST(newRequestEvent(), makePostRequest(`
|
||||
{
|
||||
"header": {
|
||||
"alg": "HS256",
|
||||
|
|
@ -48,12 +48,12 @@ func TestRejectsHS256(t *testing.T) {
|
|||
"signature": ""
|
||||
}
|
||||
`), true, "foo")
|
||||
if err == nil {
|
||||
if prob == nil {
|
||||
t.Fatalf("verifyPOST did not reject JWS with alg: 'HS256'")
|
||||
}
|
||||
expected := "algorithm 'HS256' in JWS header not acceptable"
|
||||
if err.Error() != expected {
|
||||
t.Fatalf("verifyPOST rejected JWS with alg: 'none', but for wrong reason: got '%s', wanted %s", err, expected)
|
||||
if prob.Detail != expected {
|
||||
t.Fatalf("verifyPOST rejected JWS with alg: 'none', but for wrong reason: got '%s', wanted %s", prob, expected)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,12 +40,6 @@ const (
|
|||
TermsPath = "/terms"
|
||||
IssuerPath = "/acme/issuer-cert"
|
||||
BuildIDPath = "/build"
|
||||
|
||||
// StatusRateLimited is not in net/http
|
||||
StatusRateLimited = 429
|
||||
// statusBadNonce is used as a sentinel value to force sendError to send
|
||||
// the proper error type
|
||||
statusBadNonce = 1400
|
||||
)
|
||||
|
||||
// WebFrontEndImpl provides all the logic for Boulder's web-facing interface,
|
||||
|
|
@ -95,34 +89,6 @@ type WebFrontEndImpl struct {
|
|||
ShutdownKillTimeout time.Duration
|
||||
}
|
||||
|
||||
func statusCodeFromError(err error) int {
|
||||
// Populate these as needed. We probably should trim the error list in util.go
|
||||
switch err.(type) {
|
||||
case core.MalformedRequestError:
|
||||
return http.StatusBadRequest
|
||||
case core.NotSupportedError:
|
||||
return http.StatusNotImplemented
|
||||
case core.SyntaxError:
|
||||
return http.StatusBadRequest
|
||||
case core.UnauthorizedError:
|
||||
return http.StatusForbidden
|
||||
case core.NotFoundError:
|
||||
return http.StatusNotFound
|
||||
case core.LengthRequiredError:
|
||||
return http.StatusLengthRequired
|
||||
case core.SignatureValidationError:
|
||||
return http.StatusBadRequest
|
||||
case core.InternalServerError:
|
||||
return http.StatusInternalServerError
|
||||
case core.RateLimitedError:
|
||||
return StatusRateLimited
|
||||
case core.BadNonceError:
|
||||
return statusBadNonce
|
||||
default:
|
||||
return http.StatusInternalServerError
|
||||
}
|
||||
}
|
||||
|
||||
// NewWebFrontEndImpl constructs a web service for Boulder
|
||||
func NewWebFrontEndImpl(stats statsd.Statter, clk clock.Clock) (WebFrontEndImpl, error) {
|
||||
logger := blog.GetAuditLogger()
|
||||
|
|
@ -201,9 +167,8 @@ func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h wfe
|
|||
}
|
||||
|
||||
if !methodsMap[request.Method] {
|
||||
msg := "Method not allowed"
|
||||
response.Header().Set("Allow", methodsStr)
|
||||
wfe.sendError(response, logEvent, msg, nil, http.StatusMethodNotAllowed)
|
||||
wfe.sendError(response, logEvent, probs.MethodNotAllowed(), nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -318,8 +283,7 @@ func parseIDFromPath(path string) string {
|
|||
}
|
||||
|
||||
const (
|
||||
unknownKey = "No registration exists matching provided key"
|
||||
malformedJWS = "Unable to read/verify body"
|
||||
unknownKey = "No registration exists matching provided key"
|
||||
)
|
||||
|
||||
// verifyPOST reads and parses the request body, looks up the Registration
|
||||
|
|
@ -334,44 +298,39 @@ const (
|
|||
// the key itself. verifyPOST also appends its errors to requestEvent.Errors so
|
||||
// code calling it does not need to if they imediately return a response to the
|
||||
// user.
|
||||
func (wfe *WebFrontEndImpl) verifyPOST(logEvent *requestEvent, request *http.Request, regCheck bool, resource core.AcmeResource) ([]byte, *jose.JsonWebKey, core.Registration, error) {
|
||||
var err error
|
||||
func (wfe *WebFrontEndImpl) verifyPOST(logEvent *requestEvent, request *http.Request, regCheck bool, resource core.AcmeResource) ([]byte, *jose.JsonWebKey, core.Registration, *probs.ProblemDetails) {
|
||||
// TODO: We should return a pointer to a registration, which can be nil,
|
||||
// rather the a registration value with a sentinel value.
|
||||
// https://github.com/letsencrypt/boulder/issues/877
|
||||
reg := core.Registration{ID: 0}
|
||||
|
||||
if _, ok := request.Header["Content-Length"]; !ok {
|
||||
err = core.LengthRequiredError("Content-Length header is required for POST.")
|
||||
wfe.stats.Inc("WFE.HTTP.ClientErrors.LengthRequiredError", 1, 1.0)
|
||||
logEvent.AddError("missing Content-Length header on POST")
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.ContentLengthRequired()
|
||||
}
|
||||
|
||||
// Read body
|
||||
if request.Body == nil {
|
||||
err = core.MalformedRequestError("No body on POST")
|
||||
wfe.stats.Inc("WFE.Errors.NoPOSTBody", 1, 1.0)
|
||||
logEvent.AddError("no body on POST")
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.Malformed("No body on POST")
|
||||
}
|
||||
|
||||
bodyBytes, err := ioutil.ReadAll(request.Body)
|
||||
if err != nil {
|
||||
err = core.InternalServerError("unable to read request body")
|
||||
wfe.stats.Inc("WFE.Errors.UnableToReadRequestBody", 1, 1.0)
|
||||
logEvent.AddError("unable to read request body")
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.ServerInternal("unable to read request body")
|
||||
}
|
||||
|
||||
body := string(bodyBytes)
|
||||
// Parse as JWS
|
||||
parsedJws, err := jose.ParseSigned(body)
|
||||
if err != nil {
|
||||
puberr := core.SignatureValidationError("Parse error reading JWS")
|
||||
wfe.stats.Inc("WFE.Errors.UnableToParseJWS", 1, 1.0)
|
||||
logEvent.AddError("could not JSON parse body into JWS: %s", err)
|
||||
return nil, nil, reg, puberr
|
||||
return nil, nil, reg, probs.Malformed("Parse error reading JWS")
|
||||
}
|
||||
|
||||
// Verify JWS
|
||||
|
|
@ -381,24 +340,21 @@ func (wfe *WebFrontEndImpl) verifyPOST(logEvent *requestEvent, request *http.Req
|
|||
// *anyway*, so it could always lie about what key was used by faking
|
||||
// the signature itself.
|
||||
if len(parsedJws.Signatures) > 1 {
|
||||
err = core.SignatureValidationError("Too many signatures in POST body")
|
||||
wfe.stats.Inc("WFE.Errors.TooManyJWSSignaturesInPOST", 1, 1.0)
|
||||
logEvent.AddError("too many signatures in POST body: %d", len(parsedJws.Signatures))
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.Malformed("Too many signatures in POST body")
|
||||
}
|
||||
if len(parsedJws.Signatures) == 0 {
|
||||
err = core.SignatureValidationError("POST JWS not signed")
|
||||
wfe.stats.Inc("WFE.Errors.JWSNotSignedInPOST", 1, 1.0)
|
||||
logEvent.AddError("no signatures in POST body")
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.Malformed("POST JWS not signed")
|
||||
}
|
||||
|
||||
submittedKey := parsedJws.Signatures[0].Header.JsonWebKey
|
||||
if submittedKey == nil {
|
||||
err = core.SignatureValidationError("No JWK in JWS header")
|
||||
wfe.stats.Inc("WFE.Errors.NoJWKInJWSSignatureHeader", 1, 1.0)
|
||||
logEvent.AddError("no JWK in JWS signature header in POST body")
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.Malformed("No JWK in JWS header")
|
||||
}
|
||||
|
||||
var key *jose.JsonWebKey
|
||||
|
|
@ -413,14 +369,18 @@ func (wfe *WebFrontEndImpl) verifyPOST(logEvent *requestEvent, request *http.Req
|
|||
if err = core.GoodKey(submittedKey.Key); err != nil {
|
||||
wfe.stats.Inc("WFE.Errors.JWKRejectedByGoodKey", 1, 1.0)
|
||||
logEvent.AddError("JWK in request was rejected by GoodKey: %s", err)
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.Malformed(err.Error())
|
||||
}
|
||||
key = submittedKey
|
||||
} else if err != nil {
|
||||
// For all other errors, or if regCheck is true, return error immediately.
|
||||
wfe.stats.Inc("WFE.Errors.UnableToGetRegistrationByKey", 1, 1.0)
|
||||
logEvent.AddError("unable to fetch registration by the given JWK: %s", err)
|
||||
return nil, nil, reg, err
|
||||
if _, ok := err.(core.NoSuchRegistrationError); ok {
|
||||
return nil, nil, reg, probs.Unauthorized(unknownKey)
|
||||
}
|
||||
|
||||
return nil, nil, reg, core.ProblemDetailsForError(err, "")
|
||||
} else {
|
||||
// If the lookup was successful, use that key.
|
||||
key = ®.Key
|
||||
|
|
@ -430,19 +390,18 @@ func (wfe *WebFrontEndImpl) verifyPOST(logEvent *requestEvent, request *http.Req
|
|||
|
||||
if statName, err := checkAlgorithm(key, parsedJws); err != nil {
|
||||
wfe.stats.Inc(statName, 1, 1.0)
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.Malformed(err.Error())
|
||||
}
|
||||
|
||||
payload, err := parsedJws.Verify(key)
|
||||
if err != nil {
|
||||
puberr := core.SignatureValidationError("JWS verification error")
|
||||
wfe.stats.Inc("WFE.Errors.JWSVerificationFailed", 1, 1.0)
|
||||
n := len(body)
|
||||
if n > 100 {
|
||||
n = 100
|
||||
}
|
||||
logEvent.AddError("verification of JWS with the JWK failed: %v; body: %s", err, body[:n])
|
||||
return nil, nil, reg, puberr
|
||||
return nil, nil, reg, probs.Malformed("JWS verification error")
|
||||
}
|
||||
|
||||
// Check that the request has a known anti-replay nonce
|
||||
|
|
@ -450,13 +409,11 @@ func (wfe *WebFrontEndImpl) verifyPOST(logEvent *requestEvent, request *http.Req
|
|||
if err != nil || len(nonce) == 0 {
|
||||
wfe.stats.Inc("WFE.Errors.JWSMissingNonce", 1, 1.0)
|
||||
logEvent.AddError("JWS is missing an anti-replay nonce")
|
||||
err = core.BadNonceError("JWS has no anti-replay nonce")
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.BadNonce("JWS has no anti-replay nonce")
|
||||
} else if !wfe.nonceService.Valid(nonce) {
|
||||
wfe.stats.Inc("WFE.Errors.JWSInvalidNonce", 1, 1.0)
|
||||
logEvent.AddError("JWS has an invalid anti-replay nonce")
|
||||
err = core.BadNonceError(fmt.Sprintf("JWS has invalid anti-replay nonce"))
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.BadNonce("JWS has invalid anti-replay nonce")
|
||||
}
|
||||
|
||||
// Check that the "resource" field is present and has the correct value
|
||||
|
|
@ -467,69 +424,41 @@ func (wfe *WebFrontEndImpl) verifyPOST(logEvent *requestEvent, request *http.Req
|
|||
if err != nil {
|
||||
wfe.stats.Inc("WFE.Errors.UnparsableJWSPayload", 1, 1.0)
|
||||
logEvent.AddError("unable to JSON parse resource from JWS payload: %s", err)
|
||||
puberr := core.SignatureValidationError("Request payload did not parse as JSON")
|
||||
return nil, nil, reg, puberr
|
||||
return nil, nil, reg, probs.Malformed("Request payload did not parse as JSON")
|
||||
}
|
||||
if parsedRequest.Resource == "" {
|
||||
wfe.stats.Inc("WFE.Errors.NoResourceInJWSPayload", 1, 1.0)
|
||||
logEvent.AddError("JWS request payload does not specifiy a resource")
|
||||
err = core.MalformedRequestError("Request payload does not specify a resource")
|
||||
return nil, nil, reg, err
|
||||
logEvent.AddError("JWS request payload does not specify a resource")
|
||||
return nil, nil, reg, probs.Malformed("Request payload does not specify a resource")
|
||||
} else if resource != core.AcmeResource(parsedRequest.Resource) {
|
||||
wfe.stats.Inc("WFE.Errors.MismatchedResourceInJWSPayload", 1, 1.0)
|
||||
logEvent.AddError("JWS request payload does not match resource")
|
||||
err = core.MalformedRequestError(fmt.Sprintf("JWS resource payload does not match the HTTP resource: %s != %s", parsedRequest.Resource, resource))
|
||||
return nil, nil, reg, err
|
||||
return nil, nil, reg, probs.Malformed("JWS resource payload does not match the HTTP resource: %s != %s", parsedRequest.Resource, resource)
|
||||
}
|
||||
|
||||
return []byte(payload), key, reg, nil
|
||||
}
|
||||
|
||||
// Notify the client of an error condition and log it for audit purposes.
|
||||
func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, logEvent *requestEvent, msg string, detail error, code int) {
|
||||
problem := probs.ProblemDetails{Detail: msg, HTTPStatus: code}
|
||||
switch code {
|
||||
case http.StatusPreconditionFailed:
|
||||
fallthrough
|
||||
case http.StatusForbidden:
|
||||
problem.Type = probs.UnauthorizedProblem
|
||||
case http.StatusConflict:
|
||||
fallthrough
|
||||
case http.StatusMethodNotAllowed:
|
||||
fallthrough
|
||||
case http.StatusNotFound:
|
||||
fallthrough
|
||||
case http.StatusBadRequest:
|
||||
fallthrough
|
||||
case http.StatusLengthRequired:
|
||||
problem.Type = probs.MalformedProblem
|
||||
case StatusRateLimited:
|
||||
problem.Type = probs.RateLimitedProblem
|
||||
case statusBadNonce:
|
||||
problem.Type = probs.BadNonceProblem
|
||||
problem.HTTPStatus = http.StatusBadRequest
|
||||
code = http.StatusBadRequest
|
||||
default: // Either http.StatusInternalServerError or an unexpected code
|
||||
problem.Type = probs.ServerInternalProblem
|
||||
}
|
||||
// sendError sends an error response represented by the given ProblemDetails,
|
||||
// and, if the ProblemDetails.Type is ServerInternalProblem, audit logs the
|
||||
// internal ierr.
|
||||
func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, logEvent *requestEvent, prob *probs.ProblemDetails, ierr error) {
|
||||
code := probs.ProblemDetailsToStatusCode(prob)
|
||||
|
||||
// Record details to the log event
|
||||
logEvent.AddError(msg)
|
||||
logEvent.AddError(fmt.Sprintf("%d :: %s :: %s", prob.HTTPStatus, prob.Type, prob.Detail))
|
||||
|
||||
// Only audit log internal errors so users cannot purposefully cause
|
||||
// auditable events.
|
||||
if problem.Type == probs.ServerInternalProblem {
|
||||
if prob.Type == probs.ServerInternalProblem {
|
||||
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
|
||||
wfe.log.Audit(fmt.Sprintf("Internal error - %s - %s", msg, detail))
|
||||
} else if statusCodeFromError(detail) != http.StatusInternalServerError {
|
||||
// If not an internal error and problem is a custom error type
|
||||
problem.Detail += fmt.Sprintf(" :: %s", detail)
|
||||
wfe.log.Audit(fmt.Sprintf("Internal error - %s - %s", prob.Detail, ierr))
|
||||
}
|
||||
|
||||
problemDoc, err := json.Marshal(problem)
|
||||
problemDoc, err := json.Marshal(prob)
|
||||
if err != nil {
|
||||
// AUDIT[ Error Conditions ] 9cc4d537-8534-4970-8665-4b382abe82f3
|
||||
wfe.log.Audit(fmt.Sprintf("Could not marshal error message: %s - %+v", err, problem))
|
||||
wfe.log.Audit(fmt.Sprintf("Could not marshal error message: %s - %+v", err, prob))
|
||||
problemDoc = []byte("{\"detail\": \"Problem marshalling error message.\"}")
|
||||
}
|
||||
|
||||
|
|
@ -540,7 +469,7 @@ func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, logEvent *re
|
|||
response.Write(problemDoc)
|
||||
|
||||
wfe.stats.Inc(fmt.Sprintf("WFE.HTTP.ErrorCodes.%d", code), 1, 1.0)
|
||||
problemSegments := strings.Split(string(problem.Type), ":")
|
||||
problemSegments := strings.Split(string(prob.Type), ":")
|
||||
if len(problemSegments) > 0 {
|
||||
wfe.stats.Inc(fmt.Sprintf("WFE.HTTP.ProblemTypes.%s", problemSegments[len(problemSegments)-1]), 1, 1.0)
|
||||
}
|
||||
|
|
@ -553,28 +482,29 @@ func link(url, relation string) string {
|
|||
// NewRegistration is used by clients to submit a new registration/account
|
||||
func (wfe *WebFrontEndImpl) NewRegistration(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
|
||||
|
||||
body, key, _, err := wfe.verifyPOST(logEvent, request, false, core.ResourceNewReg)
|
||||
if err != nil {
|
||||
body, key, _, prob := wfe.verifyPOST(logEvent, request, false, core.ResourceNewReg)
|
||||
if prob != nil {
|
||||
// verifyPOST handles its own setting of logEvent.Errors
|
||||
wfe.sendError(response, logEvent, malformedJWS, err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, prob, nil)
|
||||
return
|
||||
}
|
||||
|
||||
if existingReg, err := wfe.SA.GetRegistrationByKey(*key); err == nil {
|
||||
response.Header().Set("Location", fmt.Sprintf("%s%d", wfe.RegBase, existingReg.ID))
|
||||
wfe.sendError(response, logEvent, "Registration key is already in use", nil, http.StatusConflict)
|
||||
// TODO(#595): check for missing registration err
|
||||
wfe.sendError(response, logEvent, probs.Conflict("Registration key is already in use"), err)
|
||||
return
|
||||
}
|
||||
|
||||
var init core.Registration
|
||||
err = json.Unmarshal(body, &init)
|
||||
err := json.Unmarshal(body, &init)
|
||||
if err != nil {
|
||||
wfe.sendError(response, logEvent, "Error unmarshaling JSON", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling JSON"), err)
|
||||
return
|
||||
}
|
||||
if len(init.Agreement) > 0 && init.Agreement != wfe.SubscriberAgreementURL {
|
||||
msg := fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", init.Agreement, wfe.SubscriberAgreementURL)
|
||||
wfe.sendError(response, logEvent, msg, nil, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed(msg), nil)
|
||||
return
|
||||
}
|
||||
init.Key = *key
|
||||
|
|
@ -585,7 +515,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(logEvent *requestEvent, response htt
|
|||
init.InitialIP = net.ParseIP(host)
|
||||
} else {
|
||||
logEvent.AddError("Couldn't parse RemoteAddr: %s", request.RemoteAddr)
|
||||
wfe.sendError(response, logEvent, "couldn't parse the remote (that is, the client's) address", nil, http.StatusInternalServerError)
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("couldn't parse the remote (that is, the client's) address"), nil)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -593,7 +523,7 @@ func (wfe *WebFrontEndImpl) NewRegistration(logEvent *requestEvent, response htt
|
|||
reg, err := wfe.RA.NewRegistration(init)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to create new registration: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error creating new registration", err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Error creating new registration"), err)
|
||||
return
|
||||
}
|
||||
logEvent.Requester = reg.ID
|
||||
|
|
@ -604,9 +534,10 @@ func (wfe *WebFrontEndImpl) NewRegistration(logEvent *requestEvent, response htt
|
|||
regURL := fmt.Sprintf("%s%d", wfe.RegBase, reg.ID)
|
||||
responseBody, err := json.Marshal(reg)
|
||||
if err != nil {
|
||||
// StatusInternalServerError because we just created this registration, it should be OK.
|
||||
// ServerInternal because we just created this registration, and it
|
||||
// should be OK.
|
||||
logEvent.AddError("unable to marshal registration: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error marshaling registration", err, http.StatusInternalServerError)
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling registration"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -623,30 +554,24 @@ func (wfe *WebFrontEndImpl) NewRegistration(logEvent *requestEvent, response htt
|
|||
|
||||
// NewAuthorization is used by clients to submit a new ID Authorization
|
||||
func (wfe *WebFrontEndImpl) NewAuthorization(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
|
||||
body, _, currReg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceNewAuthz)
|
||||
if err != nil {
|
||||
body, _, currReg, prob := wfe.verifyPOST(logEvent, request, true, core.ResourceNewAuthz)
|
||||
if prob != nil {
|
||||
// verifyPOST handles its own setting of logEvent.Errors
|
||||
respMsg := malformedJWS
|
||||
respCode := statusCodeFromError(err)
|
||||
if _, ok := err.(core.NoSuchRegistrationError); ok {
|
||||
respMsg = unknownKey
|
||||
respCode = http.StatusForbidden
|
||||
}
|
||||
wfe.sendError(response, logEvent, respMsg, err, respCode)
|
||||
wfe.sendError(response, logEvent, prob, nil)
|
||||
return
|
||||
}
|
||||
// Any version of the agreement is acceptable here. Version match is enforced in
|
||||
// wfe.Registration when agreeing the first time. Agreement updates happen
|
||||
// by mailing subscribers and don't require a registration update.
|
||||
if currReg.Agreement == "" {
|
||||
wfe.sendError(response, logEvent, "Must agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
|
||||
wfe.sendError(response, logEvent, probs.Unauthorized("Must agree to subscriber agreement before any further actions"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
var init core.Authorization
|
||||
if err = json.Unmarshal(body, &init); err != nil {
|
||||
if err := json.Unmarshal(body, &init); err != nil {
|
||||
logEvent.AddError("unable to JSON unmarshal Authorization: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error unmarshaling JSON", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling JSON"), err)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["Identifier"] = init.Identifier
|
||||
|
|
@ -655,7 +580,7 @@ func (wfe *WebFrontEndImpl) NewAuthorization(logEvent *requestEvent, response ht
|
|||
authz, err := wfe.RA.NewAuthorization(init, currReg.ID)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to create new authz: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error creating new authz", err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Error creating new authz"), err)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["AuthzID"] = authz.ID
|
||||
|
|
@ -665,8 +590,8 @@ func (wfe *WebFrontEndImpl) NewAuthorization(logEvent *requestEvent, response ht
|
|||
wfe.prepAuthorizationForDisplay(&authz)
|
||||
responseBody, err := json.Marshal(authz)
|
||||
if err != nil {
|
||||
// StatusInternalServerError because we generated the authz, it should be OK
|
||||
wfe.sendError(response, logEvent, "Error marshaling authz", err, http.StatusInternalServerError)
|
||||
// ServerInternal because we generated the authz, it should be OK
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling authz"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -685,10 +610,10 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(logEvent *requestEvent, response h
|
|||
|
||||
// We don't ask verifyPOST to verify there is a correponding registration,
|
||||
// because anyone with the right private key can revoke a certificate.
|
||||
body, requestKey, registration, err := wfe.verifyPOST(logEvent, request, false, core.ResourceRevokeCert)
|
||||
if err != nil {
|
||||
body, requestKey, registration, prob := wfe.verifyPOST(logEvent, request, false, core.ResourceRevokeCert)
|
||||
if prob != nil {
|
||||
// verifyPOST handles its own setting of logEvent.Errors
|
||||
wfe.sendError(response, logEvent, malformedJWS, err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, prob, nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -696,29 +621,30 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(logEvent *requestEvent, response h
|
|||
CertificateDER core.JSONBuffer `json:"certificate"`
|
||||
}
|
||||
var revokeRequest RevokeRequest
|
||||
if err = json.Unmarshal(body, &revokeRequest); err != nil {
|
||||
if err := json.Unmarshal(body, &revokeRequest); err != nil {
|
||||
logEvent.AddError(fmt.Sprintf("Couldn't unmarshal in revoke request %s", string(body)))
|
||||
wfe.sendError(response, logEvent, "Unable to read/verify body", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Unable to JSON parse revoke request"), err)
|
||||
return
|
||||
}
|
||||
providedCert, err := x509.ParseCertificate(revokeRequest.CertificateDER)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to parse revoke certificate DER: %s", err)
|
||||
wfe.sendError(response, logEvent, "Unable to read/verify body", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Unable to parse certificate DER"), err)
|
||||
return
|
||||
}
|
||||
|
||||
serial := core.SerialToString(providedCert.SerialNumber)
|
||||
logEvent.Extra["ProvidedCertificateSerial"] = serial
|
||||
cert, err := wfe.SA.GetCertificate(serial)
|
||||
// TODO(#991): handle db errors better
|
||||
if err != nil || !bytes.Equal(cert.DER, revokeRequest.CertificateDER) {
|
||||
wfe.sendError(response, logEvent, "No such certificate", err, http.StatusNotFound)
|
||||
wfe.sendError(response, logEvent, probs.NotFound("No such certificate"), err)
|
||||
return
|
||||
}
|
||||
parsedCertificate, err := x509.ParseCertificate(cert.DER)
|
||||
if err != nil {
|
||||
// InternalServerError because this is a failure to decode from our DB.
|
||||
wfe.sendError(response, logEvent, "Invalid certificate", err, http.StatusInternalServerError)
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("invalid parse of stored certificate"), err)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["RetrievedCertificateSerial"] = core.SerialToString(parsedCertificate.SerialNumber)
|
||||
|
|
@ -729,14 +655,15 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(logEvent *requestEvent, response h
|
|||
certStatus, err := wfe.SA.GetCertificateStatus(serial)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to get certificate status: %s", err)
|
||||
wfe.sendError(response, logEvent, "Certificate status not yet available", err, http.StatusNotFound)
|
||||
// TODO(#991): handle db errors
|
||||
wfe.sendError(response, logEvent, probs.NotFound("Certificate status not yet available"), err)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["CertificateStatus"] = certStatus.Status
|
||||
|
||||
if certStatus.Status == core.OCSPStatusRevoked {
|
||||
logEvent.AddError("Certificate already revoked: %#v", serial)
|
||||
wfe.sendError(response, logEvent, "Certificate already revoked", nil, http.StatusConflict)
|
||||
wfe.sendError(response, logEvent, probs.Conflict("Certificate already revoked"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -744,9 +671,8 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(logEvent *requestEvent, response h
|
|||
if !(core.KeyDigestEquals(requestKey, parsedCertificate.PublicKey) ||
|
||||
registration.ID == cert.RegistrationID) {
|
||||
wfe.sendError(response, logEvent,
|
||||
"Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it.",
|
||||
nil,
|
||||
http.StatusForbidden)
|
||||
probs.Unauthorized("Revocation request must be signed by private key of cert to be revoked, or by the account key of the account that issued it."),
|
||||
nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -754,7 +680,7 @@ func (wfe *WebFrontEndImpl) RevokeCertificate(logEvent *requestEvent, response h
|
|||
err = wfe.RA.RevokeCertificateWithReg(*parsedCertificate, 0, registration.ID)
|
||||
if err != nil {
|
||||
logEvent.AddError("failed to revoke certificate: %s", err)
|
||||
wfe.sendError(response, logEvent, "Failed to revoke certificate", err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Failed to revoke certificate"), err)
|
||||
} else {
|
||||
wfe.log.Debug(fmt.Sprintf("Revoked %v", serial))
|
||||
response.WriteHeader(http.StatusOK)
|
||||
|
|
@ -777,30 +703,24 @@ func (wfe *WebFrontEndImpl) logCsr(request *http.Request, cr core.CertificateReq
|
|||
// NewCertificate is used by clients to request the issuance of a cert for an
|
||||
// authorized identifier.
|
||||
func (wfe *WebFrontEndImpl) NewCertificate(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
|
||||
body, _, reg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceNewCert)
|
||||
if err != nil {
|
||||
body, _, reg, prob := wfe.verifyPOST(logEvent, request, true, core.ResourceNewCert)
|
||||
if prob != nil {
|
||||
// verifyPOST handles its own setting of logEvent.Errors
|
||||
respMsg := malformedJWS
|
||||
respCode := statusCodeFromError(err)
|
||||
if _, ok := err.(core.NoSuchRegistrationError); ok {
|
||||
respMsg = unknownKey
|
||||
respCode = http.StatusForbidden
|
||||
}
|
||||
wfe.sendError(response, logEvent, respMsg, err, respCode)
|
||||
wfe.sendError(response, logEvent, prob, nil)
|
||||
return
|
||||
}
|
||||
// Any version of the agreement is acceptable here. Version match is enforced in
|
||||
// wfe.Registration when agreeing the first time. Agreement updates happen
|
||||
// by mailing subscribers and don't require a registration update.
|
||||
if reg.Agreement == "" {
|
||||
wfe.sendError(response, logEvent, "Must agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
|
||||
wfe.sendError(response, logEvent, probs.Unauthorized("Must agree to subscriber agreement before any further actions"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
var certificateRequest core.CertificateRequest
|
||||
if err = json.Unmarshal(body, &certificateRequest); err != nil {
|
||||
if err := json.Unmarshal(body, &certificateRequest); err != nil {
|
||||
logEvent.AddError("unable to JSON unmarshal CertificateRequest: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error unmarshaling certificate request", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling certificate request"), err)
|
||||
return
|
||||
}
|
||||
wfe.logCsr(request, certificateRequest, reg)
|
||||
|
|
@ -810,9 +730,9 @@ func (wfe *WebFrontEndImpl) NewCertificate(logEvent *requestEvent, response http
|
|||
// bytes on the wire, and (b) the CA logs all rejections as audit events, but
|
||||
// a bad key from the client is just a malformed request and doesn't need to
|
||||
// be audited.
|
||||
if err = core.GoodKey(certificateRequest.CSR.PublicKey); err != nil {
|
||||
if err := core.GoodKey(certificateRequest.CSR.PublicKey); err != nil {
|
||||
logEvent.AddError("CSR public key failed GoodKey: %s", err)
|
||||
wfe.sendError(response, logEvent, "Invalid key in certificate request", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Invalid key in certificate request :: %s", err), err)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["CSRDNSNames"] = certificateRequest.CSR.DNSNames
|
||||
|
|
@ -828,7 +748,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(logEvent *requestEvent, response http
|
|||
cert, err := wfe.RA.NewCertificate(certificateRequest, reg.ID)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to create new cert: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error creating new cert", err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Error creating new cert"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -839,7 +759,7 @@ func (wfe *WebFrontEndImpl) NewCertificate(logEvent *requestEvent, response http
|
|||
parsedCertificate, err := x509.ParseCertificate([]byte(cert.DER))
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to parse certificate: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error creating new cert", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Unable to parse certificate"), err)
|
||||
return
|
||||
}
|
||||
serial := parsedCertificate.SerialNumber
|
||||
|
|
@ -864,7 +784,7 @@ func (wfe *WebFrontEndImpl) Challenge(
|
|||
request *http.Request) {
|
||||
|
||||
notFound := func() {
|
||||
wfe.sendError(response, logEvent, "No such challenge", nil, http.StatusNotFound)
|
||||
wfe.sendError(response, logEvent, probs.NotFound("No such challenge"), nil)
|
||||
}
|
||||
|
||||
// Challenge URIs are of the form /acme/challenge/<auth id>/<challenge id>.
|
||||
|
|
@ -886,6 +806,7 @@ func (wfe *WebFrontEndImpl) Challenge(
|
|||
|
||||
authz, err := wfe.SA.GetAuthorization(authorizationID)
|
||||
if err != nil {
|
||||
// TODO(#1198): handle db errors etc
|
||||
notFound()
|
||||
return
|
||||
}
|
||||
|
|
@ -893,7 +814,7 @@ func (wfe *WebFrontEndImpl) Challenge(
|
|||
// After expiring, challenges are inaccessible
|
||||
if authz.Expires == nil || authz.Expires.Before(wfe.clk.Now()) {
|
||||
logEvent.AddError("Authorization %v expired in the past (%v)", authz.ID, *authz.Expires)
|
||||
wfe.sendError(response, logEvent, "Expired authorization", nil, http.StatusNotFound)
|
||||
wfe.sendError(response, logEvent, probs.NotFound("Expired authorization"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -957,7 +878,7 @@ func (wfe *WebFrontEndImpl) getChallenge(
|
|||
// InternalServerError because this is a failure to decode data passed in
|
||||
// by the caller, which got it from the DB.
|
||||
logEvent.AddError("unable to marshal challenge: %s", err)
|
||||
wfe.sendError(response, logEvent, "Failed to marshal challenge", err, http.StatusInternalServerError)
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("Failed to marshal challenge"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -979,23 +900,17 @@ func (wfe *WebFrontEndImpl) postChallenge(
|
|||
authz core.Authorization,
|
||||
challengeIndex int,
|
||||
logEvent *requestEvent) {
|
||||
body, _, currReg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceChallenge)
|
||||
if err != nil {
|
||||
body, _, currReg, prob := wfe.verifyPOST(logEvent, request, true, core.ResourceChallenge)
|
||||
if prob != nil {
|
||||
// verifyPOST handles its own setting of logEvent.Errors
|
||||
respMsg := malformedJWS
|
||||
respCode := http.StatusBadRequest
|
||||
if _, ok := err.(core.NoSuchRegistrationError); ok {
|
||||
respMsg = unknownKey
|
||||
respCode = http.StatusForbidden
|
||||
}
|
||||
wfe.sendError(response, logEvent, respMsg, err, respCode)
|
||||
wfe.sendError(response, logEvent, prob, nil)
|
||||
return
|
||||
}
|
||||
// Any version of the agreement is acceptable here. Version match is enforced in
|
||||
// wfe.Registration when agreeing the first time. Agreement updates happen
|
||||
// by mailing subscribers and don't require a registration update.
|
||||
if currReg.Agreement == "" {
|
||||
wfe.sendError(response, logEvent, "Registration didn't agree to subscriber agreement before any further actions", nil, http.StatusForbidden)
|
||||
wfe.sendError(response, logEvent, probs.Unauthorized("Registration didn't agree to subscriber agreement before any further actions"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1003,16 +918,18 @@ func (wfe *WebFrontEndImpl) postChallenge(
|
|||
// the registration ID on the authz object
|
||||
if currReg.ID != authz.RegistrationID {
|
||||
logEvent.AddError("User registration id: %d != Authorization registration id: %v", currReg.ID, authz.RegistrationID)
|
||||
wfe.sendError(response, logEvent, "User registration ID doesn't match registration ID in authorization",
|
||||
wfe.sendError(response,
|
||||
logEvent,
|
||||
probs.Unauthorized("User registration ID doesn't match registration ID in authorization"),
|
||||
nil,
|
||||
http.StatusForbidden)
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
var challengeUpdate core.Challenge
|
||||
if err = json.Unmarshal(body, &challengeUpdate); err != nil {
|
||||
if err := json.Unmarshal(body, &challengeUpdate); err != nil {
|
||||
logEvent.AddError("error JSON unmarshalling challenge response: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error unmarshaling challenge response", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling challenge response"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1020,7 +937,7 @@ func (wfe *WebFrontEndImpl) postChallenge(
|
|||
updatedAuthorization, err := wfe.RA.UpdateAuthorization(authz, challengeIndex, challengeUpdate)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to update challenge: %s", err)
|
||||
wfe.sendError(response, logEvent, "Unable to update challenge", err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Unable to update challenge"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1029,9 +946,9 @@ func (wfe *WebFrontEndImpl) postChallenge(
|
|||
wfe.prepChallengeForDisplay(authz, &challenge)
|
||||
jsonReply, err := json.Marshal(challenge)
|
||||
if err != nil {
|
||||
// StatusInternalServerError because we made the challenges, they should be OK
|
||||
// ServerInternal because we made the challenges, they should be OK
|
||||
logEvent.AddError("failed to marshal challenge: %s", err)
|
||||
wfe.sendError(response, logEvent, "Failed to marshal challenge", err, http.StatusInternalServerError)
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("Failed to marshal challenge"), err)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1050,16 +967,10 @@ func (wfe *WebFrontEndImpl) postChallenge(
|
|||
// Registration is used by a client to submit an update to their registration.
|
||||
func (wfe *WebFrontEndImpl) Registration(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) {
|
||||
|
||||
body, _, currReg, err := wfe.verifyPOST(logEvent, request, true, core.ResourceRegistration)
|
||||
if err != nil {
|
||||
body, _, currReg, prob := wfe.verifyPOST(logEvent, request, true, core.ResourceRegistration)
|
||||
if prob != nil {
|
||||
// verifyPOST handles its own setting of logEvent.Errors
|
||||
respMsg := malformedJWS
|
||||
respCode := statusCodeFromError(err)
|
||||
if _, ok := err.(core.NoSuchRegistrationError); ok {
|
||||
respMsg = unknownKey
|
||||
respCode = http.StatusForbidden
|
||||
}
|
||||
wfe.sendError(response, logEvent, respMsg, err, respCode)
|
||||
wfe.sendError(response, logEvent, prob, nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1069,16 +980,16 @@ func (wfe *WebFrontEndImpl) Registration(logEvent *requestEvent, response http.R
|
|||
id, err := strconv.ParseInt(idStr, 10, 64)
|
||||
if err != nil {
|
||||
logEvent.AddError("registration ID must be an integer, was %#v", idStr)
|
||||
wfe.sendError(response, logEvent, "Registration ID must be an integer", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Registration ID must be an integer"), err)
|
||||
return
|
||||
} else if id <= 0 {
|
||||
msg := fmt.Sprintf("Registration ID must be a positive non-zero integer, was %d", id)
|
||||
logEvent.AddError(msg)
|
||||
wfe.sendError(response, logEvent, msg, nil, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed(msg), nil)
|
||||
return
|
||||
} else if id != currReg.ID {
|
||||
logEvent.AddError("Request signing key did not match registration key: %d != %d", id, currReg.ID)
|
||||
wfe.sendError(response, logEvent, "Request signing key did not match registration key", nil, http.StatusForbidden)
|
||||
wfe.sendError(response, logEvent, probs.Unauthorized("Request signing key did not match registration key"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1086,14 +997,14 @@ func (wfe *WebFrontEndImpl) Registration(logEvent *requestEvent, response http.R
|
|||
err = json.Unmarshal(body, &update)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to JSON parse registration: %s", err)
|
||||
wfe.sendError(response, logEvent, "Error unmarshaling registration", err, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling registration"), err)
|
||||
return
|
||||
}
|
||||
|
||||
if len(update.Agreement) > 0 && update.Agreement != wfe.SubscriberAgreementURL {
|
||||
msg := fmt.Sprintf("Provided agreement URL [%s] does not match current agreement URL [%s]", update.Agreement, wfe.SubscriberAgreementURL)
|
||||
logEvent.AddError(msg)
|
||||
wfe.sendError(response, logEvent, msg, nil, http.StatusBadRequest)
|
||||
wfe.sendError(response, logEvent, probs.Malformed(msg), nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1107,15 +1018,15 @@ func (wfe *WebFrontEndImpl) Registration(logEvent *requestEvent, response http.R
|
|||
updatedReg, err := wfe.RA.UpdateRegistration(currReg, update)
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to update registration: %s", err)
|
||||
wfe.sendError(response, logEvent, "Unable to update registration", err, statusCodeFromError(err))
|
||||
wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Unable to update registration"), err)
|
||||
return
|
||||
}
|
||||
|
||||
jsonReply, err := json.Marshal(updatedReg)
|
||||
if err != nil {
|
||||
// StatusInternalServerError because we just generated the reg, it should be OK
|
||||
// ServerInternal because we just generated the reg, it should be OK
|
||||
logEvent.AddError("unable to marshal updated registration: %s", err)
|
||||
wfe.sendError(response, logEvent, "Failed to marshal registration", err, http.StatusInternalServerError)
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("Failed to marshal registration"), err)
|
||||
return
|
||||
}
|
||||
response.Header().Set("Content-Type", "application/json")
|
||||
|
|
@ -1135,7 +1046,8 @@ func (wfe *WebFrontEndImpl) Authorization(logEvent *requestEvent, response http.
|
|||
authz, err := wfe.SA.GetAuthorization(id)
|
||||
if err != nil {
|
||||
logEvent.AddError("No such authorization at id %s", id)
|
||||
wfe.sendError(response, logEvent, "Unable to find authorization", err, http.StatusNotFound)
|
||||
// TODO(#1199): handle db errors
|
||||
wfe.sendError(response, logEvent, probs.NotFound("Unable to find authorization"), err)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["AuthorizationID"] = authz.ID
|
||||
|
|
@ -1148,7 +1060,7 @@ func (wfe *WebFrontEndImpl) Authorization(logEvent *requestEvent, response http.
|
|||
if authz.Expires == nil || authz.Expires.Before(wfe.clk.Now()) {
|
||||
msg := fmt.Sprintf("Authorization %v expired in the past (%v)", authz.ID, *authz.Expires)
|
||||
logEvent.AddError(msg)
|
||||
wfe.sendError(response, logEvent, "Expired authorization", nil, http.StatusNotFound)
|
||||
wfe.sendError(response, logEvent, probs.NotFound("Expired authorization"), nil)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
@ -1158,7 +1070,7 @@ func (wfe *WebFrontEndImpl) Authorization(logEvent *requestEvent, response http.
|
|||
if err != nil {
|
||||
// InternalServerError because this is a failure to decode from our DB.
|
||||
logEvent.AddError("Failed to JSON marshal authz: %s", err)
|
||||
wfe.sendError(response, logEvent, "Failed to JSON marshal authz", err, http.StatusInternalServerError)
|
||||
wfe.sendError(response, logEvent, probs.ServerInternal("Failed to JSON marshal authz"), err)
|
||||
return
|
||||
}
|
||||
response.Header().Add("Link", link(wfe.NewCert, "next"))
|
||||
|
|
@ -1181,27 +1093,28 @@ func (wfe *WebFrontEndImpl) Certificate(logEvent *requestEvent, response http.Re
|
|||
// digits.
|
||||
if !strings.HasPrefix(path, CertPath) {
|
||||
logEvent.AddError("this request path should not have gotten to Certificate: %#v is not a prefix of %#v", path, CertPath)
|
||||
wfe.sendError(response, logEvent, "Certificate not found", nil, http.StatusNotFound)
|
||||
wfe.sendError(response, logEvent, probs.NotFound("Certificate not found"), nil)
|
||||
addNoCacheHeader(response)
|
||||
return
|
||||
}
|
||||
serial := path[len(CertPath):]
|
||||
if !core.ValidSerial(serial) {
|
||||
logEvent.AddError("certificate serial provided was not valid: %s", serial)
|
||||
wfe.sendError(response, logEvent, "Certificate not found", nil, http.StatusNotFound)
|
||||
wfe.sendError(response, logEvent, probs.NotFound("Certificate not found"), nil)
|
||||
addNoCacheHeader(response)
|
||||
return
|
||||
}
|
||||
logEvent.Extra["RequestedSerial"] = serial
|
||||
|
||||
cert, err := wfe.SA.GetCertificate(serial)
|
||||
// TODO(#991): handle db errors
|
||||
if err != nil {
|
||||
logEvent.AddError("unable to get certificate by serial id %#v: %s", serial, err)
|
||||
if strings.HasPrefix(err.Error(), "gorp: multiple rows returned") {
|
||||
wfe.sendError(response, logEvent, "Multiple certificates with same short serial", err, http.StatusConflict)
|
||||
wfe.sendError(response, logEvent, probs.Conflict("Multiple certificates with same short serial"), err)
|
||||
} else {
|
||||
addNoCacheHeader(response)
|
||||
wfe.sendError(response, logEvent, "Certificate not found", err, http.StatusNotFound)
|
||||
wfe.sendError(response, logEvent, probs.NotFound("Certificate not found"), err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ import (
|
|||
"net/http"
|
||||
"net/http/httptest"
|
||||
"net/url"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
|
@ -27,6 +26,7 @@ import (
|
|||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
|
||||
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
|
||||
"github.com/letsencrypt/boulder/cmd"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
|
|
@ -571,14 +571,14 @@ func TestIssueCertificate(t *testing.T) {
|
|||
})
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: No body on POST","status":400}`)
|
||||
`{"type":"urn:acme:error:malformed","detail":"No body on POST","status":400}`)
|
||||
|
||||
// POST, but body that isn't valid JWS
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewCertificate(newRequestEvent(), responseWriter, makePostRequest("hi"))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS","status":400}`)
|
||||
`{"type":"urn:acme:error:malformed","detail":"Parse error reading JWS","status":400}`)
|
||||
|
||||
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
|
||||
responseWriter.Body.Reset()
|
||||
|
|
@ -586,7 +586,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
makePostRequest(signRequest(t, "foo", wfe.nonceService)))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload did not parse as JSON","status":400}`)
|
||||
`{"type":"urn:acme:error:malformed","detail":"Request payload did not parse as JSON","status":400}`)
|
||||
|
||||
// Valid, signed JWS body, payload is '{}'
|
||||
responseWriter.Body.Reset()
|
||||
|
|
@ -595,7 +595,7 @@ func TestIssueCertificate(t *testing.T) {
|
|||
signRequest(t, "{}", wfe.nonceService)))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload does not specify a resource","status":400}`)
|
||||
`{"type":"urn:acme:error:malformed","detail":"Request payload does not specify a resource","status":400}`)
|
||||
|
||||
// Valid, signed JWS body, payload is '{"resource":"new-cert"}'
|
||||
responseWriter.Body.Reset()
|
||||
|
|
@ -755,7 +755,7 @@ func TestBadNonce(t *testing.T) {
|
|||
test.AssertNotError(t, err, "Failed to sign body")
|
||||
wfe.NewRegistration(newRequestEvent(), responseWriter,
|
||||
makePostRequest(result.FullSerialize()))
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:badNonce","detail":"Unable to read/verify body :: JWS has no anti-replay nonce","status":400}`)
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:badNonce","detail":"JWS has no anti-replay nonce","status":400}`)
|
||||
}
|
||||
|
||||
func TestNewRegistration(t *testing.T) {
|
||||
|
|
@ -799,19 +799,19 @@ func TestNewRegistration(t *testing.T) {
|
|||
"Content-Length": []string{"0"},
|
||||
},
|
||||
},
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: No body on POST","status":400}`,
|
||||
`{"type":"urn:acme:error:malformed","detail":"No body on POST","status":400}`,
|
||||
},
|
||||
|
||||
// POST, but body that isn't valid JWS
|
||||
{
|
||||
makePostRequestWithPath(NewRegPath, "hi"),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS","status":400}`,
|
||||
`{"type":"urn:acme:error:malformed","detail":"Parse error reading JWS","status":400}`,
|
||||
},
|
||||
|
||||
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
|
||||
{
|
||||
makePostRequestWithPath(NewRegPath, fooBody.FullSerialize()),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload did not parse as JSON","status":400}`,
|
||||
`{"type":"urn:acme:error:malformed","detail":"Request payload did not parse as JSON","status":400}`,
|
||||
},
|
||||
|
||||
// Same signed body, but payload modified by one byte, breaking signature.
|
||||
|
|
@ -831,7 +831,7 @@ func TestNewRegistration(t *testing.T) {
|
|||
"signature": "RjUQ679fxJgeAJlxqgvDP_sfGZnJ-1RgWF2qmcbnBWljs6h1qp63pLnJOl13u81bP_bCSjaWkelGG8Ymx_X-aQ"
|
||||
}
|
||||
`),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: JWS verification error","status":400}`,
|
||||
`{"type":"urn:acme:error:malformed","detail":"JWS verification error","status":400}`,
|
||||
},
|
||||
{
|
||||
makePostRequestWithPath(NewRegPath, wrongAgreementBody.FullSerialize()),
|
||||
|
|
@ -1062,12 +1062,12 @@ func TestAuthorization(t *testing.T) {
|
|||
"Content-Length": []string{"0"},
|
||||
},
|
||||
})
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: No body on POST","status":400}`)
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"No body on POST","status":400}`)
|
||||
|
||||
// POST, but body that isn't valid JWS
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewAuthorization(newRequestEvent(), responseWriter, makePostRequest("hi"))
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS","status":400}`)
|
||||
test.AssertEquals(t, responseWriter.Body.String(), `{"type":"urn:acme:error:malformed","detail":"Parse error reading JWS","status":400}`)
|
||||
|
||||
// POST, Properly JWS-signed, but payload is "foo", not base64-encoded JSON.
|
||||
responseWriter.Body.Reset()
|
||||
|
|
@ -1075,7 +1075,7 @@ func TestAuthorization(t *testing.T) {
|
|||
makePostRequest(signRequest(t, "foo", wfe.nonceService)))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Request payload did not parse as JSON","status":400}`)
|
||||
`{"type":"urn:acme:error:malformed","detail":"Request payload did not parse as JSON","status":400}`)
|
||||
|
||||
// Same signed body, but payload modified by one byte, breaking signature.
|
||||
// should fail JWS verification.
|
||||
|
|
@ -1096,7 +1096,7 @@ func TestAuthorization(t *testing.T) {
|
|||
`))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: JWS verification error","status":400}`)
|
||||
`{"type":"urn:acme:error:malformed","detail":"JWS verification error","status":400}`)
|
||||
|
||||
responseWriter.Body.Reset()
|
||||
wfe.NewAuthorization(newRequestEvent(), responseWriter,
|
||||
|
|
@ -1167,7 +1167,7 @@ func TestRegistration(t *testing.T) {
|
|||
wfe.Registration(newRequestEvent(), responseWriter, makePostRequestWithPath("/2", "invalid"))
|
||||
test.AssertEquals(t,
|
||||
responseWriter.Body.String(),
|
||||
`{"type":"urn:acme:error:malformed","detail":"Unable to read/verify body :: Parse error reading JWS","status":400}`)
|
||||
`{"type":"urn:acme:error:malformed","detail":"Parse error reading JWS","status":400}`)
|
||||
responseWriter.Body.Reset()
|
||||
|
||||
key, err := jose.LoadPrivateKey([]byte(test2KeyPrivatePEM))
|
||||
|
|
@ -1350,13 +1350,13 @@ func TestLogCsrPem(t *testing.T) {
|
|||
|
||||
func TestLengthRequired(t *testing.T) {
|
||||
wfe, _ := setupWFE(t)
|
||||
_, _, _, err := wfe.verifyPOST(newRequestEvent(), &http.Request{
|
||||
_, _, _, prob := wfe.verifyPOST(newRequestEvent(), &http.Request{
|
||||
Method: "POST",
|
||||
URL: mustParseURL("/"),
|
||||
}, false, "resource")
|
||||
test.Assert(t, err != nil, "No error returned for request body missing Content-Length.")
|
||||
_, ok := err.(core.LengthRequiredError)
|
||||
test.Assert(t, ok, "Error code for missing content-length wasn't 411.")
|
||||
test.Assert(t, prob != nil, "No error returned for request body missing Content-Length.")
|
||||
test.AssertEquals(t, probs.MalformedProblem, prob.Type)
|
||||
test.AssertEquals(t, http.StatusLengthRequired, prob.HTTPStatus)
|
||||
}
|
||||
|
||||
type mockSADifferentStoredKey struct {
|
||||
|
|
@ -1400,30 +1400,6 @@ func TestBadKeyCSR(t *testing.T) {
|
|||
`{"type":"urn:acme:error:malformed","detail":"Invalid key in certificate request :: Key too small: 512","status":400}`)
|
||||
}
|
||||
|
||||
func TestStatusCodeFromError(t *testing.T) {
|
||||
testCases := []struct {
|
||||
err error
|
||||
statusCode int
|
||||
}{
|
||||
{core.InternalServerError("foo"), 500},
|
||||
{core.NotSupportedError("foo"), 501},
|
||||
{core.MalformedRequestError("foo"), 400},
|
||||
{core.UnauthorizedError("foo"), 403},
|
||||
{core.NotFoundError("foo"), 404},
|
||||
{core.SyntaxError("foo"), 400},
|
||||
{core.SignatureValidationError("foo"), 400},
|
||||
{core.RateLimitedError("foo"), 429},
|
||||
{core.LengthRequiredError("foo"), 411},
|
||||
{core.BadNonceError("foo"), statusBadNonce},
|
||||
}
|
||||
for _, c := range testCases {
|
||||
got := statusCodeFromError(c.err)
|
||||
if got != c.statusCode {
|
||||
t.Errorf("Incorrect status code for %s. Expected %d, got %d", reflect.TypeOf(c.err).Name(), c.statusCode, got)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func newRequestEvent() *requestEvent {
|
||||
return &requestEvent{Extra: make(map[string]interface{})}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue