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:
Jeff Hodges 2015-11-23 13:58:10 -08:00
parent a63a88f7a7
commit 6b0e53b8e0
7 changed files with 352 additions and 263 deletions

View File

@ -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 {

View File

@ -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)
}

View File

@ -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,
}
}

View File

@ -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")
}

View File

@ -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)
}
}

View File

@ -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 = &reg.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
}

View File

@ -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{})}
}