func (wfe *WebFrontEndImpl) getChallenge( response http.ResponseWriter, request *http.Request, authz core.Authorization, challenge *core.Challenge, logEvent *requestEvent) { wfe.prepChallengeForDisplay(authz, challenge) jsonReply, err := json.Marshal(challenge) if err != nil { // 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, probs.ServerInternal("Failed to marshal challenge"), err) return } authzURL := wfe.AuthzBase + string(authz.ID) response.Header().Add("Location", challenge.URI) response.Header().Set("Content-Type", "application/json") response.Header().Add("Link", link(authzURL, "up")) response.WriteHeader(http.StatusAccepted) if _, err := response.Write(jsonReply); err != nil { wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err)) logEvent.AddError(err.Error()) return } }
// 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) } }
// NewAuthorization is used by clients to submit a new ID Authorization func (wfe *WebFrontEndImpl) NewAuthorization(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) { body, _, currReg, prob := wfe.verifyPOST(ctx, logEvent, request, true, core.ResourceNewAuthz) addRequesterHeader(response, logEvent.Requester) if prob != nil { // verifyPOST handles its own setting of logEvent.Errors 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, 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 { logEvent.AddError("unable to JSON unmarshal Authorization: %s", err) wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling JSON"), err) return } logEvent.Extra["Identifier"] = init.Identifier // Create new authz and return authz, err := wfe.RA.NewAuthorization(ctx, init, currReg.ID) if err != nil { logEvent.AddError("unable to create new authz: %s", err) wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Error creating new authz"), err) return } logEvent.Extra["AuthzID"] = authz.ID // Make a URL for this authz, then blow away the ID and RegID before serializing authzURL := wfe.relativeEndpoint(request, authzPath+string(authz.ID)) wfe.prepAuthorizationForDisplay(request, &authz) responseBody, err := marshalIndent(authz) if err != nil { // ServerInternal because we generated the authz, it should be OK wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling authz"), err) return } response.Header().Add("Location", authzURL) response.Header().Add("Link", link(wfe.relativeEndpoint(request, newCertPath), "next")) response.Header().Set("Content-Type", "application/json") response.WriteHeader(http.StatusCreated) if _, err = response.Write(responseBody); err != nil { logEvent.AddError(err.Error()) wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err)) } }
// Directory is an HTTP request handler that provides the directory // object stored in the WFE's DirectoryEndpoints member with paths prefixed // using the `request.Host` of the HTTP request. func (wfe *WebFrontEndImpl) Directory(ctx context.Context, logEvent *requestEvent, response http.ResponseWriter, request *http.Request) { directoryEndpoints := map[string]string{ "new-reg": newRegPath, "new-authz": newAuthzPath, "new-cert": newCertPath, "revoke-cert": revokeCertPath, } response.Header().Set("Content-Type", "application/json") relDir, err := wfe.relativeDirectory(request, directoryEndpoints) if err != nil { marshalProb := probs.ServerInternal("unable to marshal JSON directory") wfe.sendError(response, logEvent, marshalProb, nil) return } response.Write(relDir) }
// Authorization is used by clients to submit an update to one of their // authorizations. func (wfe *WebFrontEndImpl) Authorization(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) { // Requests to this handler should have a path that leads to a known authz id := parseIDFromPath(request.URL.Path) authz, err := wfe.SA.GetAuthorization(id) if err != nil { logEvent.AddError("No such authorization at id %s", id) // TODO(#1199): handle db errors wfe.sendError(response, logEvent, probs.NotFound("Unable to find authorization"), err) return } logEvent.Extra["AuthorizationID"] = authz.ID logEvent.Extra["AuthorizationRegistrationID"] = authz.RegistrationID logEvent.Extra["AuthorizationIdentifier"] = authz.Identifier logEvent.Extra["AuthorizationStatus"] = authz.Status logEvent.Extra["AuthorizationExpires"] = authz.Expires // After expiring, authorizations are inaccessible 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, probs.NotFound("Expired authorization"), nil) return } wfe.prepAuthorizationForDisplay(&authz) jsonReply, err := json.Marshal(authz) 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, probs.ServerInternal("Failed to JSON marshal authz"), err) return } response.Header().Add("Link", link(wfe.NewCert, "next")) response.Header().Set("Content-Type", "application/json") response.WriteHeader(http.StatusOK) if _, err = response.Write(jsonReply); err != nil { logEvent.AddError(err.Error()) wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err)) } }
// 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, prob := wfe.verifyPOST(logEvent, request, true, core.ResourceRegistration) if prob != nil { // verifyPOST handles its own setting of logEvent.Errors wfe.sendError(response, logEvent, prob, nil) return } // Requests to this handler should have a path that leads to a known // registration idStr := parseIDFromPath(request.URL.Path) 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, 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, 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, probs.Unauthorized("Request signing key did not match registration key"), nil) return } var update core.Registration err = json.Unmarshal(body, &update) if err != nil { logEvent.AddError("unable to JSON parse registration: %s", err) 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, probs.Malformed(msg), nil) return } // Registration objects contain a JWK object, which must be non-nil. We know // the key of the updated registration object is going to be the same as the // key of the current one, so we set it here. This ensures we can cleanly // serialize the update as JSON to send via AMQP to the RA. update.Key = currReg.Key // Ask the RA to update this authorization. updatedReg, err := wfe.RA.UpdateRegistration(currReg, update) if err != nil { logEvent.AddError("unable to update registration: %s", err) wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Unable to update registration"), err) return } jsonReply, err := json.Marshal(updatedReg) if err != nil { // ServerInternal because we just generated the reg, it should be OK logEvent.AddError("unable to marshal updated registration: %s", err) wfe.sendError(response, logEvent, probs.ServerInternal("Failed to marshal registration"), err) return } response.Header().Set("Content-Type", "application/json") response.Header().Add("Link", link(wfe.NewAuthz, "next")) if len(wfe.SubscriberAgreementURL) > 0 { response.Header().Add("Link", link(wfe.SubscriberAgreementURL, "terms-of-service")) } response.WriteHeader(http.StatusAccepted) response.Write(jsonReply) }
func (wfe *WebFrontEndImpl) postChallenge( response http.ResponseWriter, request *http.Request, authz core.Authorization, challengeIndex int, logEvent *requestEvent) { body, _, currReg, prob := wfe.verifyPOST(logEvent, request, true, core.ResourceChallenge) if prob != nil { // verifyPOST handles its own setting of logEvent.Errors 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, probs.Unauthorized("Registration didn't agree to subscriber agreement before any further actions"), nil) return } // Check that the registration ID matching the key used matches // 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, probs.Unauthorized("User registration ID doesn't match registration ID in authorization"), nil, ) return } var challengeUpdate core.Challenge if err := json.Unmarshal(body, &challengeUpdate); err != nil { logEvent.AddError("error JSON unmarshalling challenge response: %s", err) wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling challenge response"), err) return } // Ask the RA to update this authorization updatedAuthorization, err := wfe.RA.UpdateAuthorization(authz, challengeIndex, challengeUpdate) if err != nil { logEvent.AddError("unable to update challenge: %s", err) wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Unable to update challenge"), err) return } // assumption: UpdateAuthorization does not modify order of challenges challenge := updatedAuthorization.Challenges[challengeIndex] wfe.prepChallengeForDisplay(authz, &challenge) jsonReply, err := json.Marshal(challenge) if err != nil { // ServerInternal because we made the challenges, they should be OK logEvent.AddError("failed to marshal challenge: %s", err) wfe.sendError(response, logEvent, probs.ServerInternal("Failed to marshal challenge"), err) return } authzURL := wfe.AuthzBase + string(authz.ID) response.Header().Add("Location", challenge.URI) response.Header().Set("Content-Type", "application/json") response.Header().Add("Link", link(authzURL, "up")) response.WriteHeader(http.StatusAccepted) if _, err = response.Write(jsonReply); err != nil { logEvent.AddError(err.Error()) wfe.log.Warning(fmt.Sprintf("Could not write response: %s", err)) return } }
// RevokeCertificate is used by clients to request the revocation of a cert. func (wfe *WebFrontEndImpl) RevokeCertificate(logEvent *requestEvent, response http.ResponseWriter, request *http.Request) { // We don't ask verifyPOST to verify there is a corresponding registration, // because anyone with the right private key can revoke a certificate. 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, prob, nil) return } type RevokeRequest struct { CertificateDER core.JSONBuffer `json:"certificate"` } var revokeRequest RevokeRequest 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, 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, 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, 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, probs.ServerInternal("invalid parse of stored certificate"), err) return } logEvent.Extra["RetrievedCertificateSerial"] = core.SerialToString(parsedCertificate.SerialNumber) logEvent.Extra["RetrievedCertificateDNSNames"] = parsedCertificate.DNSNames logEvent.Extra["RetrievedCertificateEmailAddresses"] = parsedCertificate.EmailAddresses logEvent.Extra["RetrievedCertificateIPAddresses"] = parsedCertificate.IPAddresses certStatus, err := wfe.SA.GetCertificateStatus(serial) if err != nil { logEvent.AddError("unable to get certificate status: %s", err) // 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, probs.Conflict("Certificate already revoked"), nil) return } // TODO: Implement method of revocation by authorizations on account. if !(core.KeyDigestEquals(requestKey, parsedCertificate.PublicKey) || registration.ID == cert.RegistrationID) { wfe.sendError(response, logEvent, 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 } // Use revocation code 0, meaning "unspecified" err = wfe.RA.RevokeCertificateWithReg(*parsedCertificate, 0, registration.ID) if err != nil { logEvent.AddError("failed to revoke certificate: %s", 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) } }
// 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, _, prob := wfe.verifyPOST(logEvent, request, false, core.ResourceNewReg) if prob != nil { // verifyPOST handles its own setting of logEvent.Errors 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)) // 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) if err != nil { 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, probs.Malformed(msg), nil) return } init.Key = *key init.InitialIP = net.ParseIP(request.Header.Get("X-Real-IP")) if init.InitialIP == nil { host, _, err := net.SplitHostPort(request.RemoteAddr) if err == nil { init.InitialIP = net.ParseIP(host) } else { logEvent.AddError("Couldn't parse RemoteAddr: %s", request.RemoteAddr) wfe.sendError(response, logEvent, probs.ServerInternal("couldn't parse the remote (that is, the client's) address"), nil) return } } reg, err := wfe.RA.NewRegistration(init) if err != nil { logEvent.AddError("unable to create new registration: %s", err) wfe.sendError(response, logEvent, core.ProblemDetailsForError(err, "Error creating new registration"), err) return } logEvent.Requester = reg.ID logEvent.Contacts = reg.Contact // Use an explicitly typed variable. Otherwise `go vet' incorrectly complains // that reg.ID is a string being passed to %d. regURL := fmt.Sprintf("%s%d", wfe.RegBase, reg.ID) responseBody, err := json.Marshal(reg) if err != nil { // ServerInternal because we just created this registration, and it // should be OK. logEvent.AddError("unable to marshal registration: %s", err) wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling registration"), err) return } response.Header().Add("Location", regURL) response.Header().Set("Content-Type", "application/json") response.Header().Add("Link", link(wfe.NewAuthz, "next")) if len(wfe.SubscriberAgreementURL) > 0 { response.Header().Add("Link", link(wfe.SubscriberAgreementURL, "terms-of-service")) } response.WriteHeader(http.StatusCreated) response.Write(responseBody) }
// 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 }
// UpdateAuthorization updates an authorization with new values. func (ra *RegistrationAuthorityImpl) UpdateAuthorization(ctx context.Context, base core.Authorization, challengeIndex int, response core.Challenge) (authz core.Authorization, err error) { // Refuse to update expired authorizations if base.Expires == nil || base.Expires.Before(ra.clk.Now()) { err = core.NotFoundError("Expired authorization") return } authz = base if challengeIndex >= len(authz.Challenges) { err = core.MalformedRequestError(fmt.Sprintf("Invalid challenge index: %d", challengeIndex)) return } ch := &authz.Challenges[challengeIndex] // Copy information over that the client is allowed to supply ch.ProvidedKeyAuthorization = response.ProvidedKeyAuthorization if response.Type != "" && ch.Type != response.Type { // TODO(riking): Check the rate on this, uncomment error return if negligible ra.stats.Inc("RA.StartChallengeWrongType", 1, 1.0) // err = core.MalformedRequestError(fmt.Sprintf("Invalid update to challenge - provided type was %s but actual type is %s", response.Type, ch.Type)) // return } // Recompute the key authorization field provided by the client and // check it against the value provided expectedKeyAuthorization, err := ch.ExpectedKeyAuthorization() if err != nil { err = core.InternalServerError("Could not compute expected key authorization value") return } if expectedKeyAuthorization != ch.ProvidedKeyAuthorization { err = core.MalformedRequestError("Response does not complete challenge") return } // Double check before sending to VA if !ch.IsSaneForValidation() { err = core.MalformedRequestError("Response does not complete challenge") return } // Store the updated version if err = ra.SA.UpdatePendingAuthorization(ctx, authz); err != nil { // This can pretty much only happen when the client corrupts the Challenge // data. err = core.MalformedRequestError("Challenge data was corrupted") return } ra.stats.Inc("RA.NewPendingAuthorizations", 1, 1.0) // Look up the account key for this authorization reg, err := ra.SA.GetRegistration(ctx, authz.RegistrationID) if err != nil { err = core.InternalServerError(err.Error()) return } // Reject the update if the challenge in question was created // with a different account key if !core.KeyDigestEquals(reg.Key, ch.AccountKey) { err = core.UnauthorizedError("Challenge cannot be updated with a different key") return } // Dispatch to the VA for service vaCtx := context.Background() if !ra.useNewVARPC { // TODO(#1167): remove _ = ra.VA.UpdateValidations(vaCtx, authz, challengeIndex) ra.stats.Inc("RA.UpdatedPendingAuthorizations", 1, 1.0) } else { go func() { records, err := ra.VA.PerformValidation(vaCtx, authz.Identifier.Value, authz.Challenges[challengeIndex], authz) var prob *probs.ProblemDetails if p, ok := err.(*probs.ProblemDetails); ok { prob = p } else if err != nil { prob = probs.ServerInternal("Could not communicate with VA") ra.log.Err(fmt.Sprintf("Could not communicate with VA: %s", err)) } // Save the updated records challenge := &authz.Challenges[challengeIndex] challenge.ValidationRecord = records if !challenge.RecordsSane() && prob == nil { prob = probs.ServerInternal("Records for validation failed sanity check") } if prob != nil { challenge.Status = core.StatusInvalid challenge.Error = prob } else { challenge.Status = core.StatusValid } authz.Challenges[challengeIndex] = *challenge err = ra.OnValidationUpdate(vaCtx, authz) if err != nil { ra.log.Err(fmt.Sprintf("Could not record updated validation: err=[%s] regID=[%d]", err, authz.RegistrationID)) } }() ra.stats.Inc("RA.UpdatedPendingAuthorizations", 1, 1.0) } return }
// UpdateAuthorization updates an authorization with new values. func (ra *RegistrationAuthorityImpl) UpdateAuthorization(ctx context.Context, base core.Authorization, challengeIndex int, response core.Challenge) (authz core.Authorization, err error) { // Refuse to update expired authorizations if base.Expires == nil || base.Expires.Before(ra.clk.Now()) { err = core.NotFoundError("Expired authorization") return } authz = base if challengeIndex >= len(authz.Challenges) { err = core.MalformedRequestError(fmt.Sprintf("Invalid challenge index: %d", challengeIndex)) return } ch := &authz.Challenges[challengeIndex] if response.Type != "" && ch.Type != response.Type { // TODO(riking): Check the rate on this, uncomment error return if negligible ra.stats.Inc("StartChallengeWrongType", 1) // err = core.MalformedRequestError(fmt.Sprintf("Invalid update to challenge - provided type was %s but actual type is %s", response.Type, ch.Type)) // return } // When configured with `reuseValidAuthz` we can expect some clients to try // and update a challenge for an authorization that is already valid. In this // case we don't need to process the challenge update. It wouldn't be helpful, // the overall authorization is already good! We increment a stat for this // case and return early. if ra.reuseValidAuthz && authz.Status == core.StatusValid { ra.stats.Inc("ReusedValidAuthzChallenge", 1) return } // Look up the account key for this authorization reg, err := ra.SA.GetRegistration(ctx, authz.RegistrationID) if err != nil { err = core.InternalServerError(err.Error()) return } // Recompute the key authorization field provided by the client and // check it against the value provided expectedKeyAuthorization, err := ch.ExpectedKeyAuthorization(reg.Key) if err != nil { err = core.InternalServerError("Could not compute expected key authorization value") return } if expectedKeyAuthorization != response.ProvidedKeyAuthorization { err = core.MalformedRequestError("Provided key authorization was incorrect") return } // Copy information over that the client is allowed to supply ch.ProvidedKeyAuthorization = response.ProvidedKeyAuthorization // Double check before sending to VA if !ch.IsSaneForValidation() { err = core.MalformedRequestError("Response does not complete challenge") return } // Store the updated version if err = ra.SA.UpdatePendingAuthorization(ctx, authz); err != nil { ra.log.Warning(fmt.Sprintf( "Error calling ra.SA.UpdatePendingAuthorization: %s\n", err.Error())) err = core.InternalServerError("Could not update pending authorization") return } ra.stats.Inc("NewPendingAuthorizations", 1) // Dispatch to the VA for service vaCtx := context.Background() go func() { records, err := ra.VA.PerformValidation(vaCtx, authz.Identifier.Value, authz.Challenges[challengeIndex], authz) var prob *probs.ProblemDetails if p, ok := err.(*probs.ProblemDetails); ok { prob = p } else if err != nil { prob = probs.ServerInternal("Could not communicate with VA") ra.log.AuditErr(fmt.Sprintf("Could not communicate with VA: %s", err)) } // Save the updated records challenge := &authz.Challenges[challengeIndex] challenge.ValidationRecord = records if !challenge.RecordsSane() && prob == nil { prob = probs.ServerInternal("Records for validation failed sanity check") } if prob != nil { challenge.Status = core.StatusInvalid challenge.Error = prob } else { challenge.Status = core.StatusValid } authz.Challenges[challengeIndex] = *challenge err = ra.onValidationUpdate(vaCtx, authz) if err != nil { ra.log.AuditErr(fmt.Sprintf("Could not record updated validation: err=[%s] regID=[%d]", err, authz.RegistrationID)) } }() ra.stats.Inc("UpdatedPendingAuthorizations", 1) return }