// 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: 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.RateLimited(fmt.Sprintf("%s :: %s", msg, err)) 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) } }
// verifyPOST reads and parses the request body, looks up the Registration // corresponding to its JWK, verifies the JWS signature, checks that the // resource field is present and correct in the JWS protected header, and // returns the JWS payload bytes, the key used to verify, and the corresponding // Registration (or error). If regCheck is false, verifyPOST will still try to // look up a registration object, and will return it if found. However, if no // registration object is found, verifyPOST will attempt to verify the JWS using // the key in the JWS headers, and return the key plus a dummy registration if // successful. If a caller passes regCheck = false, it should plan on validating // the key itself. verifyPOST also appends its errors to requestEvent.Errors so // code calling it does not need to if they immediately 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, *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 { wfe.stats.Inc("WFE.HTTP.ClientErrors.LengthRequiredError", 1, 1.0) logEvent.AddError("missing Content-Length header on POST") return nil, nil, reg, probs.ContentLengthRequired() } // Read body if request.Body == nil { wfe.stats.Inc("WFE.Errors.NoPOSTBody", 1, 1.0) logEvent.AddError("no body on POST") return nil, nil, reg, probs.Malformed("No body on POST") } bodyBytes, err := ioutil.ReadAll(request.Body) if err != nil { wfe.stats.Inc("WFE.Errors.UnableToReadRequestBody", 1, 1.0) logEvent.AddError("unable to read request body") 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 { wfe.stats.Inc("WFE.Errors.UnableToParseJWS", 1, 1.0) logEvent.AddError("could not JSON parse body into JWS: %s", err) return nil, nil, reg, probs.Malformed("Parse error reading JWS") } // Verify JWS // NOTE: It might seem insecure for the WFE to be trusted to verify // client requests, i.e., that the verification should be done at the // RA. However the WFE is the RA's only view of the outside world // *anyway*, so it could always lie about what key was used by faking // the signature itself. if len(parsedJws.Signatures) > 1 { 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, probs.Malformed("Too many signatures in POST body") } if len(parsedJws.Signatures) == 0 { wfe.stats.Inc("WFE.Errors.JWSNotSignedInPOST", 1, 1.0) logEvent.AddError("no signatures in POST body") return nil, nil, reg, probs.Malformed("POST JWS not signed") } submittedKey := parsedJws.Signatures[0].Header.JsonWebKey if submittedKey == nil { wfe.stats.Inc("WFE.Errors.NoJWKInJWSSignatureHeader", 1, 1.0) logEvent.AddError("no JWK in JWS signature header in POST body") return nil, nil, reg, probs.Malformed("No JWK in JWS header") } var key *jose.JsonWebKey reg, err = wfe.SA.GetRegistrationByKey(*submittedKey) // Special case: If no registration was found, but regCheck is false, use an // empty registration and the submitted key. The caller is expected to do some // validation on the returned key. if _, ok := err.(core.NoSuchRegistrationError); ok && !regCheck { // When looking up keys from the registrations DB, we can be confident they // are "good". But when we are verifying against any submitted key, we want // to check its quality before doing the verify. if err = wfe.keyPolicy.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, 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) 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 logEvent.Requester = reg.ID logEvent.Contacts = reg.Contact } if statName, err := checkAlgorithm(key, parsedJws); err != nil { wfe.stats.Inc(statName, 1, 1.0) return nil, nil, reg, probs.Malformed(err.Error()) } payload, err := parsedJws.Verify(key) if err != nil { 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, probs.Malformed("JWS verification error") } // Check that the request has a known anti-replay nonce nonce := parsedJws.Signatures[0].Header.Nonce logEvent.RequestNonce = nonce if len(nonce) == 0 { wfe.stats.Inc("WFE.Errors.JWSMissingNonce", 1, 1.0) logEvent.AddError("JWS is missing an anti-replay nonce") 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: %s", nonce) return nil, nil, reg, probs.BadNonce(fmt.Sprintf("JWS has invalid anti-replay nonce %v", nonce)) } // Check that the "resource" field is present and has the correct value var parsedRequest struct { Resource string `json:"resource"` } err = json.Unmarshal([]byte(payload), &parsedRequest) if err != nil { wfe.stats.Inc("WFE.Errors.UnparsableJWSPayload", 1, 1.0) logEvent.AddError("unable to JSON parse resource from JWS payload: %s", err) 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 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") 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 }