github.com/letsencrypt/boulder@v0.20251208.0/wfe2/verify.go (about)

     1  package wfe2
     2  
     3  import (
     4  	"context"
     5  	"crypto/ecdsa"
     6  	"crypto/rsa"
     7  	"encoding/base64"
     8  	"encoding/json"
     9  	"errors"
    10  	"fmt"
    11  	"io"
    12  	"net/http"
    13  	"net/url"
    14  	"slices"
    15  	"strconv"
    16  	"strings"
    17  
    18  	"github.com/go-jose/go-jose/v4"
    19  	"github.com/prometheus/client_golang/prometheus"
    20  	"google.golang.org/grpc/status"
    21  
    22  	"github.com/letsencrypt/boulder/core"
    23  	berrors "github.com/letsencrypt/boulder/errors"
    24  	"github.com/letsencrypt/boulder/goodkey"
    25  	"github.com/letsencrypt/boulder/grpc"
    26  	nb "github.com/letsencrypt/boulder/grpc/noncebalancer"
    27  	"github.com/letsencrypt/boulder/nonce"
    28  	noncepb "github.com/letsencrypt/boulder/nonce/proto"
    29  	sapb "github.com/letsencrypt/boulder/sa/proto"
    30  	"github.com/letsencrypt/boulder/web"
    31  )
    32  
    33  const (
    34  	// POST requests with a JWS body must have the following Content-Type header
    35  	expectedJWSContentType = "application/jose+json"
    36  
    37  	maxRequestSize = 50000
    38  )
    39  
    40  func sigAlgorithmForKey(key *jose.JSONWebKey) (jose.SignatureAlgorithm, error) {
    41  	switch k := key.Key.(type) {
    42  	case *rsa.PublicKey:
    43  		return jose.RS256, nil
    44  	case *ecdsa.PublicKey:
    45  		switch k.Params().Name {
    46  		case "P-256":
    47  			return jose.ES256, nil
    48  		case "P-384":
    49  			return jose.ES384, nil
    50  		case "P-521":
    51  			return jose.ES512, nil
    52  		}
    53  	}
    54  	return "", berrors.BadPublicKeyError("JWK contains unsupported key type (expected RSA, or ECDSA P-256, P-384, or P-521)")
    55  }
    56  
    57  // getSupportedAlgs returns a sorted slice of joseSignatureAlgorithm's from a
    58  // map of boulder allowed signature algorithms. We use a function for this to
    59  // ensure that the source-of-truth slice can never be modified.
    60  func getSupportedAlgs() []jose.SignatureAlgorithm {
    61  	return []jose.SignatureAlgorithm{
    62  		jose.RS256,
    63  		jose.ES256,
    64  		jose.ES384,
    65  		jose.ES512,
    66  	}
    67  }
    68  
    69  // Check that (1) there is a suitable algorithm for the provided key based on its
    70  // Golang type, (2) the Algorithm field on the JWK is either absent, or matches
    71  // that algorithm, and (3) the Algorithm field on the JWK is present and matches
    72  // that algorithm.
    73  func checkAlgorithm(key *jose.JSONWebKey, header jose.Header) error {
    74  	sigHeaderAlg := jose.SignatureAlgorithm(header.Algorithm)
    75  	if !slices.Contains(getSupportedAlgs(), sigHeaderAlg) {
    76  		return berrors.BadSignatureAlgorithmError(
    77  			"JWS signature header contains unsupported algorithm %q, expected one of %s",
    78  			header.Algorithm, getSupportedAlgs(),
    79  		)
    80  	}
    81  
    82  	expectedAlg, err := sigAlgorithmForKey(key)
    83  	if err != nil {
    84  		return err
    85  	}
    86  	if sigHeaderAlg != expectedAlg {
    87  		return berrors.MalformedError("JWS signature header algorithm %q does not match expected algorithm %q for JWK", sigHeaderAlg, string(expectedAlg))
    88  	}
    89  	if key.Algorithm != "" && key.Algorithm != string(expectedAlg) {
    90  		return berrors.MalformedError("JWK key header algorithm %q does not match expected algorithm %q for JWK", key.Algorithm, string(expectedAlg))
    91  	}
    92  	return nil
    93  }
    94  
    95  // jwsAuthType represents whether a given POST request is authenticated using
    96  // a JWS with an embedded JWK (v1 ACME style, new-account, revoke-cert) or an
    97  // embedded Key ID (v2 AMCE style) or an unsupported/unknown auth type.
    98  type jwsAuthType int
    99  
   100  const (
   101  	embeddedJWK jwsAuthType = iota
   102  	embeddedKeyID
   103  	invalidAuthType
   104  )
   105  
   106  // checkJWSAuthType examines the protected headers from a bJSONWebSignature to
   107  // determine if the request being authenticated by the JWS is identified using
   108  // an embedded JWK or an embedded key ID. If no signatures are present, or
   109  // mutually exclusive authentication types are specified at the same time, a
   110  // error is returned. checkJWSAuthType is separate from enforceJWSAuthType so
   111  // that endpoints that need to handle both embedded JWK and embedded key ID
   112  // requests can determine which type of request they have and act accordingly
   113  // (e.g. acme v2 cert revocation).
   114  func checkJWSAuthType(header jose.Header) (jwsAuthType, error) {
   115  	// There must not be a Key ID *and* an embedded JWK
   116  	if header.KeyID != "" && header.JSONWebKey != nil {
   117  		return invalidAuthType, berrors.MalformedError("jwk and kid header fields are mutually exclusive")
   118  	} else if header.KeyID != "" {
   119  		return embeddedKeyID, nil
   120  	} else if header.JSONWebKey != nil {
   121  		return embeddedJWK, nil
   122  	}
   123  
   124  	return invalidAuthType, nil
   125  }
   126  
   127  // enforceJWSAuthType enforces that the protected headers from a
   128  // bJSONWebSignature have the provided auth type. If there is an error
   129  // determining the auth type or if it is not the expected auth type then a
   130  // error is returned.
   131  func (wfe *WebFrontEndImpl) enforceJWSAuthType(
   132  	header jose.Header,
   133  	expectedAuthType jwsAuthType) error {
   134  	// Check the auth type for the provided JWS
   135  	authType, err := checkJWSAuthType(header)
   136  	if err != nil {
   137  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSAuthTypeInvalid"}).Inc()
   138  		return err
   139  	}
   140  	// If the auth type isn't the one expected return a sensible error based on
   141  	// what was expected
   142  	if authType != expectedAuthType {
   143  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSAuthTypeWrong"}).Inc()
   144  		switch expectedAuthType {
   145  		case embeddedKeyID:
   146  			return berrors.MalformedError("No Key ID in JWS header")
   147  		case embeddedJWK:
   148  			return berrors.MalformedError("No embedded JWK in JWS header")
   149  		}
   150  	}
   151  	return nil
   152  }
   153  
   154  // validPOSTRequest checks a *http.Request to ensure it has the headers
   155  // a well-formed ACME POST request has, and to ensure there is a body to
   156  // process.
   157  func (wfe *WebFrontEndImpl) validPOSTRequest(request *http.Request) error {
   158  	// Per 6.2 ALL POSTs should have the correct JWS Content-Type for flattened
   159  	// JSON serialization.
   160  	if _, present := request.Header["Content-Type"]; !present {
   161  		wfe.stats.httpErrorCount.With(prometheus.Labels{"type": "NoContentType"}).Inc()
   162  		return berrors.MalformedError("No Content-Type header on POST. Content-Type must be %q", expectedJWSContentType)
   163  	}
   164  	if contentType := request.Header.Get("Content-Type"); contentType != expectedJWSContentType {
   165  		wfe.stats.httpErrorCount.With(prometheus.Labels{"type": "WrongContentType"}).Inc()
   166  		return berrors.MalformedError("Invalid Content-Type header on POST. Content-Type must be %q", expectedJWSContentType)
   167  	}
   168  
   169  	// Per 6.4.1 "Replay-Nonce" clients should not send a Replay-Nonce header in
   170  	// the HTTP request, it needs to be part of the signed JWS request body
   171  	if _, present := request.Header["Replay-Nonce"]; present {
   172  		wfe.stats.httpErrorCount.With(prometheus.Labels{"type": "ReplayNonceOutsideJWS"}).Inc()
   173  		return berrors.MalformedError("HTTP requests should NOT contain Replay-Nonce header. Use JWS nonce field")
   174  	}
   175  
   176  	// All POSTs should have a non-nil body
   177  	if request.Body == nil {
   178  		wfe.stats.httpErrorCount.With(prometheus.Labels{"type": "NoPOSTBody"}).Inc()
   179  		return berrors.MalformedError("No body on POST")
   180  	}
   181  
   182  	return nil
   183  }
   184  
   185  // nonceWellFormed checks whether a JWS' Nonce header is well-formed, returning
   186  // false if it is not. This avoids unnecessary RPCs to the nonce redemption
   187  // service.
   188  func nonceWellFormed(nonceHeader string, prefixLen int) bool {
   189  	if len(nonceHeader) <= prefixLen {
   190  		// Nonce header was an unexpected length because there is either:
   191  		// 1) no nonce, or
   192  		// 2) no nonce material after the prefix.
   193  		return false
   194  	}
   195  	body, err := base64.RawURLEncoding.DecodeString(nonceHeader[prefixLen:])
   196  	if err != nil {
   197  		// Nonce was not valid base64url.
   198  		return false
   199  	}
   200  	if len(body) != nonce.NonceLen {
   201  		// Nonce was an unexpected length.
   202  		return false
   203  	}
   204  	return true
   205  }
   206  
   207  // validNonce checks a JWS' Nonce header to ensure it is one that the
   208  // nonceService knows about, otherwise a bad nonce error is returned.
   209  // NOTE: this function assumes the JWS has already been verified with the
   210  // correct public key.
   211  func (wfe *WebFrontEndImpl) validNonce(ctx context.Context, header jose.Header) error {
   212  	if len(header.Nonce) == 0 {
   213  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSMissingNonce"}).Inc()
   214  		return berrors.BadNonceError("JWS has no anti-replay nonce")
   215  	}
   216  
   217  	if !nonceWellFormed(header.Nonce, nonce.PrefixLen) {
   218  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSMalformedNonce"}).Inc()
   219  		return berrors.BadNonceError("JWS has a malformed anti-replay nonce: %q", header.Nonce)
   220  	}
   221  
   222  	// Populate the context with the nonce prefix and HMAC key. These are
   223  	// used by a custom gRPC balancer, known as "noncebalancer", to route
   224  	// redemption RPCs to the backend that originally issued the nonce.
   225  	ctx = context.WithValue(ctx, nonce.PrefixCtxKey{}, header.Nonce[:nonce.PrefixLen])
   226  	ctx = context.WithValue(ctx, nonce.HMACKeyCtxKey{}, wfe.rncKey)
   227  
   228  	resp, err := wfe.rnc.Redeem(ctx, &noncepb.NonceMessage{Nonce: header.Nonce})
   229  	if err != nil {
   230  		rpcStatus, ok := status.FromError(err)
   231  		if ok && rpcStatus == nb.ErrNoBackendsMatchPrefix {
   232  			// Getting our sentinel ErrNoBackendsMatchPrefix status.Status means that
   233  			// the nonce backend which issued this nonce is presently unreachable or
   234  			// unrecognized by this WFE. As this is a transient failure, the client
   235  			// should retry their request with a fresh nonce.
   236  			wfe.stats.nonceNoMatchingBackendCount.Inc()
   237  			wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSNoBackendNonce"}).Inc()
   238  			return berrors.BadNonceError("JWS has a nonce whose prefix matches no nonce service: %q", header.Nonce)
   239  		}
   240  
   241  		if errors.Is(err, berrors.BadNonce) {
   242  			// Getting a berrors.BadNonce means that the nonce service itself had
   243  			// something to say about why the nonce was invalid; no need to wrap it.
   244  			wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSUnredeemableNonce"}).Inc()
   245  			return err
   246  		}
   247  
   248  		// We don't recognize this error, so just pass it upwards.
   249  		return fmt.Errorf("failed to redeem nonce: %w", err)
   250  	}
   251  
   252  	// TODO: Remove this clause, as we're updating the NonceService to return an
   253  	// error rather than Valid=false when redemption fails.
   254  	if !resp.Valid {
   255  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSUnredeemableNonce"}).Inc()
   256  		return berrors.BadNonceError("JWS has an expired anti-replay nonce: %q", header.Nonce)
   257  	}
   258  
   259  	return nil
   260  }
   261  
   262  // validPOSTURL checks the JWS' URL header against the expected URL based on the
   263  // HTTP request. This prevents a JWS intended for one endpoint being replayed
   264  // against a different endpoint. If the URL isn't present, is invalid, or
   265  // doesn't match the HTTP request a error is returned.
   266  func (wfe *WebFrontEndImpl) validPOSTURL(
   267  	request *http.Request,
   268  	header jose.Header) error {
   269  	extraHeaders := header.ExtraHeaders
   270  	// Check that there is at least one Extra Header
   271  	if len(extraHeaders) == 0 {
   272  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSNoExtraHeaders"}).Inc()
   273  		return berrors.MalformedError("JWS header parameter 'url' required")
   274  	}
   275  	// Try to read a 'url' Extra Header as a string
   276  	headerURL, ok := extraHeaders[jose.HeaderKey("url")].(string)
   277  	if !ok || len(headerURL) == 0 {
   278  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSMissingURL"}).Inc()
   279  		return berrors.MalformedError("JWS header parameter 'url' required")
   280  	}
   281  	// Compute the URL we expect to be in the JWS based on the HTTP request
   282  	expectedURL := url.URL{
   283  		Scheme: requestProto(request),
   284  		Host:   request.Host,
   285  		Path:   request.RequestURI,
   286  	}
   287  	// Check that the URL we expect is the one that was found in the signed JWS
   288  	// header
   289  	if expectedURL.String() != headerURL {
   290  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSMismatchedURL"}).Inc()
   291  		return berrors.MalformedError("JWS header parameter 'url' incorrect. Expected %q got %q", expectedURL.String(), headerURL)
   292  	}
   293  	return nil
   294  }
   295  
   296  // matchJWSURLs checks two JWS' URL headers are equal. This is used during key
   297  // rollover to check that the inner JWS URL matches the outer JWS URL. If the
   298  // JWS URLs do not match a error is returned.
   299  func (wfe *WebFrontEndImpl) matchJWSURLs(outer, inner jose.Header) error {
   300  	// Verify that the outer JWS has a non-empty URL header. This is strictly
   301  	// defensive since the expectation is that endpoints using `matchJWSURLs`
   302  	// have received at least one of their JWS from calling validPOSTForAccount(),
   303  	// which checks the outer JWS has the expected URL header before processing
   304  	// the inner JWS.
   305  	outerURL, ok := outer.ExtraHeaders[jose.HeaderKey("url")].(string)
   306  	if !ok || len(outerURL) == 0 {
   307  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "KeyRolloverOuterJWSNoURL"}).Inc()
   308  		return berrors.MalformedError("Outer JWS header parameter 'url' required")
   309  	}
   310  
   311  	// Verify the inner JWS has a non-empty URL header.
   312  	innerURL, ok := inner.ExtraHeaders[jose.HeaderKey("url")].(string)
   313  	if !ok || len(innerURL) == 0 {
   314  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "KeyRolloverInnerJWSNoURL"}).Inc()
   315  		return berrors.MalformedError("Inner JWS header parameter 'url' required")
   316  	}
   317  
   318  	// Verify that the outer URL matches the inner URL
   319  	if outerURL != innerURL {
   320  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "KeyRolloverMismatchedURLs"}).Inc()
   321  		return berrors.MalformedError("Outer JWS 'url' value %q does not match inner JWS 'url' value %q", outerURL, innerURL)
   322  	}
   323  
   324  	return nil
   325  }
   326  
   327  // bJSONWebSignature is a new distinct type which embeds the
   328  // *jose.JSONWebSignature concrete type. Callers must never create their own
   329  // bJSONWebSignature. Instead they should rely upon wfe.parseJWS instead.
   330  type bJSONWebSignature struct {
   331  	*jose.JSONWebSignature
   332  }
   333  
   334  // parseJWS extracts a JSONWebSignature from a byte slice. If there is an error
   335  // reading the JWS or it is unacceptable (e.g. too many/too few signatures,
   336  // presence of unprotected headers) a error is returned, otherwise a
   337  // *bJSONWebSignature is returned.
   338  func (wfe *WebFrontEndImpl) parseJWS(body []byte) (*bJSONWebSignature, error) {
   339  	// Parse the raw JWS JSON to check that:
   340  	// * the unprotected Header field is not being used.
   341  	// * the "signatures" member isn't present, just "signature".
   342  	//
   343  	// This must be done prior to `jose.parseSigned` since it will strip away
   344  	// these headers.
   345  	var unprotected struct {
   346  		Header     map[string]string
   347  		Signatures []any
   348  	}
   349  	err := json.Unmarshal(body, &unprotected)
   350  	if err != nil {
   351  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSUnmarshalFailed"}).Inc()
   352  		return nil, berrors.MalformedError("Parse error reading JWS")
   353  	}
   354  
   355  	// ACME v2 never uses values from the unprotected JWS header. Reject JWS that
   356  	// include unprotected headers.
   357  	if unprotected.Header != nil {
   358  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSUnprotectedHeaders"}).Inc()
   359  		return nil, berrors.MalformedError(
   360  			"JWS \"header\" field not allowed. All headers must be in \"protected\" field")
   361  	}
   362  
   363  	// ACME v2 never uses the "signatures" array of JSON serialized JWS, just the
   364  	// mandatory "signature" field. Reject JWS that include the "signatures" array.
   365  	if len(unprotected.Signatures) > 0 {
   366  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSMultiSig"}).Inc()
   367  		return nil, berrors.MalformedError(
   368  			"JWS \"signatures\" field not allowed. Only the \"signature\" field should contain a signature")
   369  	}
   370  
   371  	// Parse the JWS using go-jose and enforce that the expected one non-empty
   372  	// signature is present in the parsed JWS.
   373  	bodyStr := string(body)
   374  	parsedJWS, err := jose.ParseSigned(bodyStr, getSupportedAlgs())
   375  	if err != nil {
   376  		var unexpectedSignAlgoErr *jose.ErrUnexpectedSignatureAlgorithm
   377  		if errors.As(err, &unexpectedSignAlgoErr) {
   378  			wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSAlgorithmCheckFailed"}).Inc()
   379  			return nil, berrors.BadSignatureAlgorithmError(
   380  				"JWS signature header contains unsupported algorithm %q, expected one of %s",
   381  				unexpectedSignAlgoErr.Got,
   382  				getSupportedAlgs(),
   383  			)
   384  		}
   385  
   386  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSParseError"}).Inc()
   387  		return nil, berrors.MalformedError("Parse error reading JWS")
   388  	}
   389  	if len(parsedJWS.Signatures) > 1 {
   390  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSTooManySignatures"}).Inc()
   391  		return nil, berrors.MalformedError("Too many signatures in POST body")
   392  	}
   393  	if len(parsedJWS.Signatures) == 0 {
   394  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSNoSignatures"}).Inc()
   395  		return nil, berrors.MalformedError("POST JWS not signed")
   396  	}
   397  	if len(parsedJWS.Signatures) == 1 && len(parsedJWS.Signatures[0].Signature) == 0 {
   398  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSEmptySignature"}).Inc()
   399  		return nil, berrors.MalformedError("POST JWS not signed")
   400  	}
   401  
   402  	return &bJSONWebSignature{parsedJWS}, nil
   403  }
   404  
   405  // parseJWSRequest extracts a bJSONWebSignature from an HTTP POST request's body using parseJWS.
   406  func (wfe *WebFrontEndImpl) parseJWSRequest(request *http.Request) (*bJSONWebSignature, error) {
   407  	// Verify that the POST request has the expected headers
   408  	if err := wfe.validPOSTRequest(request); err != nil {
   409  		return nil, err
   410  	}
   411  
   412  	// Read the POST request body's bytes. validPOSTRequest has already checked
   413  	// that the body is non-nil
   414  	bodyBytes, err := io.ReadAll(http.MaxBytesReader(nil, request.Body, maxRequestSize))
   415  	if err != nil {
   416  		if err.Error() == "http: request body too large" {
   417  			return nil, berrors.UnauthorizedError("request body too large")
   418  		}
   419  		wfe.stats.httpErrorCount.With(prometheus.Labels{"type": "UnableToReadReqBody"}).Inc()
   420  		return nil, errors.New("unable to read request body")
   421  	}
   422  
   423  	jws, err := wfe.parseJWS(bodyBytes)
   424  	if err != nil {
   425  		return nil, err
   426  	}
   427  
   428  	return jws, nil
   429  }
   430  
   431  // extractJWK extracts a JWK from the protected headers of a bJSONWebSignature
   432  // or returns a error. It expects that the JWS is using the embedded JWK style
   433  // of authentication and does not contain an embedded Key ID. Callers should
   434  // have acquired the headers from a bJSONWebSignature returned by parseJWS to
   435  // ensure it has the correct number of signatures present.
   436  func (wfe *WebFrontEndImpl) extractJWK(header jose.Header) (*jose.JSONWebKey, error) {
   437  	// extractJWK expects the request to be using an embedded JWK auth type and
   438  	// to not contain the mutually exclusive KeyID.
   439  	if err := wfe.enforceJWSAuthType(header, embeddedJWK); err != nil {
   440  		return nil, err
   441  	}
   442  
   443  	// We can be sure that JSONWebKey is != nil because we have already called
   444  	// enforceJWSAuthType()
   445  	key := header.JSONWebKey
   446  
   447  	// If the key isn't considered valid by go-jose return a error immediately
   448  	if !key.Valid() {
   449  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWKInvalid"}).Inc()
   450  		return nil, berrors.MalformedError("Invalid JWK in JWS header")
   451  	}
   452  
   453  	return key, nil
   454  }
   455  
   456  // acctIDFromURL extracts the numeric int64 account ID from a ACMEv1 or ACMEv2
   457  // account URL. If the acctURL has an invalid URL or the account ID in the
   458  // acctURL is non-numeric a MalformedError is returned.
   459  func (wfe *WebFrontEndImpl) acctIDFromURL(acctURL string, request *http.Request) (int64, error) {
   460  	// For normal ACME v2 accounts we expect the account URL has a prefix composed
   461  	// of the Host header and the acctPath.
   462  	expectedURLPrefix := web.RelativeEndpoint(request, acctPath)
   463  
   464  	// Process the acctURL to find only the trailing numeric account ID. Both the
   465  	// expected URL prefix and a legacy URL prefix are permitted in order to allow
   466  	// ACME v1 clients to use legacy accounts with unmodified account URLs for V2
   467  	// requests.
   468  	accountIDStr, hasPrefix := strings.CutPrefix(acctURL, expectedURLPrefix)
   469  	if !hasPrefix {
   470  		accountIDStr, hasPrefix = strings.CutPrefix(acctURL, wfe.LegacyKeyIDPrefix)
   471  		if !hasPrefix {
   472  			return 0, berrors.MalformedError("KeyID header contained an invalid account URL: %q", acctURL)
   473  		}
   474  	}
   475  
   476  	// Convert the raw account ID string to an int64 for use with the SA's
   477  	// GetRegistration RPC
   478  	accountID, err := strconv.ParseInt(accountIDStr, 10, 64)
   479  	if err != nil {
   480  		return 0, berrors.MalformedError("Malformed account ID in KeyID header URL: %q", acctURL)
   481  	}
   482  	return accountID, nil
   483  }
   484  
   485  // lookupJWK finds a JWK associated with the Key ID present in the provided
   486  // headers, returning the JWK and a pointer to the associated account, or a
   487  // error. It expects that the JWS header is using the embedded Key ID style of
   488  // authentication and does not contain an embedded JWK. Callers should have
   489  // acquired headers from a bJSONWebSignature.
   490  func (wfe *WebFrontEndImpl) lookupJWK(
   491  	header jose.Header,
   492  	ctx context.Context,
   493  	request *http.Request,
   494  	logEvent *web.RequestEvent) (*jose.JSONWebKey, *core.Registration, error) {
   495  	// We expect the request to be using an embedded Key ID auth type and to not
   496  	// contain the mutually exclusive embedded JWK.
   497  	if err := wfe.enforceJWSAuthType(header, embeddedKeyID); err != nil {
   498  		return nil, nil, err
   499  	}
   500  
   501  	accountURL := header.KeyID
   502  	accountID, err := wfe.acctIDFromURL(accountURL, request)
   503  	if err != nil {
   504  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSInvalidKeyID"}).Inc()
   505  		return nil, nil, err
   506  	}
   507  
   508  	// Try to find the account for this account ID
   509  	account, err := wfe.accountGetter.GetRegistration(ctx, &sapb.RegistrationID{Id: accountID})
   510  	if err != nil {
   511  		// If the account isn't found, return a suitable error
   512  		if errors.Is(err, berrors.NotFound) {
   513  			wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSKeyIDNotFound"}).Inc()
   514  			return nil, nil, berrors.AccountDoesNotExistError("Account %q not found", accountURL)
   515  		}
   516  
   517  		// If there was an error and it isn't a "Not Found" error, return
   518  		// a ServerInternal error since this is unexpected.
   519  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSKeyIDLookupFailed"}).Inc()
   520  		// Add an error to the log event with the internal error message
   521  		logEvent.AddError("calling SA.GetRegistration: %s", err)
   522  		return nil, nil, berrors.InternalServerError("Error retrieving account %q: %s", accountURL, err)
   523  	}
   524  
   525  	// Verify the account is not deactivated
   526  	if core.AcmeStatus(account.Status) != core.StatusValid {
   527  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSKeyIDAccountInvalid"}).Inc()
   528  		return nil, nil, berrors.UnauthorizedError("Account is not valid, has status %q", account.Status)
   529  	}
   530  
   531  	// Update the logEvent with the account information and return the JWK
   532  	logEvent.Requester = account.Id
   533  
   534  	acct, err := grpc.PbToRegistration(account)
   535  	if err != nil {
   536  		return nil, nil, fmt.Errorf("error unmarshalling account %q: %w", accountURL, err)
   537  	}
   538  	return acct.Key, &acct, nil
   539  }
   540  
   541  // validJWSForKey checks a provided JWS for a given HTTP request validates
   542  // correctly using the provided JWK. If the JWS verifies the protected payload
   543  // is returned. The key/JWS algorithms are verified and
   544  // the JWK is checked against the keyPolicy before any signature validation is
   545  // done. If the JWS signature validates correctly then the JWS nonce value
   546  // and the JWS URL are verified to ensure that they are correct.
   547  func (wfe *WebFrontEndImpl) validJWSForKey(
   548  	ctx context.Context,
   549  	jws *bJSONWebSignature,
   550  	jwk *jose.JSONWebKey,
   551  	request *http.Request) ([]byte, error) {
   552  	err := checkAlgorithm(jwk, jws.Signatures[0].Header)
   553  	if err != nil {
   554  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSAlgorithmCheckFailed"}).Inc()
   555  		return nil, err
   556  	}
   557  
   558  	// Verify the JWS signature with the public key.
   559  	// NOTE: It might seem insecure for the WFE to be trusted to verify
   560  	// client requests, i.e., that the verification should be done at the
   561  	// RA.  However the WFE is the RA's only view of the outside world
   562  	// *anyway*, so it could always lie about what key was used by faking
   563  	// the signature itself.
   564  	payload, err := jws.Verify(jwk)
   565  	if err != nil {
   566  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSVerifyFailed"}).Inc()
   567  		return nil, berrors.MalformedError("JWS verification error")
   568  	}
   569  
   570  	// Check that the JWS contains a correct Nonce header
   571  	if err := wfe.validNonce(ctx, jws.Signatures[0].Header); err != nil {
   572  		return nil, err
   573  	}
   574  
   575  	// Check that the HTTP request URL matches the URL in the signed JWS
   576  	if err := wfe.validPOSTURL(request, jws.Signatures[0].Header); err != nil {
   577  		return nil, err
   578  	}
   579  
   580  	// In the WFE1 package the check for the request URL required unmarshalling
   581  	// the payload JSON to check the "resource" field of the protected JWS body.
   582  	// This caught invalid JSON early and so we preserve this check by explicitly
   583  	// trying to unmarshal the payload (when it is non-empty to allow POST-as-GET
   584  	// behaviour) as part of the verification and failing early if it isn't valid JSON.
   585  	var parsedBody struct{}
   586  	err = json.Unmarshal(payload, &parsedBody)
   587  	if string(payload) != "" && err != nil {
   588  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWSBodyUnmarshalFailed"}).Inc()
   589  		return nil, berrors.MalformedError("Request payload did not parse as JSON")
   590  	}
   591  
   592  	return payload, nil
   593  }
   594  
   595  // validJWSForAccount checks that a given JWS is valid and verifies with the
   596  // public key associated to a known account specified by the JWS Key ID. If the
   597  // JWS is valid (e.g. the JWS is well formed, verifies with the JWK stored for the
   598  // specified key ID, specifies the correct URL, and has a valid nonce) then
   599  // `validJWSForAccount` returns the validated JWS body, the parsed
   600  // JSONWebSignature, and a pointer to the JWK's associated account. If any of
   601  // these conditions are not met or an error occurs only a error is returned.
   602  func (wfe *WebFrontEndImpl) validJWSForAccount(
   603  	jws *bJSONWebSignature,
   604  	request *http.Request,
   605  	ctx context.Context,
   606  	logEvent *web.RequestEvent) ([]byte, *bJSONWebSignature, *core.Registration, error) {
   607  	// Lookup the account and JWK for the key ID that authenticated the JWS
   608  	pubKey, account, err := wfe.lookupJWK(jws.Signatures[0].Header, ctx, request, logEvent)
   609  	if err != nil {
   610  		return nil, nil, nil, err
   611  	}
   612  
   613  	// Verify the JWS with the JWK from the SA
   614  	payload, err := wfe.validJWSForKey(ctx, jws, pubKey, request)
   615  	if err != nil {
   616  		return nil, nil, nil, err
   617  	}
   618  
   619  	return payload, jws, account, nil
   620  }
   621  
   622  // validPOSTForAccount checks that a given POST request has a valid JWS
   623  // using `validJWSForAccount`. If valid, the authenticated JWS body and the
   624  // registration that authenticated the body are returned. Otherwise a error is
   625  // returned. The returned JWS body may be empty if the request is a POST-as-GET
   626  // request.
   627  func (wfe *WebFrontEndImpl) validPOSTForAccount(
   628  	request *http.Request,
   629  	ctx context.Context,
   630  	logEvent *web.RequestEvent) ([]byte, *bJSONWebSignature, *core.Registration, error) {
   631  	// Parse the JWS from the POST request
   632  	jws, err := wfe.parseJWSRequest(request)
   633  	if err != nil {
   634  		return nil, nil, nil, err
   635  	}
   636  	return wfe.validJWSForAccount(jws, request, ctx, logEvent)
   637  }
   638  
   639  // validPOSTAsGETForAccount checks that a given POST request is valid using
   640  // `validPOSTForAccount`. It additionally validates that the JWS request payload
   641  // is empty, indicating that it is a POST-as-GET request per ACME draft 15+
   642  // section 6.3 "GET and POST-as-GET requests". If a non empty payload is
   643  // provided in the JWS the invalidPOSTAsGETErr error is returned. This
   644  // function is useful only for endpoints that do not need to handle both POSTs
   645  // with a body and POST-as-GET requests (e.g. Order, Certificate).
   646  func (wfe *WebFrontEndImpl) validPOSTAsGETForAccount(
   647  	request *http.Request,
   648  	ctx context.Context,
   649  	logEvent *web.RequestEvent) (*core.Registration, error) {
   650  	// Call validPOSTForAccount to verify the JWS and extract the body.
   651  	body, _, reg, err := wfe.validPOSTForAccount(request, ctx, logEvent)
   652  	if err != nil {
   653  		return nil, err
   654  	}
   655  	// Verify the POST-as-GET payload is empty
   656  	if string(body) != "" {
   657  		return nil, berrors.MalformedError("POST-as-GET requests must have an empty payload")
   658  	}
   659  	// To make log analysis easier we choose to elevate the pseudo ACME HTTP
   660  	// method "POST-as-GET" to the logEvent's Method, replacing the
   661  	// http.MethodPost value.
   662  	logEvent.Method = "POST-as-GET"
   663  	return reg, err
   664  }
   665  
   666  // validSelfAuthenticatedJWS checks that a given JWS verifies with the JWK
   667  // embedded in the JWS itself (e.g. self-authenticated). This type of JWS
   668  // is only used for creating new accounts or revoking a certificate by signing
   669  // the request with the private key corresponding to the certificate's public
   670  // key and embedding that public key in the JWS. All other request should be
   671  // validated using `validJWSforAccount`.
   672  // If the JWS validates (e.g. the JWS is well formed, verifies with the JWK
   673  // embedded in it, has the correct URL, and includes a valid nonce) then
   674  // `validSelfAuthenticatedJWS` returns the validated JWS body and the JWK that
   675  // was embedded in the JWS. Otherwise if the valid JWS conditions are not met or
   676  // an error occurs only a error is returned.
   677  // Note that this function does *not* enforce that the JWK abides by our goodkey
   678  // policies. This is because this method is used by the RevokeCertificate path,
   679  // which must allow JWKs which are signed by blocklisted (i.e. already revoked
   680  // due to compromise) keys, in case multiple clients attempt to revoke the same
   681  // cert.
   682  func (wfe *WebFrontEndImpl) validSelfAuthenticatedJWS(
   683  	ctx context.Context,
   684  	jws *bJSONWebSignature,
   685  	request *http.Request) ([]byte, *jose.JSONWebKey, error) {
   686  	// Extract the embedded JWK from the parsed protected JWS' headers
   687  	pubKey, err := wfe.extractJWK(jws.Signatures[0].Header)
   688  	if err != nil {
   689  		return nil, nil, err
   690  	}
   691  
   692  	// Verify the JWS with the embedded JWK
   693  	payload, err := wfe.validJWSForKey(ctx, jws, pubKey, request)
   694  	if err != nil {
   695  		return nil, nil, err
   696  	}
   697  
   698  	return payload, pubKey, nil
   699  }
   700  
   701  // validSelfAuthenticatedPOST checks that a given POST request has a valid JWS
   702  // using `validSelfAuthenticatedJWS`. It enforces that the JWK abides by our
   703  // goodkey policies (key algorithm, length, blocklist, etc).
   704  func (wfe *WebFrontEndImpl) validSelfAuthenticatedPOST(
   705  	ctx context.Context,
   706  	request *http.Request) ([]byte, *jose.JSONWebKey, error) {
   707  	// Parse the JWS from the POST request
   708  	jws, err := wfe.parseJWSRequest(request)
   709  	if err != nil {
   710  		return nil, nil, err
   711  	}
   712  
   713  	// Extract and validate the embedded JWK from the parsed JWS
   714  	payload, pubKey, err := wfe.validSelfAuthenticatedJWS(ctx, jws, request)
   715  	if err != nil {
   716  		return nil, nil, err
   717  	}
   718  
   719  	// If the key doesn't meet the GoodKey policy return a error
   720  	err = wfe.keyPolicy.GoodKey(ctx, pubKey.Key)
   721  	if err != nil {
   722  		if errors.Is(err, goodkey.ErrBadKey) {
   723  			wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "JWKRejectedByGoodKey"}).Inc()
   724  			return nil, nil, berrors.BadPublicKeyError("invalid request signing key: %s", err.Error())
   725  		}
   726  		return nil, nil, berrors.InternalServerError("internal error while checking JWK: %s", err)
   727  	}
   728  
   729  	return payload, pubKey, nil
   730  }
   731  
   732  // rolloverRequest is a client request to change the key for the account ID
   733  // provided from the specified old key to a new key (the embedded JWK in the
   734  // inner JWS).
   735  type rolloverRequest struct {
   736  	OldKey  jose.JSONWebKey
   737  	Account string
   738  }
   739  
   740  // rolloverOperation is a struct representing a requested rollover operation
   741  // from the specified old key to the new key for the given account ID.
   742  type rolloverOperation struct {
   743  	rolloverRequest
   744  	NewKey jose.JSONWebKey
   745  }
   746  
   747  // validKeyRollover checks if the innerJWS is a valid key rollover operation
   748  // given the outer JWS that carried it. It is assumed that the outerJWS has
   749  // already been validated per the normal ACME process using `validPOSTForAccount`.
   750  // It is *critical* this is the case since `validKeyRollover` does not check the
   751  // outerJWS signature. This function checks that:
   752  // 1) the inner JWS is valid and well formed
   753  // 2) the inner JWS has the same "url" header as the outer JWS
   754  // 3) the inner JWS is self-authenticated with an embedded JWK
   755  //
   756  // This function verifies that the inner JWS' body is a rolloverRequest instance
   757  // that specifies the correct oldKey. The returned rolloverOperation's NewKey
   758  // field will be set to the JWK from the inner JWS.
   759  //
   760  // If the request is valid a *rolloverOperation object is returned,
   761  // otherwise a error is returned. The caller is left to verify
   762  // whether the new key is appropriate (e.g. isn't being used by another existing
   763  // account) and that the account field of the rollover object matches the
   764  // account that verified the outer JWS.
   765  func (wfe *WebFrontEndImpl) validKeyRollover(
   766  	ctx context.Context,
   767  	outerJWS *bJSONWebSignature,
   768  	innerJWS *bJSONWebSignature,
   769  	oldKey *jose.JSONWebKey) (*rolloverOperation, error) {
   770  
   771  	// Extract the embedded JWK from the inner JWS' protected headers
   772  	innerJWK, err := wfe.extractJWK(innerJWS.Signatures[0].Header)
   773  	if err != nil {
   774  		return nil, err
   775  	}
   776  
   777  	// If the key doesn't meet the GoodKey policy return a error immediately
   778  	err = wfe.keyPolicy.GoodKey(ctx, innerJWK.Key)
   779  	if err != nil {
   780  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "KeyRolloverJWKRejectedByGoodKey"}).Inc()
   781  		return nil, berrors.BadPublicKeyError("invalid request signing key: %s", err.Error())
   782  	}
   783  
   784  	// Check that the public key and JWS algorithms match expected
   785  	err = checkAlgorithm(innerJWK, innerJWS.Signatures[0].Header)
   786  	if err != nil {
   787  		return nil, err
   788  	}
   789  
   790  	// Verify the inner JWS signature with the public key from the embedded JWK.
   791  	// NOTE(@cpu): We do not use `wfe.validJWSForKey` here because the inner JWS
   792  	// of a key rollover operation is special (e.g. has no nonce, doesn't have an
   793  	// HTTP request to match the URL to)
   794  	innerPayload, err := innerJWS.Verify(innerJWK)
   795  	if err != nil {
   796  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "KeyRolloverJWSVerifyFailed"}).Inc()
   797  		return nil, berrors.MalformedError("Inner JWS does not verify with embedded JWK")
   798  	}
   799  	// NOTE(@cpu): we do not stomp the web.RequestEvent's payload here since that is set
   800  	// from the outerJWS in validPOSTForAccount and contains the inner JWS and inner
   801  	// payload already.
   802  
   803  	// Verify that the outer and inner JWS protected URL headers match
   804  	if err := wfe.matchJWSURLs(outerJWS.Signatures[0].Header, innerJWS.Signatures[0].Header); err != nil {
   805  		return nil, err
   806  	}
   807  
   808  	var req rolloverRequest
   809  	if json.Unmarshal(innerPayload, &req) != nil {
   810  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "KeyRolloverUnmarshalFailed"}).Inc()
   811  		return nil, berrors.MalformedError("Inner JWS payload did not parse as JSON key rollover object")
   812  	}
   813  
   814  	// If there's no oldkey specified fail before trying to use
   815  	// core.PublicKeyEqual on a nil argument.
   816  	if req.OldKey.Key == nil {
   817  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "KeyRolloverWrongOldKey"}).Inc()
   818  		return nil, berrors.MalformedError("Inner JWS does not contain old key field matching current account key")
   819  	}
   820  
   821  	// We must validate that the inner JWS' rollover request specifies the correct
   822  	// oldKey.
   823  	keysEqual, err := core.PublicKeysEqual(req.OldKey.Key, oldKey.Key)
   824  	if err != nil {
   825  		return nil, berrors.MalformedError("Unable to compare new and old keys: %s", err.Error())
   826  	}
   827  	if !keysEqual {
   828  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "KeyRolloverWrongOldKey"}).Inc()
   829  		return nil, berrors.MalformedError("Inner JWS does not contain old key field matching current account key")
   830  	}
   831  
   832  	// Return a rolloverOperation populated with the validated old JWK, the
   833  	// requested account, and the new JWK extracted from the inner JWS.
   834  	return &rolloverOperation{
   835  		rolloverRequest: rolloverRequest{
   836  			OldKey:  *oldKey,
   837  			Account: req.Account,
   838  		},
   839  		NewKey: *innerJWK,
   840  	}, nil
   841  }