// Unwraps a RPCError and returns the correct error type. func unwrapError(rpcError RPCError) (err error) { if rpcError.Value != "" { switch rpcError.Type { case "InternalServerError": err = core.InternalServerError(rpcError.Value) case "NotSupportedError": err = core.NotSupportedError(rpcError.Value) case "MalformedRequestError": err = core.MalformedRequestError(rpcError.Value) case "UnauthorizedError": err = core.UnauthorizedError(rpcError.Value) case "NotFoundError": err = core.NotFoundError(rpcError.Value) case "SyntaxError": err = core.SyntaxError(rpcError.Value) case "SignatureValidationError": err = core.SignatureValidationError(rpcError.Value) case "CertificateIssuanceError": err = core.CertificateIssuanceError(rpcError.Value) default: err = errors.New(rpcError.Value) } } return }
func unwrapError(err error) error { code := grpc.Code(err) errBody := grpc.ErrorDesc(err) switch code { case InternalServerError: return core.InternalServerError(errBody) case NotSupportedError: return core.NotSupportedError(errBody) case MalformedRequestError: return core.MalformedRequestError(errBody) case UnauthorizedError: return core.UnauthorizedError(errBody) case NotFoundError: return core.NotFoundError(errBody) case SignatureValidationError: return core.SignatureValidationError(errBody) case NoSuchRegistrationError: return core.NoSuchRegistrationError(errBody) case RateLimitedError: return core.RateLimitedError(errBody) case LengthRequiredError: return core.LengthRequiredError(errBody) case BadNonceError: return core.BadNonceError(errBody) default: return err } }
// Unwraps a rpcError and returns the correct error type. func unwrapError(rpcError *rpcError) error { if rpcError != nil { switch rpcError.Type { case "InternalServerError": return core.InternalServerError(rpcError.Value) case "NotSupportedError": return core.NotSupportedError(rpcError.Value) case "MalformedRequestError": return core.MalformedRequestError(rpcError.Value) case "UnauthorizedError": return core.UnauthorizedError(rpcError.Value) case "NotFoundError": return core.NotFoundError(rpcError.Value) case "SignatureValidationError": return core.SignatureValidationError(rpcError.Value) case "NoSuchRegistrationError": return core.NoSuchRegistrationError(rpcError.Value) case "TooManyRPCRequestsError": return core.TooManyRPCRequestsError(rpcError.Value) case "RateLimitedError": return core.RateLimitedError(rpcError.Value) default: if strings.HasPrefix(rpcError.Type, "urn:") { return &probs.ProblemDetails{ Type: probs.ProblemType(rpcError.Type), Detail: rpcError.Value, HTTPStatus: rpcError.HTTPStatus, } } return errors.New(rpcError.Value) } } return nil }
// Unwraps a rpcError and returns the correct error type. func unwrapError(rpcError *rpcError) error { if rpcError != nil { switch rpcError.Type { case "InternalServerError": return core.InternalServerError(rpcError.Value) case "NotSupportedError": return core.NotSupportedError(rpcError.Value) case "MalformedRequestError": return core.MalformedRequestError(rpcError.Value) case "UnauthorizedError": return core.UnauthorizedError(rpcError.Value) case "NotFoundError": return core.NotFoundError(rpcError.Value) case "SyntaxError": return core.SyntaxError(rpcError.Value) case "SignatureValidationError": return core.SignatureValidationError(rpcError.Value) case "CertificateIssuanceError": return core.CertificateIssuanceError(rpcError.Value) case "NoSuchRegistrationError": return core.NoSuchRegistrationError(rpcError.Value) case "TooManyRPCRequestsError": return core.TooManyRPCRequestsError(rpcError.Value) case "RateLimitedError": return core.RateLimitedError(rpcError.Value) case "ServiceUnavailableError": return core.ServiceUnavailableError(rpcError.Value) default: return errors.New(rpcError.Value) } } return nil }
func algorithmForKey(key *jose.JsonWebKey) (string, error) { // TODO(https://github.com/letsencrypt/boulder/issues/792): Support EC. switch key.Key.(type) { case *rsa.PublicKey: return string(jose.RS256), nil } return "", core.SignatureValidationError("no signature algorithms suitable for given key type") }
// Check that (1) there is a suitable algorithm for the provided key based on its // Golang type, (2) the Algorithm field on the JWK is either absent, or matches // that algorithm, and (3) the Algorithm field on the JWK is present and matches // that algorithm. Precondition: parsedJws must have exactly one signature on // it. Returns stat name to increment if err is non-nil. func checkAlgorithm(key *jose.JsonWebKey, parsedJws *jose.JsonWebSignature) (string, error) { algorithm, err := algorithmForKey(key) if err != nil { return noAlgorithmForKey, err } jwsAlgorithm := parsedJws.Signatures[0].Header.Algorithm if jwsAlgorithm != algorithm { return invalidJWSAlgorithm, core.SignatureValidationError(fmt.Sprintf( "algorithm '%s' in JWS header not acceptable", jwsAlgorithm)) } if key.Algorithm != "" && key.Algorithm != algorithm { return invalidAlgorithmOnKey, core.SignatureValidationError(fmt.Sprintf( "algorithm '%s' on JWK is unacceptable", key.Algorithm)) } return "", nil }
func TestWrapError(t *testing.T) { testCases := []error{ core.InternalServerError("foo"), core.NotSupportedError("foo"), core.MalformedRequestError("foo"), core.UnauthorizedError("foo"), core.NotFoundError("foo"), core.SignatureValidationError("foo"), core.CertificateIssuanceError("foo"), core.NoSuchRegistrationError("foo"), core.RateLimitedError("foo"), core.TooManyRPCRequestsError("foo"), errors.New("foo"), } for _, c := range testCases { wrapped := wrapError(c) test.AssertEquals(t, wrapped.Type, reflect.TypeOf(c).Name()) test.AssertEquals(t, wrapped.Value, "foo") unwrapped := unwrapError(wrapped) test.AssertEquals(t, wrapped.Type, reflect.TypeOf(unwrapped).Name()) test.AssertEquals(t, unwrapped.Error(), "foo") } complicated := []struct { given error expected error }{ { &probs.ProblemDetails{ Type: probs.ConnectionProblem, Detail: "whoops", HTTPStatus: 417, }, &probs.ProblemDetails{ Type: probs.ConnectionProblem, Detail: "whoops", HTTPStatus: 417, }, }, { &probs.ProblemDetails{Type: "invalid", Detail: "hm"}, errors.New("hm"), }, { errors.New(""), errors.New(""), }, } for i, tc := range complicated { actual := unwrapError(wrapError(tc.given)) if !reflect.DeepEqual(tc.expected, actual) { t.Errorf("rpc error wrapping case %d: want %#v, got %#v", i, tc.expected, actual) } } }
func algorithmForKey(key *jose.JsonWebKey) (string, error) { switch k := key.Key.(type) { case *rsa.PublicKey: return string(jose.RS256), nil case *ecdsa.PublicKey: switch k.Params().Name { case "P-256": return string(jose.ES256), nil case "P-384": return string(jose.ES384), nil case "P-521": return string(jose.ES512), nil } } return "", core.SignatureValidationError("no signature algorithms suitable for given key type") }
func TestWrapError(t *testing.T) { testCases := []error{ core.InternalServerError("foo"), core.NotSupportedError("foo"), core.MalformedRequestError("foo"), core.UnauthorizedError("foo"), core.NotFoundError("foo"), core.SyntaxError("foo"), core.SignatureValidationError("foo"), core.CertificateIssuanceError("foo"), core.NoSuchRegistrationError("foo"), core.RateLimitedError("foo"), core.TooManyRPCRequestsError("foo"), } for _, c := range testCases { wrapped := wrapError(c) test.AssertEquals(t, wrapped.Type, reflect.TypeOf(c).Name()) test.AssertEquals(t, wrapped.Value, "foo") unwrapped := unwrapError(wrapped) test.AssertEquals(t, wrapped.Type, reflect.TypeOf(unwrapped).Name()) test.AssertEquals(t, unwrapped.Error(), "foo") } }
func TestErrors(t *testing.T) { testcases := []struct { err error expectedCode codes.Code }{ {core.MalformedRequestError("test 1"), MalformedRequestError}, {core.NotSupportedError("test 2"), NotSupportedError}, {core.UnauthorizedError("test 3"), UnauthorizedError}, {core.NotFoundError("test 4"), NotFoundError}, {core.LengthRequiredError("test 5"), LengthRequiredError}, {core.SignatureValidationError("test 6"), SignatureValidationError}, {core.RateLimitedError("test 7"), RateLimitedError}, {core.BadNonceError("test 8"), BadNonceError}, {core.NoSuchRegistrationError("test 9"), NoSuchRegistrationError}, {core.InternalServerError("test 10"), InternalServerError}, } for _, tc := range testcases { wrappedErr := wrapError(tc.err) test.AssertEquals(t, grpc.Code(wrappedErr), tc.expectedCode) test.AssertEquals(t, tc.err, unwrapError(wrappedErr)) } }
func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool, resource core.AcmeResource) ([]byte, *jose.JsonWebKey, core.Registration, error) { var err error var reg core.Registration // Read body if request.Body == nil { err = core.MalformedRequestError("No body on POST") wfe.log.Debug(err.Error()) return nil, nil, reg, err } bodyBytes, err := ioutil.ReadAll(request.Body) if err != nil { err = core.InternalServerError(err.Error()) wfe.log.Debug(err.Error()) return nil, nil, reg, err } body := string(bodyBytes) // Parse as JWS parsedJws, err := jose.ParseSigned(body) if err != nil { puberr := core.SignatureValidationError("Parse error reading JWS") wfe.log.Debug(fmt.Sprintf("%v :: %v", puberr.Error(), err.Error())) return nil, nil, reg, puberr } // 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 { err = core.SignatureValidationError("Too many signatures on POST") wfe.log.Debug(err.Error()) return nil, nil, reg, err } if len(parsedJws.Signatures) == 0 { err = core.SignatureValidationError("POST JWS not signed") wfe.log.Debug(err.Error()) return nil, nil, reg, err } key := parsedJws.Signatures[0].Header.JsonWebKey payload, header, err := parsedJws.Verify(key) if err != nil { puberr := core.SignatureValidationError("JWS verification error") wfe.log.Debug(string(body)) wfe.log.Debug(fmt.Sprintf("%v :: %v", puberr.Error(), err.Error())) return nil, nil, reg, puberr } // Check that the request has a known anti-replay nonce // i.e., Nonce is in protected header and if err != nil || len(header.Nonce) == 0 { err = core.SignatureValidationError("JWS has no anti-replay nonce") wfe.log.Debug(err.Error()) return nil, nil, reg, err } else if !wfe.nonceService.Valid(header.Nonce) { err = core.SignatureValidationError(fmt.Sprintf("JWS has invalid anti-replay nonce")) wfe.log.Debug(err.Error()) return nil, nil, reg, err } reg, err = wfe.SA.GetRegistrationByKey(*key) if err != nil { // If we are requiring a valid registration, any failure to look up the // registration is an overall failure to verify. if regCheck { return nil, nil, reg, err } // Otherwise we just return an empty registration. The caller is expected // to use the returned key instead. reg = core.Registration{} } // 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 { puberr := core.SignatureValidationError("Request payload did not parse as JSON") wfe.log.Debug(fmt.Sprintf("%v :: %v", puberr.Error(), err.Error())) return nil, nil, reg, puberr } if parsedRequest.Resource == "" { err = core.MalformedRequestError("Request payload does not specify a resource") wfe.log.Debug(err.Error()) return nil, nil, reg, err } else if resource != core.AcmeResource(parsedRequest.Resource) { err = core.MalformedRequestError(fmt.Sprintf("Request payload has invalid resource: %s != %s", parsedRequest.Resource, resource)) wfe.log.Debug(err.Error()) return nil, nil, reg, err } return []byte(payload), key, reg, nil }
// 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 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 // 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 } // 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 } 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 } 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 } // 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 { 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 } 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 } 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 } 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 = 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 } 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 } else { // If the lookup was successful, use that key. key = ®.Key logEvent.Requester = reg.ID logEvent.Contacts = reg.Contact } payload, header, 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 } // Check that the request has a known anti-replay nonce // i.e., Nonce is in protected header and if err != nil || len(header.Nonce) == 0 { wfe.stats.Inc("WFE.Errors.JWSMissingNonce", 1, 1.0) logEvent.AddError("JWS is missing an anti-replay nonce") err = core.SignatureValidationError("JWS has no anti-replay nonce") return nil, nil, reg, err } else if !wfe.nonceService.Valid(header.Nonce) { wfe.stats.Inc("WFE.Errors.JWSInvalidNonce", 1, 1.0) logEvent.AddError("JWS has an invalid anti-replay nonce") err = core.SignatureValidationError(fmt.Sprintf("JWS has invalid anti-replay nonce")) return nil, nil, reg, err } // 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) puberr := core.SignatureValidationError("Request payload did not parse as JSON") return nil, nil, reg, puberr } 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 } 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 []byte(payload), key, reg, nil }
// 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. func (wfe *WebFrontEndImpl) verifyPOST(request *http.Request, regCheck bool, resource core.AcmeResource) ([]byte, *jose.JsonWebKey, core.Registration, error) { var err error // 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: -1} if _, ok := request.Header["Content-Length"]; !ok { err = core.LengthRequiredError("Content-Length header is required for POST.") wfe.log.Debug(err.Error()) return nil, nil, reg, err } // Read body if request.Body == nil { err = core.MalformedRequestError("No body on POST") wfe.log.Debug(err.Error()) return nil, nil, reg, err } bodyBytes, err := ioutil.ReadAll(request.Body) if err != nil { err = core.InternalServerError(err.Error()) wfe.log.Debug(err.Error()) return nil, nil, reg, err } body := string(bodyBytes) // Parse as JWS parsedJws, err := jose.ParseSigned(body) if err != nil { puberr := core.SignatureValidationError("Parse error reading JWS") wfe.log.Debug(fmt.Sprintf("%v :: %v", puberr.Error(), err.Error())) return nil, nil, reg, puberr } // 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 { err = core.SignatureValidationError("Too many signatures on POST") wfe.log.Debug(err.Error()) return nil, nil, reg, err } if len(parsedJws.Signatures) == 0 { err = core.SignatureValidationError("POST JWS not signed") wfe.log.Debug(err.Error()) return nil, nil, reg, err } submittedKey := parsedJws.Signatures[0].Header.JsonWebKey if submittedKey == nil { err = core.SignatureValidationError("No JWK in JWS header") wfe.log.Debug(err.Error()) return nil, nil, reg, err } 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 = core.GoodKey(submittedKey.Key); err != nil { return nil, nil, reg, err } key = submittedKey } else if err != nil { // For all other errors, or if regCheck is true, return error immediately. return nil, nil, reg, err } else { // If the lookup was successful, use that key. key = ®.Key } payload, header, err := parsedJws.Verify(key) if err != nil { puberr := core.SignatureValidationError("JWS verification error") wfe.log.Debug(string(body)) wfe.log.Debug(fmt.Sprintf("%v :: %v", puberr.Error(), err.Error())) return nil, nil, reg, puberr } // Check that the request has a known anti-replay nonce // i.e., Nonce is in protected header and if err != nil || len(header.Nonce) == 0 { err = core.SignatureValidationError("JWS has no anti-replay nonce") wfe.log.Debug(err.Error()) return nil, nil, reg, err } else if !wfe.nonceService.Valid(header.Nonce) { err = core.SignatureValidationError(fmt.Sprintf("JWS has invalid anti-replay nonce")) wfe.log.Debug(err.Error()) return nil, nil, reg, err } // 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 { puberr := core.SignatureValidationError("Request payload did not parse as JSON") wfe.log.Debug(fmt.Sprintf("%v :: %v", puberr.Error(), err.Error())) return nil, nil, reg, puberr } if parsedRequest.Resource == "" { err = core.MalformedRequestError("Request payload does not specify a resource") wfe.log.Debug(err.Error()) return nil, nil, reg, err } else if resource != core.AcmeResource(parsedRequest.Resource) { err = core.MalformedRequestError(fmt.Sprintf("Request payload has invalid resource: %s != %s", parsedRequest.Resource, resource)) wfe.log.Debug(err.Error()) return nil, nil, reg, err } return []byte(payload), key, reg, nil }