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

     1  package wfe2
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"crypto/x509"
     7  	"encoding/base64"
     8  	"encoding/json"
     9  	"encoding/pem"
    10  	"errors"
    11  	"fmt"
    12  	"math/big"
    13  	"math/rand/v2"
    14  	"net/http"
    15  	"net/netip"
    16  	"net/url"
    17  	"strconv"
    18  	"strings"
    19  	"time"
    20  
    21  	"github.com/jmhodges/clock"
    22  	"github.com/prometheus/client_golang/prometheus"
    23  	"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
    24  	"go.opentelemetry.io/otel/trace"
    25  	"google.golang.org/protobuf/types/known/durationpb"
    26  	"google.golang.org/protobuf/types/known/emptypb"
    27  
    28  	"github.com/letsencrypt/boulder/core"
    29  	corepb "github.com/letsencrypt/boulder/core/proto"
    30  	emailpb "github.com/letsencrypt/boulder/email/proto"
    31  	berrors "github.com/letsencrypt/boulder/errors"
    32  	"github.com/letsencrypt/boulder/features"
    33  	"github.com/letsencrypt/boulder/goodkey"
    34  	bgrpc "github.com/letsencrypt/boulder/grpc"
    35  	_ "github.com/letsencrypt/boulder/grpc/noncebalancer" // imported for its init function.
    36  	"github.com/letsencrypt/boulder/identifier"
    37  	"github.com/letsencrypt/boulder/issuance"
    38  	blog "github.com/letsencrypt/boulder/log"
    39  	"github.com/letsencrypt/boulder/metrics/measured_http"
    40  	"github.com/letsencrypt/boulder/nonce"
    41  	"github.com/letsencrypt/boulder/policy"
    42  	"github.com/letsencrypt/boulder/probs"
    43  	rapb "github.com/letsencrypt/boulder/ra/proto"
    44  	"github.com/letsencrypt/boulder/ratelimits"
    45  	"github.com/letsencrypt/boulder/revocation"
    46  	sapb "github.com/letsencrypt/boulder/sa/proto"
    47  	"github.com/letsencrypt/boulder/unpause"
    48  	"github.com/letsencrypt/boulder/web"
    49  )
    50  
    51  // Paths are the ACME-spec identified URL path-segments for various methods.
    52  // NOTE: In metrics/measured_http we make the assumption that these are all
    53  // lowercase plus hyphens. If you violate that assumption you should update
    54  // measured_http.
    55  const (
    56  	directoryPath     = "/directory"
    57  	newNoncePath      = "/acme/new-nonce"
    58  	newAcctPath       = "/acme/new-acct"
    59  	newOrderPath      = "/acme/new-order"
    60  	rolloverPath      = "/acme/key-change"
    61  	revokeCertPath    = "/acme/revoke-cert"
    62  	acctPath          = "/acme/acct/"
    63  	orderPath         = "/acme/order/"
    64  	authzPath         = "/acme/authz/"
    65  	challengePath     = "/acme/chall/"
    66  	finalizeOrderPath = "/acme/finalize/"
    67  	certPath          = "/acme/cert/"
    68  	renewalInfoPath   = "/acme/renewal-info/"
    69  
    70  	// Non-ACME paths.
    71  	getCertPath     = "/get/cert/"
    72  	getCertInfoPath = "/get/certinfo/"
    73  	buildIDPath     = "/build"
    74  	healthzPath     = "/healthz"
    75  )
    76  
    77  const (
    78  	headerRetryAfter = "Retry-After"
    79  	// Our 99th percentile finalize latency is 2.3s. Asking clients to wait 3s
    80  	// before polling the order to get an updated status means that >99% of
    81  	// clients will fetch the updated order object exactly once,.
    82  	orderRetryAfter = 3
    83  )
    84  
    85  var errIncompleteGRPCResponse = errors.New("incomplete gRPC response message")
    86  
    87  // WebFrontEndImpl provides all the logic for Boulder's web-facing interface,
    88  // i.e., ACME.  Its members configure the paths for various ACME functions,
    89  // plus a few other data items used in ACME.  Its methods are primarily handlers
    90  // for HTTPS requests for the various ACME functions.
    91  type WebFrontEndImpl struct {
    92  	ra rapb.RegistrationAuthorityClient
    93  	sa sapb.StorageAuthorityReadOnlyClient
    94  	ee emailpb.ExporterClient
    95  	// gnc is a nonce-service client used exclusively for the issuance of
    96  	// nonces. It's configured to route requests to backends colocated with the
    97  	// WFE.
    98  	gnc nonce.Getter
    99  	// rnc is a nonce-service client used exclusively for the redemption of
   100  	// nonces. It uses a custom RPC load balancer which is configured to route
   101  	// requests to backends based on the prefix and HMAC key passed as in the
   102  	// context of the request. The HMAC and prefix are passed using context keys
   103  	// `nonce.HMACKeyCtxKey` and `nonce.PrefixCtxKey`.
   104  	rnc nonce.Redeemer
   105  	// rncKey is the HMAC key used to derive the prefix of nonce backends used
   106  	// for nonce redemption.
   107  	rncKey        []byte
   108  	accountGetter AccountGetter
   109  	log           blog.Logger
   110  	clk           clock.Clock
   111  	stats         wfe2Stats
   112  
   113  	// certificateChains maps IssuerNameIDs to slice of []byte containing a leading
   114  	// newline and one or more PEM encoded certificates separated by a newline,
   115  	// sorted from leaf to root. The first []byte is the default certificate chain,
   116  	// and any subsequent []byte is an alternate certificate chain.
   117  	certificateChains map[issuance.NameID][][]byte
   118  
   119  	// issuerCertificates is a map of IssuerNameIDs to issuer certificates built with the
   120  	// first entry from each of the certificateChains. These certificates are used
   121  	// to verify the signature of certificates provided in revocation requests.
   122  	issuerCertificates map[issuance.NameID]*issuance.Certificate
   123  
   124  	// URL to the current subscriber agreement (should contain some version identifier)
   125  	SubscriberAgreementURL string
   126  
   127  	// DirectoryCAAIdentity is used for the /directory response's "meta"
   128  	// element's "caaIdentities" field. It should match the VA's issuerDomain
   129  	// field value.
   130  	DirectoryCAAIdentity string
   131  
   132  	// DirectoryWebsite is used for the /directory response's "meta" element's
   133  	// "website" field.
   134  	DirectoryWebsite string
   135  
   136  	// Allowed prefix for legacy accounts used by verify.go's `lookupJWK`.
   137  	// See `cmd/boulder-wfe2/main.go`'s comment on the configuration field
   138  	// `LegacyKeyIDPrefix` for more information.
   139  	LegacyKeyIDPrefix string
   140  
   141  	// Key policy.
   142  	keyPolicy goodkey.KeyPolicy
   143  
   144  	// CORS settings
   145  	AllowOrigins []string
   146  
   147  	// How many contacts to allow in a single NewAccount request.
   148  	maxContactsPerReg int
   149  
   150  	// requestTimeout is the per-request overall timeout.
   151  	requestTimeout time.Duration
   152  
   153  	// StaleTimeout determines the required staleness for certificates to be
   154  	// accessed via the Boulder-specific GET API. Certificates newer than
   155  	// staleTimeout must be accessed via POST-as-GET and the RFC 8555 ACME API. We
   156  	// do this to incentivize client developers to use the standard API.
   157  	staleTimeout time.Duration
   158  
   159  	limiter    *ratelimits.Limiter
   160  	txnBuilder *ratelimits.TransactionBuilder
   161  
   162  	unpauseSigner      unpause.JWTSigner
   163  	unpauseJWTLifetime time.Duration
   164  	unpauseURL         string
   165  
   166  	// certProfiles is a map of acceptable certificate profile names to
   167  	// descriptions (perhaps including URLs) of those profiles. NewOrder
   168  	// Requests with a profile name not present in this map will be rejected.
   169  	certProfiles map[string]string
   170  }
   171  
   172  // NewWebFrontEndImpl constructs a web service for Boulder
   173  func NewWebFrontEndImpl(
   174  	stats prometheus.Registerer,
   175  	clk clock.Clock,
   176  	keyPolicy goodkey.KeyPolicy,
   177  	certificateChains map[issuance.NameID][][]byte,
   178  	issuerCertificates map[issuance.NameID]*issuance.Certificate,
   179  	logger blog.Logger,
   180  	requestTimeout time.Duration,
   181  	staleTimeout time.Duration,
   182  	maxContactsPerReg int,
   183  	rac rapb.RegistrationAuthorityClient,
   184  	sac sapb.StorageAuthorityReadOnlyClient,
   185  	eec emailpb.ExporterClient,
   186  	gnc nonce.Getter,
   187  	rnc nonce.Redeemer,
   188  	rncKey []byte,
   189  	accountGetter AccountGetter,
   190  	limiter *ratelimits.Limiter,
   191  	txnBuilder *ratelimits.TransactionBuilder,
   192  	certProfiles map[string]string,
   193  	unpauseSigner unpause.JWTSigner,
   194  	unpauseJWTLifetime time.Duration,
   195  	unpauseURL string,
   196  ) (WebFrontEndImpl, error) {
   197  	if len(issuerCertificates) == 0 {
   198  		return WebFrontEndImpl{}, errors.New("must provide at least one issuer certificate")
   199  	}
   200  
   201  	if len(certificateChains) == 0 {
   202  		return WebFrontEndImpl{}, errors.New("must provide at least one certificate chain")
   203  	}
   204  
   205  	if gnc == nil {
   206  		return WebFrontEndImpl{}, errors.New("must provide a service for nonce issuance")
   207  	}
   208  
   209  	if rnc == nil {
   210  		return WebFrontEndImpl{}, errors.New("must provide a service for nonce redemption")
   211  	}
   212  
   213  	wfe := WebFrontEndImpl{
   214  		log:                logger,
   215  		clk:                clk,
   216  		keyPolicy:          keyPolicy,
   217  		certificateChains:  certificateChains,
   218  		issuerCertificates: issuerCertificates,
   219  		stats:              initStats(stats),
   220  		requestTimeout:     requestTimeout,
   221  		staleTimeout:       staleTimeout,
   222  		maxContactsPerReg:  maxContactsPerReg,
   223  		ra:                 rac,
   224  		sa:                 sac,
   225  		ee:                 eec,
   226  		gnc:                gnc,
   227  		rnc:                rnc,
   228  		rncKey:             rncKey,
   229  		accountGetter:      accountGetter,
   230  		limiter:            limiter,
   231  		txnBuilder:         txnBuilder,
   232  		certProfiles:       certProfiles,
   233  		unpauseSigner:      unpauseSigner,
   234  		unpauseJWTLifetime: unpauseJWTLifetime,
   235  		unpauseURL:         unpauseURL,
   236  	}
   237  
   238  	return wfe, nil
   239  }
   240  
   241  // HandleFunc registers a handler at the given path. It's
   242  // http.HandleFunc(), but with a wrapper around the handler that
   243  // provides some generic per-request functionality:
   244  //
   245  // * Set a Replay-Nonce header.
   246  //
   247  // * Respond to OPTIONS requests, including CORS preflight requests.
   248  //
   249  // * Set a no cache header
   250  //
   251  // * Respond http.StatusMethodNotAllowed for HTTP methods other than
   252  // those listed.
   253  //
   254  // * Set CORS headers when responding to CORS "actual" requests.
   255  //
   256  // * Never send a body in response to a HEAD request. Anything
   257  // written by the handler will be discarded if the method is HEAD.
   258  // Also, all handlers that accept GET automatically accept HEAD.
   259  func (wfe *WebFrontEndImpl) HandleFunc(mux *http.ServeMux, pattern string, h web.WFEHandlerFunc, methods ...string) {
   260  	methodsMap := make(map[string]bool)
   261  	for _, m := range methods {
   262  		methodsMap[m] = true
   263  	}
   264  	if methodsMap["GET"] && !methodsMap["HEAD"] {
   265  		// Allow HEAD for any resource that allows GET
   266  		methods = append(methods, "HEAD")
   267  		methodsMap["HEAD"] = true
   268  	}
   269  	methodsStr := strings.Join(methods, ", ")
   270  	handler := http.StripPrefix(pattern, web.NewTopHandler(wfe.log,
   271  		web.WFEHandlerFunc(func(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
   272  			span := trace.SpanFromContext(ctx)
   273  			span.SetName(pattern)
   274  
   275  			logEvent.Endpoint = pattern
   276  			if request.URL != nil {
   277  				logEvent.Slug = request.URL.Path
   278  			}
   279  			if request.Method != "GET" || pattern == newNoncePath {
   280  				nonceMsg, err := wfe.gnc.Nonce(ctx, &emptypb.Empty{})
   281  				if err != nil {
   282  					wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "unable to get nonce"), err)
   283  					return
   284  				}
   285  				response.Header().Set("Replay-Nonce", nonceMsg.Nonce)
   286  			}
   287  			// Per section 7.1 "Resources":
   288  			//   The "index" link relation is present on all resources other than the
   289  			//   directory and indicates the URL of the directory.
   290  			if pattern != directoryPath {
   291  				directoryURL := web.RelativeEndpoint(request, directoryPath)
   292  				response.Header().Add("Link", link(directoryURL, "index"))
   293  			}
   294  
   295  			switch request.Method {
   296  			case "HEAD":
   297  				// Go's net/http (and httptest) servers will strip out the body
   298  				// of responses for us. This keeps the Content-Length for HEAD
   299  				// requests as the same as GET requests per the spec.
   300  			case "OPTIONS":
   301  				wfe.Options(response, request, methodsStr, methodsMap)
   302  				return
   303  			}
   304  
   305  			// No cache header is set for all requests, succeed or fail.
   306  			addNoCacheHeader(response)
   307  
   308  			if !methodsMap[request.Method] {
   309  				response.Header().Set("Allow", methodsStr)
   310  				wfe.sendError(response, logEvent, probs.MethodNotAllowed(), nil)
   311  				return
   312  			}
   313  
   314  			wfe.setCORSHeaders(response, request, "")
   315  
   316  			timeout := wfe.requestTimeout
   317  			if timeout == 0 {
   318  				timeout = 5 * time.Minute
   319  			}
   320  			ctx, cancel := context.WithTimeout(ctx, timeout)
   321  
   322  			// Call the wrapped handler.
   323  			h(ctx, logEvent, response, request)
   324  			cancel()
   325  		}),
   326  	))
   327  	mux.Handle(pattern, handler)
   328  }
   329  
   330  func marshalIndent(v any) ([]byte, error) {
   331  	return json.MarshalIndent(v, "", "  ")
   332  }
   333  
   334  func (wfe *WebFrontEndImpl) writeJsonResponse(response http.ResponseWriter, logEvent *web.RequestEvent, status int, v any) error {
   335  	jsonReply, err := marshalIndent(v)
   336  	if err != nil {
   337  		return err // All callers are responsible for handling this error
   338  	}
   339  
   340  	response.Header().Set("Content-Type", "application/json")
   341  	response.WriteHeader(status)
   342  	_, err = response.Write(jsonReply)
   343  	if err != nil {
   344  		// Don't worry about returning this error because the caller will
   345  		// never handle it.
   346  		wfe.log.Warningf("Could not write response: %s", err)
   347  		logEvent.AddError("failed to write response: %s", err)
   348  	}
   349  	return nil
   350  }
   351  
   352  // requestProto returns "http" for HTTP requests and "https" for HTTPS
   353  // requests. It supports the use of "X-Forwarded-Proto" to override the protocol.
   354  func requestProto(request *http.Request) string {
   355  	proto := "http"
   356  
   357  	// If the request was received via TLS, use `https://` for the protocol
   358  	if request.TLS != nil {
   359  		proto = "https"
   360  	}
   361  
   362  	// Allow upstream proxies  to specify the forwarded protocol. Allow this value
   363  	// to override our own guess.
   364  	if specifiedProto := request.Header.Get("X-Forwarded-Proto"); specifiedProto != "" {
   365  		proto = specifiedProto
   366  	}
   367  
   368  	return proto
   369  }
   370  
   371  const randomDirKeyExplanationLink = "https://community.letsencrypt.org/t/adding-random-entries-to-the-directory/33417"
   372  
   373  func (wfe *WebFrontEndImpl) relativeDirectory(request *http.Request, directory map[string]any) ([]byte, error) {
   374  	// Create an empty map sized equal to the provided directory to store the
   375  	// relative-ized result
   376  	relativeDir := make(map[string]any, len(directory))
   377  
   378  	// Copy each entry of the provided directory into the new relative map,
   379  	// prefixing it with the request protocol and host.
   380  	for k, v := range directory {
   381  		if v == randomDirKeyExplanationLink {
   382  			relativeDir[k] = v
   383  			continue
   384  		}
   385  		switch v := v.(type) {
   386  		case string:
   387  			// Only relative-ize top level string values, e.g. not the "meta" element
   388  			relativeDir[k] = web.RelativeEndpoint(request, v)
   389  		default:
   390  			// If it isn't a string, put it into the results unmodified
   391  			relativeDir[k] = v
   392  		}
   393  	}
   394  
   395  	directoryJSON, err := marshalIndent(relativeDir)
   396  	// This should never happen since we are just marshalling known strings
   397  	if err != nil {
   398  		return nil, err
   399  	}
   400  
   401  	return directoryJSON, nil
   402  }
   403  
   404  // Handler returns an http.Handler that uses various functions for
   405  // various ACME-specified paths.
   406  func (wfe *WebFrontEndImpl) Handler(stats prometheus.Registerer, oTelHTTPOptions ...otelhttp.Option) http.Handler {
   407  	m := http.NewServeMux()
   408  
   409  	// POSTable ACME endpoints
   410  	wfe.HandleFunc(m, newAcctPath, wfe.NewAccount, "POST")
   411  	wfe.HandleFunc(m, acctPath, wfe.Account, "POST")
   412  	wfe.HandleFunc(m, revokeCertPath, wfe.RevokeCertificate, "POST")
   413  	wfe.HandleFunc(m, rolloverPath, wfe.KeyRollover, "POST")
   414  	wfe.HandleFunc(m, newOrderPath, wfe.NewOrder, "POST")
   415  	wfe.HandleFunc(m, finalizeOrderPath, wfe.FinalizeOrder, "POST")
   416  
   417  	// GETable and POST-as-GETable ACME endpoints
   418  	wfe.HandleFunc(m, directoryPath, wfe.Directory, "GET", "POST")
   419  	wfe.HandleFunc(m, newNoncePath, wfe.Nonce, "GET", "POST")
   420  	wfe.HandleFunc(m, orderPath, wfe.GetOrder, "GET", "POST")
   421  	wfe.HandleFunc(m, authzPath, wfe.AuthorizationHandler, "GET", "POST")
   422  	wfe.HandleFunc(m, challengePath, wfe.ChallengeHandler, "GET", "POST")
   423  	wfe.HandleFunc(m, certPath, wfe.Certificate, "GET", "POST")
   424  
   425  	// Boulder specific endpoints
   426  	wfe.HandleFunc(m, getCertPath, wfe.Certificate, "GET")
   427  	wfe.HandleFunc(m, getCertInfoPath, wfe.CertificateInfo, "GET")
   428  	wfe.HandleFunc(m, buildIDPath, wfe.BuildID, "GET")
   429  	wfe.HandleFunc(m, healthzPath, wfe.Healthz, "GET")
   430  
   431  	// Endpoint for draft-ietf-acme-ari
   432  	if features.Get().ServeRenewalInfo {
   433  		wfe.HandleFunc(m, renewalInfoPath, wfe.RenewalInfo, "GET", "POST")
   434  	}
   435  
   436  	// We don't use our special HandleFunc for "/" because it matches everything,
   437  	// meaning we can wind up returning 405 when we mean to return 404. See
   438  	// https://github.com/letsencrypt/boulder/issues/717
   439  	m.Handle("/", web.NewTopHandler(wfe.log, web.WFEHandlerFunc(wfe.Index)))
   440  	return measured_http.New(m, wfe.clk, stats, oTelHTTPOptions...)
   441  }
   442  
   443  // Method implementations
   444  
   445  // Index serves a simple identification page. It is not part of the ACME spec.
   446  func (wfe *WebFrontEndImpl) Index(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
   447  	// All requests that are not handled by our ACME endpoints ends up
   448  	// here. Set the our logEvent endpoint to "/" and the slug to the path
   449  	// minus "/" to make sure that we properly set log information about
   450  	// the request, even in the case of a 404
   451  	logEvent.Endpoint = "/"
   452  	logEvent.Slug = request.URL.Path[1:]
   453  
   454  	// http://golang.org/pkg/net/http/#example_ServeMux_Handle
   455  	// The "/" pattern matches everything, so we need to check
   456  	// that we're at the root here.
   457  	if request.URL.Path != "/" {
   458  		logEvent.AddError("Resource not found")
   459  		http.NotFound(response, request)
   460  		response.Header().Set("Content-Type", "application/problem+json")
   461  		return
   462  	}
   463  
   464  	if request.Method != "GET" {
   465  		response.Header().Set("Allow", "GET")
   466  		wfe.sendError(response, logEvent, probs.MethodNotAllowed(), errors.New("Bad method"))
   467  		return
   468  	}
   469  
   470  	addNoCacheHeader(response)
   471  	response.Header().Set("Content-Type", "text/html")
   472  	fmt.Fprintf(response, `<html>
   473  		<body>
   474  			This is an <a href="https://tools.ietf.org/html/rfc8555">ACME</a>
   475  			Certificate Authority running <a href="https://github.com/letsencrypt/boulder">Boulder</a>.
   476  			JSON directory is available at <a href="%s">%s</a>.
   477  		</body>
   478  	</html>
   479  	`, directoryPath, directoryPath)
   480  }
   481  
   482  func addNoCacheHeader(w http.ResponseWriter) {
   483  	w.Header().Add("Cache-Control", "public, max-age=0, no-cache")
   484  }
   485  
   486  func addRequesterHeader(w http.ResponseWriter, requester int64) {
   487  	if requester > 0 {
   488  		w.Header().Set("Boulder-Requester", strconv.FormatInt(requester, 10))
   489  	}
   490  }
   491  
   492  // Directory is an HTTP request handler that provides the directory
   493  // object stored in the WFE's DirectoryEndpoints member with paths prefixed
   494  // using the `request.Host` of the HTTP request.
   495  func (wfe *WebFrontEndImpl) Directory(
   496  	ctx context.Context,
   497  	logEvent *web.RequestEvent,
   498  	response http.ResponseWriter,
   499  	request *http.Request) {
   500  	directoryEndpoints := map[string]any{
   501  		"newAccount": newAcctPath,
   502  		"newNonce":   newNoncePath,
   503  		"revokeCert": revokeCertPath,
   504  		"newOrder":   newOrderPath,
   505  		"keyChange":  rolloverPath,
   506  	}
   507  
   508  	if features.Get().ServeRenewalInfo {
   509  		// ARI-capable clients are expected to add the trailing slash per the
   510  		// draft. We explicitly strip the trailing slash here so that clients
   511  		// don't need to add trailing slash handling in their own code, saving
   512  		// them minimal amounts of complexity.
   513  		directoryEndpoints["renewalInfo"] = strings.TrimRight(renewalInfoPath, "/")
   514  	}
   515  
   516  	if request.Method == http.MethodPost {
   517  		acct, err := wfe.validPOSTAsGETForAccount(request, ctx, logEvent)
   518  		if err != nil {
   519  			wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to validate JWS"), err)
   520  			return
   521  		}
   522  		logEvent.Requester = acct.ID
   523  	}
   524  
   525  	// Add a random key to the directory in order to make sure that clients don't hardcode an
   526  	// expected set of keys. This ensures that we can properly extend the directory when we
   527  	// need to add a new endpoint or meta element.
   528  	directoryEndpoints[core.RandomString(8)] = randomDirKeyExplanationLink
   529  
   530  	// ACME since draft-02 describes an optional "meta" directory entry. The
   531  	// meta entry may optionally contain a "termsOfService" URI for the
   532  	// current ToS.
   533  	metaMap := map[string]any{
   534  		"termsOfService": wfe.SubscriberAgreementURL,
   535  	}
   536  	// The "meta" directory entry may also include a []string of CAA identities
   537  	if wfe.DirectoryCAAIdentity != "" {
   538  		// The specification says caaIdentities is an array of strings. In
   539  		// practice Boulder's VA only allows configuring ONE CAA identity. Given
   540  		// that constraint it doesn't make sense to allow multiple directory CAA
   541  		// identities so we use just the `wfe.DirectoryCAAIdentity` alone.
   542  		metaMap["caaIdentities"] = []string{
   543  			wfe.DirectoryCAAIdentity,
   544  		}
   545  	}
   546  	if len(wfe.certProfiles) != 0 {
   547  		metaMap["profiles"] = wfe.certProfiles
   548  	}
   549  	// The "meta" directory entry may also include a string with a website URL
   550  	if wfe.DirectoryWebsite != "" {
   551  		metaMap["website"] = wfe.DirectoryWebsite
   552  	}
   553  	directoryEndpoints["meta"] = metaMap
   554  
   555  	response.Header().Set("Content-Type", "application/json")
   556  
   557  	relDir, err := wfe.relativeDirectory(request, directoryEndpoints)
   558  	if err != nil {
   559  		marshalProb := probs.ServerInternal("unable to marshal JSON directory")
   560  		wfe.sendError(response, logEvent, marshalProb, nil)
   561  		return
   562  	}
   563  
   564  	logEvent.Suppress()
   565  	response.Write(relDir)
   566  }
   567  
   568  // Nonce is an endpoint for getting a fresh nonce with an HTTP GET or HEAD
   569  // request. This endpoint only returns a status code header - the `HandleFunc`
   570  // wrapper ensures that a nonce is written in the correct response header.
   571  func (wfe *WebFrontEndImpl) Nonce(
   572  	ctx context.Context,
   573  	logEvent *web.RequestEvent,
   574  	response http.ResponseWriter,
   575  	request *http.Request) {
   576  	if request.Method == http.MethodPost {
   577  		acct, err := wfe.validPOSTAsGETForAccount(request, ctx, logEvent)
   578  		if err != nil {
   579  			wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to validate JWS"), err)
   580  			return
   581  		}
   582  		logEvent.Requester = acct.ID
   583  	}
   584  
   585  	statusCode := http.StatusNoContent
   586  	// The ACME specification says GET requests should receive http.StatusNoContent
   587  	// and HEAD/POST-as-GET requests should receive http.StatusOK.
   588  	if request.Method != "GET" {
   589  		statusCode = http.StatusOK
   590  	}
   591  	response.WriteHeader(statusCode)
   592  
   593  	// The ACME specification says the server MUST include a Cache-Control header
   594  	// field with the "no-store" directive in responses for the newNonce resource,
   595  	// in order to prevent caching of this resource.
   596  	response.Header().Set("Cache-Control", "no-store")
   597  
   598  	// No need to log successful nonce requests, they're boring.
   599  	logEvent.Suppress()
   600  }
   601  
   602  // sendError wraps web.SendError
   603  func (wfe *WebFrontEndImpl) sendError(response http.ResponseWriter, logEvent *web.RequestEvent, eerr any, ierr error) {
   604  	// TODO(#4980): Simplify this function to only take a single error argument,
   605  	// and use web.ProblemDetailsForError to extract the corresponding prob from
   606  	// that. For now, though, the third argument has to be `any` so that it can
   607  	// be either an error or a problem, and this function can handle either one.
   608  	var prob *probs.ProblemDetails
   609  	switch v := eerr.(type) {
   610  	case *probs.ProblemDetails:
   611  		prob = v
   612  	case error:
   613  		prob = web.ProblemDetailsForError(v, "")
   614  	default:
   615  		panic(fmt.Sprintf("wfe.sendError got %#v (type %T), but expected ProblemDetails or error", eerr, eerr))
   616  	}
   617  
   618  	if prob.Type == probs.BadSignatureAlgorithmProblem {
   619  		prob.Algorithms = getSupportedAlgs()
   620  	}
   621  
   622  	var bErr *berrors.BoulderError
   623  	if errors.As(ierr, &bErr) {
   624  		retryAfterSeconds := int(bErr.RetryAfter.Round(time.Second).Seconds())
   625  		if retryAfterSeconds > 0 {
   626  			response.Header().Add(headerRetryAfter, strconv.Itoa(retryAfterSeconds))
   627  			if bErr.Type == berrors.RateLimit {
   628  				response.Header().Add("Link", link("https://letsencrypt.org/docs/rate-limits", "help"))
   629  			}
   630  		}
   631  	}
   632  	if prob.HTTPStatus == http.StatusInternalServerError {
   633  		response.Header().Add(headerRetryAfter, "60")
   634  	}
   635  	wfe.stats.httpErrorCount.With(prometheus.Labels{"type": string(prob.Type)}).Inc()
   636  	web.SendError(wfe.log, response, logEvent, prob, ierr)
   637  }
   638  
   639  func link(url, relation string) string {
   640  	return fmt.Sprintf("<%s>;rel=\"%s\"", url, relation)
   641  }
   642  
   643  // contactsToEmails converts a slice of ACME contacts (e.g.
   644  // "mailto:person@example.com") to a slice of valid email addresses. If any of
   645  // the contacts contain non-mailto schemes, unparsable addresses, or forbidden
   646  // mail domains, it returns an error so that we can provide feedback to
   647  // misconfigured clients.
   648  func (wfe *WebFrontEndImpl) contactsToEmails(contacts []string) ([]string, error) {
   649  	if len(contacts) == 0 {
   650  		return nil, nil
   651  	}
   652  
   653  	if wfe.maxContactsPerReg > 0 && len(contacts) > wfe.maxContactsPerReg {
   654  		return nil, berrors.MalformedError("too many contacts provided: %d > %d", len(contacts), wfe.maxContactsPerReg)
   655  	}
   656  
   657  	var emails []string
   658  	for _, contact := range contacts {
   659  		if contact == "" {
   660  			return nil, berrors.InvalidEmailError("empty contact")
   661  		}
   662  
   663  		parsed, err := url.Parse(contact)
   664  		if err != nil {
   665  			return nil, berrors.InvalidEmailError("unparsable contact")
   666  		}
   667  
   668  		if parsed.Scheme != "mailto" {
   669  			return nil, berrors.UnsupportedContactError("only contact scheme 'mailto:' is supported")
   670  		}
   671  
   672  		if parsed.RawQuery != "" || contact[len(contact)-1] == '?' {
   673  			return nil, berrors.InvalidEmailError("contact email contains a question mark")
   674  		}
   675  
   676  		if parsed.Fragment != "" || contact[len(contact)-1] == '#' {
   677  			return nil, berrors.InvalidEmailError("contact email contains a '#'")
   678  		}
   679  
   680  		if !core.IsASCII(contact) {
   681  			return nil, berrors.InvalidEmailError("contact email contains non-ASCII characters")
   682  		}
   683  
   684  		err = policy.ValidEmail(parsed.Opaque)
   685  		if err != nil {
   686  			return nil, err
   687  		}
   688  
   689  		emails = append(emails, parsed.Opaque)
   690  	}
   691  
   692  	return emails, nil
   693  }
   694  
   695  // checkNewAccountLimits checks whether sufficient limit quota exists for the
   696  // creation of a new account. If so, that quota is spent. If an error is
   697  // encountered during the check, it is logged but not returned. A refund
   698  // function is returned that can be called to refund the quota if the account
   699  // creation fails, the func will be nil if any error was encountered during the
   700  // check.
   701  func (wfe *WebFrontEndImpl) checkNewAccountLimits(ctx context.Context, ip netip.Addr) (func(), error) {
   702  	txns, err := wfe.txnBuilder.NewAccountLimitTransactions(ip)
   703  	if err != nil {
   704  		return nil, fmt.Errorf("building new account limit transactions: %w", err)
   705  	}
   706  
   707  	d, err := wfe.limiter.BatchSpend(ctx, txns)
   708  	if err != nil {
   709  		return nil, fmt.Errorf("spending new account limits: %w", err)
   710  	}
   711  
   712  	err = d.Result(wfe.clk.Now())
   713  	if err != nil {
   714  		return nil, err
   715  	}
   716  
   717  	return func() {
   718  		_, err := wfe.limiter.BatchRefund(ctx, txns)
   719  		if err != nil {
   720  			wfe.log.Warningf("refunding new account limits: %s", err)
   721  		}
   722  	}, nil
   723  }
   724  
   725  // NewAccount is used by clients to submit a new account
   726  func (wfe *WebFrontEndImpl) NewAccount(
   727  	ctx context.Context,
   728  	logEvent *web.RequestEvent,
   729  	response http.ResponseWriter,
   730  	request *http.Request) {
   731  
   732  	// NewAccount uses `validSelfAuthenticatedPOST` instead of
   733  	// `validPOSTforAccount` because there is no account to authenticate against
   734  	// until after it is created!
   735  	body, key, err := wfe.validSelfAuthenticatedPOST(ctx, request)
   736  	if err != nil {
   737  		// validSelfAuthenticatedPOST handles its own setting of logEvent.Errors
   738  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to validate JWS"), err)
   739  		return
   740  	}
   741  
   742  	var accountCreateRequest struct {
   743  		Contact              []string `json:"contact"`
   744  		TermsOfServiceAgreed bool     `json:"termsOfServiceAgreed"`
   745  		OnlyReturnExisting   bool     `json:"onlyReturnExisting"`
   746  	}
   747  
   748  	err = json.Unmarshal(body, &accountCreateRequest)
   749  	if err != nil {
   750  		wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling JSON"), err)
   751  		return
   752  	}
   753  
   754  	returnExistingAcct := func(acctPB *corepb.Registration) {
   755  		if core.AcmeStatus(acctPB.Status) == core.StatusDeactivated {
   756  			// If there is an existing, but deactivated account, then return an unauthorized
   757  			// problem informing the user that this account was deactivated
   758  			wfe.sendError(response, logEvent, probs.Unauthorized(
   759  				"An account with the provided public key exists but is deactivated"), nil)
   760  			return
   761  		}
   762  
   763  		response.Header().Set("Location",
   764  			web.RelativeEndpoint(request, fmt.Sprintf("%s%d", acctPath, acctPB.Id)))
   765  		logEvent.Requester = acctPB.Id
   766  		addRequesterHeader(response, acctPB.Id)
   767  
   768  		acct, err := bgrpc.PbToRegistration(acctPB)
   769  		if err != nil {
   770  			wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling account"), err)
   771  			return
   772  		}
   773  
   774  		err = wfe.writeJsonResponse(response, logEvent, http.StatusOK, acct)
   775  		if err != nil {
   776  			// ServerInternal because we just created this account, and it
   777  			// should be OK.
   778  			wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling account"), err)
   779  			return
   780  		}
   781  	}
   782  
   783  	keyBytes, err := key.MarshalJSON()
   784  	if err != nil {
   785  		wfe.sendError(response, logEvent,
   786  			web.ProblemDetailsForError(err, "Error creating new account"), err)
   787  		return
   788  	}
   789  	existingAcct, err := wfe.sa.GetRegistrationByKey(ctx, &sapb.JSONWebKey{Jwk: keyBytes})
   790  	if err == nil {
   791  		returnExistingAcct(existingAcct)
   792  		return
   793  	} else if !errors.Is(err, berrors.NotFound) {
   794  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "failed check for existing account"), err)
   795  		return
   796  	}
   797  
   798  	// If the request included a true "OnlyReturnExisting" field and we did not
   799  	// find an existing registration with the key specified then we must return an
   800  	// error and not create a new account.
   801  	if accountCreateRequest.OnlyReturnExisting {
   802  		wfe.sendError(response, logEvent, probs.AccountDoesNotExist(
   803  			"No account exists with the provided key"), nil)
   804  		return
   805  	}
   806  
   807  	if !accountCreateRequest.TermsOfServiceAgreed {
   808  		wfe.sendError(response, logEvent, probs.Malformed("must agree to terms of service"), nil)
   809  		return
   810  	}
   811  
   812  	// Do this extraction now, so that we can reject requests whose contact field
   813  	// does not contain valid contacts before we actually create the account.
   814  	emails, err := wfe.contactsToEmails(accountCreateRequest.Contact)
   815  	if err != nil {
   816  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Error validating contact(s)"), nil)
   817  		return
   818  	}
   819  
   820  	ip, err := web.ExtractRequesterIP(request)
   821  	if err != nil {
   822  		wfe.sendError(
   823  			response,
   824  			logEvent,
   825  			probs.ServerInternal("couldn't parse the remote (that is, the client's) address"),
   826  			fmt.Errorf("couldn't parse RemoteAddr: %s", request.RemoteAddr),
   827  		)
   828  		return
   829  	}
   830  
   831  	refundLimits, err := wfe.checkNewAccountLimits(ctx, ip)
   832  	if err != nil {
   833  		if errors.Is(err, berrors.RateLimit) {
   834  			wfe.sendError(response, logEvent, probs.RateLimited(err.Error()), err)
   835  			return
   836  		} else {
   837  			// Proceed, since we don't want internal rate limit system failures to
   838  			// block all account creation.
   839  			logEvent.IgnoredRateLimitError = err.Error()
   840  		}
   841  	}
   842  
   843  	var newRegistrationSuccessful bool
   844  	defer func() {
   845  		if !newRegistrationSuccessful && refundLimits != nil {
   846  			go refundLimits()
   847  		}
   848  	}()
   849  
   850  	// Create corepb.Registration from provided account information
   851  	reg := corepb.Registration{
   852  		Agreement: wfe.SubscriberAgreementURL,
   853  		Key:       keyBytes,
   854  	}
   855  
   856  	acctPB, err := wfe.ra.NewRegistration(ctx, &reg)
   857  	if err != nil {
   858  		if errors.Is(err, berrors.Duplicate) {
   859  			existingAcct, err := wfe.sa.GetRegistrationByKey(ctx, &sapb.JSONWebKey{Jwk: keyBytes})
   860  			if err == nil {
   861  				returnExistingAcct(existingAcct)
   862  				return
   863  			}
   864  			// return error even if berrors.NotFound, as the duplicate key error we got from
   865  			// ra.NewRegistration indicates it _does_ already exist.
   866  			wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "checking for existing account"), err)
   867  			return
   868  		}
   869  		wfe.sendError(response, logEvent,
   870  			web.ProblemDetailsForError(err, "Error creating new account"), err)
   871  		return
   872  	}
   873  
   874  	registrationValid := func(reg *corepb.Registration) bool {
   875  		return !(len(reg.Key) == 0) && reg.Id != 0
   876  	}
   877  
   878  	if acctPB == nil || !registrationValid(acctPB) {
   879  		wfe.sendError(response, logEvent,
   880  			web.ProblemDetailsForError(err, "Error creating new account"), err)
   881  		return
   882  	}
   883  	acct, err := bgrpc.PbToRegistration(acctPB)
   884  	if err != nil {
   885  		wfe.sendError(response, logEvent,
   886  			web.ProblemDetailsForError(err, "Error creating new account"), err)
   887  		return
   888  	}
   889  	logEvent.Requester = acct.ID
   890  	addRequesterHeader(response, acct.ID)
   891  
   892  	acctURL := web.RelativeEndpoint(request, fmt.Sprintf("%s%d", acctPath, acct.ID))
   893  
   894  	response.Header().Add("Location", acctURL)
   895  	if len(wfe.SubscriberAgreementURL) > 0 {
   896  		response.Header().Add("Link", link(wfe.SubscriberAgreementURL, "terms-of-service"))
   897  	}
   898  
   899  	err = wfe.writeJsonResponse(response, logEvent, http.StatusCreated, acct)
   900  	if err != nil {
   901  		// ServerInternal because we just created this account, and it
   902  		// should be OK.
   903  		wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling account"), err)
   904  		return
   905  	}
   906  	newRegistrationSuccessful = true
   907  
   908  	if wfe.ee != nil && len(emails) > 0 {
   909  		_, err := wfe.ee.SendContacts(ctx, &emailpb.SendContactsRequest{
   910  			// Note: We are explicitly using the contacts provided by the
   911  			// subscriber here. The RA will eventually stop accepting contacts.
   912  			Emails: emails,
   913  		})
   914  		if err != nil {
   915  			wfe.sendError(response, logEvent, probs.ServerInternal("Error sending contacts"), err)
   916  			return
   917  		}
   918  	}
   919  }
   920  
   921  // parseRevocation accepts the payload for a revocation request and parses it
   922  // into both the certificate to be revoked and the requested revocation reason
   923  // (if any). Returns an error if any of the parsing fails, or if the given cert
   924  // or revocation reason don't pass simple static checks. Also populates some
   925  // metadata fields on the given logEvent.
   926  func (wfe *WebFrontEndImpl) parseRevocation(
   927  	jwsBody []byte, logEvent *web.RequestEvent) (*x509.Certificate, revocation.Reason, error) {
   928  	// Read the revoke request from the JWS payload
   929  	var revokeRequest struct {
   930  		CertificateDER core.JSONBuffer    `json:"certificate"`
   931  		Reason         *revocation.Reason `json:"reason"`
   932  	}
   933  	err := json.Unmarshal(jwsBody, &revokeRequest)
   934  	if err != nil {
   935  		return nil, 0, berrors.MalformedError("Unable to JSON parse revoke request")
   936  	}
   937  
   938  	// Parse the provided certificate
   939  	parsedCertificate, err := x509.ParseCertificate(revokeRequest.CertificateDER)
   940  	if err != nil {
   941  		return nil, 0, berrors.MalformedError("Unable to parse certificate DER")
   942  	}
   943  
   944  	// Compute and record the serial number of the provided certificate
   945  	serial := core.SerialToString(parsedCertificate.SerialNumber)
   946  	logEvent.Extra["CertificateSerial"] = serial
   947  	if revokeRequest.Reason != nil {
   948  		logEvent.Extra["RevocationReason"] = *revokeRequest.Reason
   949  	}
   950  
   951  	// Try to validate the signature on the provided cert using its corresponding
   952  	// issuer certificate.
   953  	issuerCert, ok := wfe.issuerCertificates[issuance.IssuerNameID(parsedCertificate)]
   954  	if !ok || issuerCert == nil {
   955  		return nil, 0, berrors.NotFoundError("Certificate from unrecognized issuer")
   956  	}
   957  	err = parsedCertificate.CheckSignatureFrom(issuerCert.Certificate)
   958  	if err != nil {
   959  		return nil, 0, berrors.NotFoundError("No such certificate")
   960  	}
   961  	logEvent.Identifiers = identifier.FromCert(parsedCertificate)
   962  
   963  	if parsedCertificate.NotAfter.Before(wfe.clk.Now()) {
   964  		return nil, 0, berrors.UnauthorizedError("Certificate is expired")
   965  	}
   966  
   967  	// Verify the revocation reason supplied is allowed
   968  	reason := revocation.Reason(0)
   969  	if revokeRequest.Reason != nil {
   970  		if !revocation.UserAllowedReason(*revokeRequest.Reason) {
   971  			return nil, 0, berrors.BadRevocationReasonError(int64(*revokeRequest.Reason))
   972  		}
   973  		reason = *revokeRequest.Reason
   974  	}
   975  
   976  	return parsedCertificate, reason, nil
   977  }
   978  
   979  type revocationEvidence struct {
   980  	Serial string
   981  	Reason revocation.Reason
   982  	RegID  int64
   983  	Method string
   984  }
   985  
   986  // revokeCertBySubscriberKey processes an outer JWS as a revocation request that
   987  // is authenticated by a KeyID and the associated account.
   988  func (wfe *WebFrontEndImpl) revokeCertBySubscriberKey(
   989  	ctx context.Context,
   990  	outerJWS *bJSONWebSignature,
   991  	request *http.Request,
   992  	logEvent *web.RequestEvent) error {
   993  	// For Key ID revocations we authenticate the outer JWS by using
   994  	// `validJWSForAccount` similar to other WFE endpoints
   995  	jwsBody, _, acct, err := wfe.validJWSForAccount(outerJWS, request, ctx, logEvent)
   996  	if err != nil {
   997  		return err
   998  	}
   999  
  1000  	cert, reason, err := wfe.parseRevocation(jwsBody, logEvent)
  1001  	if err != nil {
  1002  		return err
  1003  	}
  1004  
  1005  	wfe.log.AuditObject("Authenticated revocation", revocationEvidence{
  1006  		Serial: core.SerialToString(cert.SerialNumber),
  1007  		Reason: reason,
  1008  		RegID:  acct.ID,
  1009  		Method: "applicant",
  1010  	})
  1011  
  1012  	// The RA will confirm that the authenticated account either originally
  1013  	// issued the certificate, or has demonstrated control over all identifiers
  1014  	// in the certificate.
  1015  	_, err = wfe.ra.RevokeCertByApplicant(ctx, &rapb.RevokeCertByApplicantRequest{
  1016  		Cert:  cert.Raw,
  1017  		Code:  int64(reason),
  1018  		RegID: acct.ID,
  1019  	})
  1020  	if err != nil {
  1021  		return err
  1022  	}
  1023  
  1024  	return nil
  1025  }
  1026  
  1027  // revokeCertByCertKey processes an outer JWS as a revocation request that is
  1028  // authenticated by an embedded JWK. E.g. in the case where someone is
  1029  // requesting a revocation by using the keypair associated with the certificate
  1030  // to be revoked
  1031  func (wfe *WebFrontEndImpl) revokeCertByCertKey(
  1032  	ctx context.Context,
  1033  	outerJWS *bJSONWebSignature,
  1034  	request *http.Request,
  1035  	logEvent *web.RequestEvent) error {
  1036  	// For embedded JWK revocations we authenticate the outer JWS by using
  1037  	// `validSelfAuthenticatedJWS` similar to new-reg and key rollover.
  1038  	// We do *not* use `validSelfAuthenticatedPOST` here because we've already
  1039  	// read the HTTP request body in `parseJWSRequest` and it is now empty.
  1040  	jwsBody, jwk, prob := wfe.validSelfAuthenticatedJWS(ctx, outerJWS, request)
  1041  	if prob != nil {
  1042  		return prob
  1043  	}
  1044  
  1045  	cert, reason, err := wfe.parseRevocation(jwsBody, logEvent)
  1046  	if err != nil {
  1047  		return err
  1048  	}
  1049  
  1050  	// For embedded JWK revocations we decide if a requester is able to revoke a specific
  1051  	// certificate by checking that to-be-revoked certificate has the same public
  1052  	// key as the JWK that was used to authenticate the request
  1053  	if !core.KeyDigestEquals(jwk, cert.PublicKey) {
  1054  		return berrors.UnauthorizedError(
  1055  			"JWK embedded in revocation request must be the same public key as the cert to be revoked")
  1056  	}
  1057  
  1058  	wfe.log.AuditObject("Authenticated revocation", revocationEvidence{
  1059  		Serial: core.SerialToString(cert.SerialNumber),
  1060  		Reason: reason,
  1061  		RegID:  0,
  1062  		Method: "privkey",
  1063  	})
  1064  
  1065  	// The RA assumes here that the WFE2 has validated the JWS as proving
  1066  	// control of the private key corresponding to this certificate.
  1067  	_, err = wfe.ra.RevokeCertByKey(ctx, &rapb.RevokeCertByKeyRequest{
  1068  		Cert: cert.Raw,
  1069  	})
  1070  	if err != nil {
  1071  		return err
  1072  	}
  1073  
  1074  	return nil
  1075  }
  1076  
  1077  // RevokeCertificate is used by clients to request the revocation of a cert. The
  1078  // revocation request is handled uniquely based on the method of authentication
  1079  // used.
  1080  func (wfe *WebFrontEndImpl) RevokeCertificate(
  1081  	ctx context.Context,
  1082  	logEvent *web.RequestEvent,
  1083  	response http.ResponseWriter,
  1084  	request *http.Request) {
  1085  
  1086  	// The ACME specification handles the verification of revocation requests
  1087  	// differently from other endpoints. For this reason we do *not* immediately
  1088  	// call `wfe.validPOSTForAccount` like all of the other endpoints.
  1089  	// For this endpoint we need to accept a JWS with an embedded JWK, or a JWS
  1090  	// with an embedded key ID, handling each case differently in terms of which
  1091  	// certificates are authorized to be revoked by the requester
  1092  
  1093  	// Parse the JWS from the HTTP Request
  1094  	jws, err := wfe.parseJWSRequest(request)
  1095  	if err != nil {
  1096  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to validate JWS"), err)
  1097  		return
  1098  	}
  1099  
  1100  	// Figure out which type of authentication this JWS uses
  1101  	authType, err := checkJWSAuthType(jws.Signatures[0].Header)
  1102  	if err != nil {
  1103  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to validate JWS"), err)
  1104  		return
  1105  	}
  1106  
  1107  	// Handle the revocation request according to how it is authenticated, or if
  1108  	// the authentication type is unknown, error immediately
  1109  	switch authType {
  1110  	case embeddedKeyID:
  1111  		err = wfe.revokeCertBySubscriberKey(ctx, jws, request, logEvent)
  1112  	case embeddedJWK:
  1113  		err = wfe.revokeCertByCertKey(ctx, jws, request, logEvent)
  1114  	default:
  1115  		err = berrors.MalformedError("Malformed JWS, no KeyID or embedded JWK")
  1116  	}
  1117  	if err != nil {
  1118  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to revoke"), err)
  1119  		return
  1120  	}
  1121  
  1122  	response.WriteHeader(http.StatusOK)
  1123  }
  1124  
  1125  // ChallengeHandler handles POST requests to challenge URLs of the form /acme/chall/{regID}/{authzID}/{challID}.
  1126  func (wfe *WebFrontEndImpl) ChallengeHandler(
  1127  	ctx context.Context,
  1128  	logEvent *web.RequestEvent,
  1129  	response http.ResponseWriter,
  1130  	request *http.Request) {
  1131  	slug := strings.Split(request.URL.Path, "/")
  1132  	if len(slug) != 3 {
  1133  		wfe.sendError(response, logEvent, probs.NotFound("No such challenge"), nil)
  1134  		return
  1135  	}
  1136  	// TODO(#7683): the regID is currently ignored.
  1137  	wfe.Challenge(ctx, logEvent, response, request, slug[1], slug[2])
  1138  }
  1139  
  1140  // Challenge handles POSTS to both formats of challenge URLs.
  1141  func (wfe *WebFrontEndImpl) Challenge(
  1142  	ctx context.Context,
  1143  	logEvent *web.RequestEvent,
  1144  	response http.ResponseWriter,
  1145  	request *http.Request,
  1146  	authorizationIDStr string,
  1147  	challengeID string) {
  1148  	authorizationID, err := strconv.ParseInt(authorizationIDStr, 10, 64)
  1149  	if err != nil {
  1150  		wfe.sendError(response, logEvent, probs.Malformed("Invalid authorization ID"), nil)
  1151  		return
  1152  	}
  1153  	authzPB, err := wfe.ra.GetAuthorization(ctx, &rapb.GetAuthorizationRequest{Id: authorizationID})
  1154  	if err != nil {
  1155  		if errors.Is(err, berrors.NotFound) {
  1156  			wfe.sendError(response, logEvent, probs.NotFound("No such challenge"), nil)
  1157  		} else {
  1158  			wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Problem getting authorization"), err)
  1159  		}
  1160  		return
  1161  	}
  1162  
  1163  	// Ensure gRPC response is complete.
  1164  	if core.IsAnyNilOrZero(authzPB.Id, authzPB.Identifier, authzPB.Status, authzPB.Expires) {
  1165  		wfe.sendError(response, logEvent, probs.ServerInternal("Problem getting authorization"), errIncompleteGRPCResponse)
  1166  		return
  1167  	}
  1168  
  1169  	authz, err := bgrpc.PBToAuthz(authzPB)
  1170  	if err != nil {
  1171  		wfe.sendError(response, logEvent, probs.ServerInternal("Problem getting authorization"), err)
  1172  		return
  1173  	}
  1174  	challengeIndex := authz.FindChallengeByStringID(challengeID)
  1175  	if challengeIndex == -1 {
  1176  		wfe.sendError(response, logEvent, probs.NotFound("No such challenge"), nil)
  1177  		return
  1178  	}
  1179  
  1180  	if authz.Expires == nil || authz.Expires.Before(wfe.clk.Now()) {
  1181  		wfe.sendError(response, logEvent, probs.NotFound("Expired authorization"), nil)
  1182  		return
  1183  	}
  1184  
  1185  	logEvent.Identifiers = identifier.ACMEIdentifiers{authz.Identifier}
  1186  	logEvent.Status = string(authz.Status)
  1187  
  1188  	challenge := authz.Challenges[challengeIndex]
  1189  	switch request.Method {
  1190  	case "GET", "HEAD":
  1191  		wfe.getChallenge(response, request, authz, &challenge, logEvent)
  1192  
  1193  	case "POST":
  1194  		logEvent.ChallengeType = string(challenge.Type)
  1195  		wfe.postChallenge(ctx, response, request, authz, challengeIndex, logEvent)
  1196  	}
  1197  }
  1198  
  1199  // prepChallengeForDisplay takes a core.Challenge and prepares it for display to
  1200  // the client by filling in its URL field and clearing several unnecessary
  1201  // fields.
  1202  func (wfe *WebFrontEndImpl) prepChallengeForDisplay(
  1203  	request *http.Request,
  1204  	authz core.Authorization,
  1205  	challenge *core.Challenge,
  1206  ) {
  1207  	// Update the challenge URL to be relative to the HTTP request Host
  1208  	challenge.URL = web.RelativeEndpoint(request, fmt.Sprintf("%s%d/%s/%s", challengePath, authz.RegistrationID, authz.ID, challenge.StringID()))
  1209  
  1210  	// Internally, we store challenge error problems with just the short form
  1211  	// (e.g. "CAA") of the problem type. But for external display, we need to
  1212  	// prefix the error type with the RFC8555 ACME Error namespace.
  1213  	if challenge.Error != nil {
  1214  		challenge.Error.Type = probs.ErrorNS + challenge.Error.Type
  1215  	}
  1216  
  1217  	// If the authz has been marked invalid, consider all challenges on that authz
  1218  	// to be invalid as well.
  1219  	if authz.Status == core.StatusInvalid {
  1220  		challenge.Status = authz.Status
  1221  	}
  1222  
  1223  	// This field is not useful for the client, only internal debugging,
  1224  	for idx := range challenge.ValidationRecord {
  1225  		challenge.ValidationRecord[idx].ResolverAddrs = nil
  1226  	}
  1227  }
  1228  
  1229  // prepAuthorizationForDisplay takes a core.Authorization and prepares it for
  1230  // display to the client by preparing all its challenges.
  1231  func (wfe *WebFrontEndImpl) prepAuthorizationForDisplay(request *http.Request, authz *core.Authorization) {
  1232  	for i := range authz.Challenges {
  1233  		wfe.prepChallengeForDisplay(request, *authz, &authz.Challenges[i])
  1234  	}
  1235  
  1236  	// Shuffle the challenges so no one relies on their order.
  1237  	rand.Shuffle(len(authz.Challenges), func(i, j int) {
  1238  		authz.Challenges[i], authz.Challenges[j] = authz.Challenges[j], authz.Challenges[i]
  1239  	})
  1240  
  1241  	// The ACME spec forbids allowing "*" in authorization identifiers. Boulder
  1242  	// allows this internally as a means of tracking when an authorization
  1243  	// corresponds to a wildcard request (e.g. to handle CAA properly). We strip
  1244  	// the "*." prefix from the Authz's Identifier's Value here to respect the law
  1245  	// of the protocol.
  1246  	ident, isWildcard := strings.CutPrefix(authz.Identifier.Value, "*.")
  1247  	if isWildcard {
  1248  		authz.Identifier.Value = ident
  1249  		// Mark that the authorization corresponds to a wildcard request since we've
  1250  		// now removed the wildcard prefix from the identifier.
  1251  		authz.Wildcard = true
  1252  	}
  1253  }
  1254  
  1255  func (wfe *WebFrontEndImpl) getChallenge(
  1256  	response http.ResponseWriter,
  1257  	request *http.Request,
  1258  	authz core.Authorization,
  1259  	challenge *core.Challenge,
  1260  	logEvent *web.RequestEvent) {
  1261  	wfe.prepChallengeForDisplay(request, authz, challenge)
  1262  
  1263  	authzURL := urlForAuthz(authz, request)
  1264  	response.Header().Add("Location", challenge.URL)
  1265  	response.Header().Add("Link", link(authzURL, "up"))
  1266  
  1267  	err := wfe.writeJsonResponse(response, logEvent, http.StatusOK, challenge)
  1268  	if err != nil {
  1269  		// InternalServerError because this is a failure to decode data passed in
  1270  		// by the caller, which got it from the DB.
  1271  		wfe.sendError(response, logEvent, probs.ServerInternal("Failed to marshal challenge"), err)
  1272  		return
  1273  	}
  1274  }
  1275  
  1276  func (wfe *WebFrontEndImpl) postChallenge(
  1277  	ctx context.Context,
  1278  	response http.ResponseWriter,
  1279  	request *http.Request,
  1280  	authz core.Authorization,
  1281  	challengeIndex int,
  1282  	logEvent *web.RequestEvent) {
  1283  	body, _, currAcct, err := wfe.validPOSTForAccount(request, ctx, logEvent)
  1284  	addRequesterHeader(response, logEvent.Requester)
  1285  	if err != nil {
  1286  		// validPOSTForAccount handles its own setting of logEvent.Errors
  1287  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to validate JWS"), err)
  1288  		return
  1289  	}
  1290  
  1291  	// Check that the account ID matching the key used matches
  1292  	// the account ID on the authz object
  1293  	if currAcct.ID != authz.RegistrationID {
  1294  		wfe.sendError(response,
  1295  			logEvent,
  1296  			probs.Unauthorized("User account ID doesn't match account ID in authorization"),
  1297  			nil,
  1298  		)
  1299  		return
  1300  	}
  1301  
  1302  	// If the JWS body is empty then this POST is a POST-as-GET to retrieve
  1303  	// challenge details, not a POST to initiate a challenge
  1304  	if string(body) == "" {
  1305  		challenge := authz.Challenges[challengeIndex]
  1306  		wfe.getChallenge(response, request, authz, &challenge, logEvent)
  1307  		return
  1308  	}
  1309  
  1310  	// We can expect some clients to try and update a challenge for an authorization
  1311  	// that is already valid. In this case we don't need to process the challenge
  1312  	// update. It wouldn't be helpful, the overall authorization is already good!
  1313  	var returnAuthz core.Authorization
  1314  	if authz.Status == core.StatusValid {
  1315  		returnAuthz = authz
  1316  	} else {
  1317  
  1318  		// NOTE(@cpu): Historically a challenge update needed to include
  1319  		// a KeyAuthorization field. This is no longer the case, since both sides can
  1320  		// calculate the key authorization as needed. We unmarshal here only to check
  1321  		// that the POST body is valid JSON. Any data/fields included are ignored to
  1322  		// be kind to ACMEv2 implementations that still send a key authorization.
  1323  		var challengeUpdate struct{}
  1324  		err := json.Unmarshal(body, &challengeUpdate)
  1325  		if err != nil {
  1326  			wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling challenge response"), err)
  1327  			return
  1328  		}
  1329  
  1330  		authzPB, err := bgrpc.AuthzToPB(authz)
  1331  		if err != nil {
  1332  			wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to serialize authz"), err)
  1333  			return
  1334  		}
  1335  
  1336  		authzPB, err = wfe.ra.PerformValidation(ctx, &rapb.PerformValidationRequest{
  1337  			Authz:          authzPB,
  1338  			ChallengeIndex: int64(challengeIndex),
  1339  		})
  1340  		if err != nil || core.IsAnyNilOrZero(authzPB, authzPB.Id, authzPB.Identifier, authzPB.Status, authzPB.Expires) {
  1341  			wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to update challenge"), err)
  1342  			return
  1343  		}
  1344  
  1345  		updatedAuthz, err := bgrpc.PBToAuthz(authzPB)
  1346  		if err != nil {
  1347  			wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to deserialize authz"), err)
  1348  			return
  1349  		}
  1350  		returnAuthz = updatedAuthz
  1351  	}
  1352  
  1353  	// assumption: PerformValidation does not modify order of challenges
  1354  	challenge := returnAuthz.Challenges[challengeIndex]
  1355  	wfe.prepChallengeForDisplay(request, authz, &challenge)
  1356  
  1357  	authzURL := urlForAuthz(authz, request)
  1358  	response.Header().Add("Location", challenge.URL)
  1359  	response.Header().Add("Link", link(authzURL, "up"))
  1360  
  1361  	err = wfe.writeJsonResponse(response, logEvent, http.StatusOK, challenge)
  1362  	if err != nil {
  1363  		// ServerInternal because we made the challenges, they should be OK
  1364  		wfe.sendError(response, logEvent, probs.ServerInternal("Failed to marshal challenge"), err)
  1365  		return
  1366  	}
  1367  }
  1368  
  1369  // Account is used by a client to submit an update to their account.
  1370  func (wfe *WebFrontEndImpl) Account(
  1371  	ctx context.Context,
  1372  	logEvent *web.RequestEvent,
  1373  	response http.ResponseWriter,
  1374  	request *http.Request) {
  1375  	body, _, currAcct, err := wfe.validPOSTForAccount(request, ctx, logEvent)
  1376  	addRequesterHeader(response, logEvent.Requester)
  1377  	if err != nil {
  1378  		// validPOSTForAccount handles its own setting of logEvent.Errors
  1379  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to validate JWS"), err)
  1380  		return
  1381  	}
  1382  
  1383  	// Requests to this handler should have a path that leads to a known
  1384  	// account
  1385  	idStr := request.URL.Path
  1386  	id, err := strconv.ParseInt(idStr, 10, 64)
  1387  	if err != nil {
  1388  		wfe.sendError(response, logEvent, probs.Malformed(fmt.Sprintf("Account ID must be an integer, was %q", idStr)), err)
  1389  		return
  1390  	} else if id <= 0 {
  1391  		wfe.sendError(response, logEvent, probs.Malformed(fmt.Sprintf("Account ID must be a positive non-zero integer, was %d", id)), nil)
  1392  		return
  1393  	} else if id != currAcct.ID {
  1394  		wfe.sendError(response, logEvent, probs.Unauthorized("Request signing key did not match account key"), nil)
  1395  		return
  1396  	}
  1397  
  1398  	var acct *core.Registration
  1399  	if string(body) == "" || string(body) == "{}" {
  1400  		// An empty string means POST-as-GET (i.e. no update). A body of "{}" means
  1401  		// an update of zero fields, returning the unchanged object. This was the
  1402  		// recommended way to fetch the account object in ACMEv1.
  1403  		acct = currAcct
  1404  	} else {
  1405  		acct, err = wfe.updateAccount(ctx, body, currAcct)
  1406  		if err != nil {
  1407  			wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to update account"), nil)
  1408  			return
  1409  		}
  1410  	}
  1411  
  1412  	if len(wfe.SubscriberAgreementURL) > 0 {
  1413  		response.Header().Add("Link", link(wfe.SubscriberAgreementURL, "terms-of-service"))
  1414  	}
  1415  
  1416  	err = wfe.writeJsonResponse(response, logEvent, http.StatusOK, acct)
  1417  	if err != nil {
  1418  		wfe.sendError(response, logEvent, probs.ServerInternal("Failed to marshal account"), err)
  1419  		return
  1420  	}
  1421  }
  1422  
  1423  // updateAccount unmarshals an account update request from the provided
  1424  // requestBody to update the given registration. Important: It is assumed the
  1425  // request has already been authenticated by the caller. If the request is a
  1426  // valid update the resulting updated account is returned, otherwise a problem
  1427  // is returned.
  1428  func (wfe *WebFrontEndImpl) updateAccount(ctx context.Context, requestBody []byte, currAcct *core.Registration) (*core.Registration, error) {
  1429  	// Only the Status field of an account may be updated this way.
  1430  	// For key updates clients should be using the key change endpoint.
  1431  	var accountUpdateRequest struct {
  1432  		Status core.AcmeStatus `json:"status"`
  1433  	}
  1434  
  1435  	err := json.Unmarshal(requestBody, &accountUpdateRequest)
  1436  	if err != nil {
  1437  		return nil, berrors.MalformedError("parsing account update request: %s", err)
  1438  	}
  1439  
  1440  	switch accountUpdateRequest.Status {
  1441  	case core.StatusValid, "":
  1442  		// They probably intended to update their contact address, but we don't do
  1443  		// that anymore, so simply return their account as-is. We don't error out
  1444  		// here because it would break too many clients.
  1445  		return currAcct, nil
  1446  
  1447  	case core.StatusDeactivated:
  1448  		updatedAcct, err := wfe.ra.DeactivateRegistration(
  1449  			ctx, &rapb.DeactivateRegistrationRequest{RegistrationID: currAcct.ID})
  1450  		if err != nil {
  1451  			return nil, fmt.Errorf("deactivating account: %w", err)
  1452  		}
  1453  
  1454  		updatedReg, err := bgrpc.PbToRegistration(updatedAcct)
  1455  		if err != nil {
  1456  			return nil, fmt.Errorf("parsing deactivated account: %w", err)
  1457  		}
  1458  		return &updatedReg, nil
  1459  
  1460  	default:
  1461  		return nil, berrors.MalformedError("invalid status %q for account update request, must be %q or %q", accountUpdateRequest.Status, core.StatusValid, core.StatusDeactivated)
  1462  	}
  1463  }
  1464  
  1465  // deactivateAuthorization processes the given JWS POST body as a request to
  1466  // deactivate the provided authorization. If an error occurs it is written to
  1467  // the response writer. Important: `deactivateAuthorization` does not check that
  1468  // the requester is authorized to deactivate the given authorization. It is
  1469  // assumed that this check is performed prior to calling deactivateAuthorzation.
  1470  func (wfe *WebFrontEndImpl) deactivateAuthorization(
  1471  	ctx context.Context,
  1472  	authzPB *corepb.Authorization,
  1473  	logEvent *web.RequestEvent,
  1474  	response http.ResponseWriter,
  1475  	body []byte) bool {
  1476  	var req struct {
  1477  		Status core.AcmeStatus
  1478  	}
  1479  	err := json.Unmarshal(body, &req)
  1480  	if err != nil {
  1481  		wfe.sendError(response, logEvent, probs.Malformed("Error unmarshaling JSON"), err)
  1482  		return false
  1483  	}
  1484  	if req.Status != core.StatusDeactivated {
  1485  		wfe.sendError(response, logEvent, probs.Malformed("Invalid status value"), err)
  1486  		return false
  1487  	}
  1488  	_, err = wfe.ra.DeactivateAuthorization(ctx, authzPB)
  1489  	if err != nil {
  1490  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Error deactivating authorization"), err)
  1491  		return false
  1492  	}
  1493  	// Since the authorization passed to DeactivateAuthorization isn't
  1494  	// mutated locally by the function we must manually set the status
  1495  	// here before displaying the authorization to the user
  1496  	authzPB.Status = string(core.StatusDeactivated)
  1497  	return true
  1498  }
  1499  
  1500  // AuthorizationHandler handles requests to authorization URLs of the form /acme/authz/{regID}/{authzID}.
  1501  func (wfe *WebFrontEndImpl) AuthorizationHandler(
  1502  	ctx context.Context,
  1503  	logEvent *web.RequestEvent,
  1504  	response http.ResponseWriter,
  1505  	request *http.Request) {
  1506  	slug := strings.Split(request.URL.Path, "/")
  1507  	if len(slug) != 2 {
  1508  		wfe.sendError(response, logEvent, probs.NotFound("No such authorization"), nil)
  1509  		return
  1510  	}
  1511  	// TODO(#7683): The regID is currently ignored.
  1512  	wfe.Authorization(ctx, logEvent, response, request, slug[1])
  1513  }
  1514  
  1515  // Authorization handles both `/acme/authz/{authzID}` and `/acme/authz/{regID}/{authzID}` requests,
  1516  // after the calling function has parsed out the authzID.
  1517  func (wfe *WebFrontEndImpl) Authorization(
  1518  	ctx context.Context,
  1519  	logEvent *web.RequestEvent,
  1520  	response http.ResponseWriter,
  1521  	request *http.Request,
  1522  	authzIDStr string) {
  1523  	var requestAccount *core.Registration
  1524  	var requestBody []byte
  1525  	// If the request is a POST it is either:
  1526  	//   A) an update to an authorization to deactivate it
  1527  	//   B) a POST-as-GET to query the authorization details
  1528  	if request.Method == "POST" {
  1529  		// Both POST options need to be authenticated by an account
  1530  		body, _, acct, err := wfe.validPOSTForAccount(request, ctx, logEvent)
  1531  		addRequesterHeader(response, logEvent.Requester)
  1532  		if err != nil {
  1533  			wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to validate JWS"), err)
  1534  			return
  1535  		}
  1536  		requestAccount = acct
  1537  		requestBody = body
  1538  	}
  1539  
  1540  	authzID, err := strconv.ParseInt(authzIDStr, 10, 64)
  1541  	if err != nil {
  1542  		wfe.sendError(response, logEvent, probs.Malformed("Invalid authorization ID"), nil)
  1543  		return
  1544  	}
  1545  
  1546  	authzPB, err := wfe.ra.GetAuthorization(ctx, &rapb.GetAuthorizationRequest{Id: authzID})
  1547  	if errors.Is(err, berrors.NotFound) {
  1548  		wfe.sendError(response, logEvent, probs.NotFound("No such authorization"), nil)
  1549  		return
  1550  	} else if errors.Is(err, berrors.Malformed) {
  1551  		wfe.sendError(response, logEvent, probs.Malformed(err.Error()), nil)
  1552  		return
  1553  	} else if err != nil {
  1554  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Problem getting authorization"), err)
  1555  		return
  1556  	}
  1557  
  1558  	ident := identifier.FromProto(authzPB.Identifier)
  1559  
  1560  	// Ensure gRPC response is complete.
  1561  	if core.IsAnyNilOrZero(authzPB.Id, ident, authzPB.Status, authzPB.Expires) {
  1562  		wfe.sendError(response, logEvent, probs.ServerInternal("Problem getting authorization"), errIncompleteGRPCResponse)
  1563  		return
  1564  	}
  1565  
  1566  	logEvent.Identifiers = identifier.ACMEIdentifiers{ident}
  1567  	logEvent.Status = authzPB.Status
  1568  
  1569  	// After expiring, authorizations are inaccessible
  1570  	if authzPB.Expires.AsTime().Before(wfe.clk.Now()) {
  1571  		wfe.sendError(response, logEvent, probs.NotFound("Expired authorization"), nil)
  1572  		return
  1573  	}
  1574  
  1575  	// If this was a POST that has an associated requestAccount and that account
  1576  	// doesn't own the authorization, abort before trying to deactivate the authz
  1577  	// or return its details
  1578  	if requestAccount != nil && requestAccount.ID != authzPB.RegistrationID {
  1579  		wfe.sendError(response, logEvent,
  1580  			probs.Unauthorized("Account ID doesn't match ID for authorization"), nil)
  1581  		return
  1582  	}
  1583  
  1584  	// If the body isn't empty we know it isn't a POST-as-GET and must be an
  1585  	// attempt to deactivate an authorization.
  1586  	if string(requestBody) != "" {
  1587  		// If the deactivation fails return early as errors and return codes
  1588  		// have already been set. Otherwise continue so that the user gets
  1589  		// sent the deactivated authorization.
  1590  		if !wfe.deactivateAuthorization(ctx, authzPB, logEvent, response, requestBody) {
  1591  			return
  1592  		}
  1593  	}
  1594  
  1595  	authz, err := bgrpc.PBToAuthz(authzPB)
  1596  	if err != nil {
  1597  		wfe.sendError(response, logEvent, probs.ServerInternal("Problem getting authorization"), err)
  1598  		return
  1599  	}
  1600  
  1601  	wfe.prepAuthorizationForDisplay(request, &authz)
  1602  
  1603  	err = wfe.writeJsonResponse(response, logEvent, http.StatusOK, authz)
  1604  	if err != nil {
  1605  		// InternalServerError because this is a failure to decode from our DB.
  1606  		wfe.sendError(response, logEvent, probs.ServerInternal("Failed to JSON marshal authz"), err)
  1607  		return
  1608  	}
  1609  }
  1610  
  1611  // CertificateInfo is a Boulder-specific endpoint to return notAfter, even for serials
  1612  // which only appear in a precertificate and don't have a corresponding final cert.
  1613  //
  1614  // This is used by our CRL monitoring infrastructure to determine when it is acceptable
  1615  // for a serial to be removed from a CRL.
  1616  func (wfe *WebFrontEndImpl) CertificateInfo(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
  1617  	serial := request.URL.Path
  1618  	if !core.ValidSerial(serial) {
  1619  		wfe.sendError(response, logEvent, probs.NotFound("Certificate not found"), nil)
  1620  		return
  1621  	}
  1622  	metadata, err := wfe.sa.GetSerialMetadata(ctx, &sapb.Serial{Serial: serial})
  1623  	if err != nil {
  1624  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Error getting certificate metadata"), err)
  1625  		return
  1626  	}
  1627  	certInfoStruct := struct {
  1628  		NotAfter time.Time `json:"notAfter"`
  1629  	}{
  1630  		NotAfter: metadata.Expires.AsTime(),
  1631  	}
  1632  	err = wfe.writeJsonResponse(response, logEvent, http.StatusOK, certInfoStruct)
  1633  	if err != nil {
  1634  		wfe.sendError(response, logEvent, probs.ServerInternal("Error marshalling certInfoStruct"), err)
  1635  		return
  1636  	}
  1637  }
  1638  
  1639  // Certificate is used by clients to request a copy of their current certificate, or to
  1640  // request a reissuance of the certificate.
  1641  func (wfe *WebFrontEndImpl) Certificate(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
  1642  	var requesterAccount *core.Registration
  1643  	// Any POSTs to the Certificate endpoint should be POST-as-GET requests. There are
  1644  	// no POSTs with a body allowed for this endpoint.
  1645  	if request.Method == "POST" {
  1646  		acct, err := wfe.validPOSTAsGETForAccount(request, ctx, logEvent)
  1647  		if err != nil {
  1648  			wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to validate JWS"), err)
  1649  			return
  1650  		}
  1651  		requesterAccount = acct
  1652  	}
  1653  
  1654  	requestedChain := 0
  1655  	serial := request.URL.Path
  1656  
  1657  	// An alternate chain may be requested with the request path {serial}/{chain}, where chain
  1658  	// is a number - an index into the slice of chains for the issuer. If a specific chain is
  1659  	// not requested, then it defaults to zero - the default certificate chain for the issuer.
  1660  	serialAndChain := strings.SplitN(serial, "/", 2)
  1661  	if len(serialAndChain) == 2 {
  1662  		idx, err := strconv.Atoi(serialAndChain[1])
  1663  		if err != nil || idx < 0 {
  1664  			wfe.sendError(response, logEvent, probs.Malformed("Chain ID must be a non-negative integer"),
  1665  				fmt.Errorf("certificate chain id provided was not valid: %s", serialAndChain[1]))
  1666  			return
  1667  		}
  1668  		serial = serialAndChain[0]
  1669  		requestedChain = idx
  1670  	}
  1671  
  1672  	// Certificate paths consist of the CertBase path, plus exactly sixteen hex
  1673  	// digits.
  1674  	if !core.ValidSerial(serial) {
  1675  		wfe.sendError(
  1676  			response,
  1677  			logEvent,
  1678  			probs.NotFound("Certificate not found"),
  1679  			fmt.Errorf("certificate serial provided was not valid: %s", serial),
  1680  		)
  1681  		return
  1682  	}
  1683  	logEvent.Extra["RequestedSerial"] = serial
  1684  
  1685  	cert, err := wfe.sa.GetCertificate(ctx, &sapb.Serial{Serial: serial})
  1686  	if err != nil {
  1687  		if errors.Is(err, berrors.NotFound) {
  1688  			wfe.sendError(response, logEvent, probs.NotFound("Certificate not found"), nil)
  1689  		} else {
  1690  			wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Failed to retrieve certificate"), err)
  1691  		}
  1692  		return
  1693  	}
  1694  
  1695  	// Don't serve certificates from the /get/ path until they're a little stale,
  1696  	// to prevent ACME clients from using that path.
  1697  	if strings.HasPrefix(logEvent.Endpoint, getCertPath) && wfe.clk.Since(cert.Issued.AsTime()) < wfe.staleTimeout {
  1698  		wfe.sendError(response, logEvent, probs.Unauthorized(fmt.Sprintf(
  1699  			"Certificate is too new for GET API. You should only use this non-standard API to access resources created more than %s ago",
  1700  			wfe.staleTimeout)), nil)
  1701  		return
  1702  	}
  1703  
  1704  	// If there was a requesterAccount (e.g. because it was a POST-as-GET request)
  1705  	// then the requesting account must be the owner of the certificate, otherwise
  1706  	// return an unauthorized error.
  1707  	if requesterAccount != nil && requesterAccount.ID != cert.RegistrationID {
  1708  		wfe.sendError(response, logEvent, probs.Unauthorized("Account in use did not issue specified certificate"), nil)
  1709  		return
  1710  	}
  1711  
  1712  	responsePEM, prob := func() ([]byte, *probs.ProblemDetails) {
  1713  		leafPEM := pem.EncodeToMemory(&pem.Block{
  1714  			Type:  "CERTIFICATE",
  1715  			Bytes: cert.Der,
  1716  		})
  1717  
  1718  		parsedCert, err := x509.ParseCertificate(cert.Der)
  1719  		if err != nil {
  1720  			// If we can't parse one of our own certs there's a serious problem
  1721  			return nil, probs.ServerInternal(
  1722  				fmt.Sprintf(
  1723  					"unable to parse Boulder issued certificate with serial %#v: %s",
  1724  					serial,
  1725  					err),
  1726  			)
  1727  		}
  1728  
  1729  		issuerNameID := issuance.IssuerNameID(parsedCert)
  1730  		availableChains, ok := wfe.certificateChains[issuerNameID]
  1731  		if !ok || len(availableChains) == 0 {
  1732  			// If there is no wfe.certificateChains entry for the IssuerNameID then
  1733  			// we can't provide a chain for this cert. If the certificate is expired,
  1734  			// just return the bare cert. If the cert is still valid, then there is
  1735  			// a misconfiguration and we should treat it as an internal server error.
  1736  			if parsedCert.NotAfter.Before(wfe.clk.Now()) {
  1737  				return leafPEM, nil
  1738  			}
  1739  			return nil, probs.ServerInternal(
  1740  				fmt.Sprintf(
  1741  					"Certificate serial %#v has an unknown IssuerNameID %d - no PEM certificate chain associated.",
  1742  					serial,
  1743  					issuerNameID),
  1744  			)
  1745  		}
  1746  
  1747  		// If the requested chain is outside the bounds of the available chains,
  1748  		// then it is an error by the client - not found.
  1749  		if requestedChain < 0 || requestedChain >= len(availableChains) {
  1750  			return nil, probs.NotFound("Unknown issuance chain")
  1751  		}
  1752  
  1753  		// Double check that the signature validates.
  1754  		err = parsedCert.CheckSignatureFrom(wfe.issuerCertificates[issuerNameID].Certificate)
  1755  		if err != nil {
  1756  			return nil, probs.ServerInternal(
  1757  				fmt.Sprintf(
  1758  					"Certificate serial %#v has a signature which cannot be verified from issuer %d.",
  1759  					serial,
  1760  					issuerNameID),
  1761  			)
  1762  		}
  1763  
  1764  		// Add rel="alternate" links for every chain available for this issuer,
  1765  		// excluding the currently requested chain.
  1766  		for chainID := range availableChains {
  1767  			if chainID == requestedChain {
  1768  				continue
  1769  			}
  1770  			chainURL := web.RelativeEndpoint(request,
  1771  				fmt.Sprintf("%s%s/%d", certPath, serial, chainID))
  1772  			response.Header().Add("Link", link(chainURL, "alternate"))
  1773  		}
  1774  
  1775  		// Prepend the chain with the leaf certificate
  1776  		return append(leafPEM, availableChains[requestedChain]...), nil
  1777  	}()
  1778  	if prob != nil {
  1779  		wfe.sendError(response, logEvent, prob, nil)
  1780  		return
  1781  	}
  1782  
  1783  	// NOTE(@cpu): We must explicitly set the Content-Length header here. The Go
  1784  	// HTTP library will only add this header if the body is below a certain size
  1785  	// and with the addition of a PEM encoded certificate chain the body size of
  1786  	// this endpoint will exceed this threshold. Since we know the length we can
  1787  	// reliably set it ourselves and not worry.
  1788  	response.Header().Set("Content-Length", strconv.Itoa(len(responsePEM)))
  1789  	response.Header().Set("Content-Type", "application/pem-certificate-chain")
  1790  	response.WriteHeader(http.StatusOK)
  1791  	if _, err = response.Write(responsePEM); err != nil {
  1792  		wfe.log.Warningf("Could not write response: %s", err)
  1793  	}
  1794  }
  1795  
  1796  // BuildID tells the requester what build we're running.
  1797  func (wfe *WebFrontEndImpl) BuildID(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
  1798  	response.Header().Set("Content-Type", "text/plain")
  1799  	response.WriteHeader(http.StatusOK)
  1800  	detailsString := fmt.Sprintf("Boulder=(%s %s)", core.GetBuildID(), core.GetBuildTime())
  1801  	if _, err := fmt.Fprintln(response, detailsString); err != nil {
  1802  		wfe.log.Warningf("Could not write response: %s", err)
  1803  	}
  1804  }
  1805  
  1806  type WfeHealthzResponse struct {
  1807  	Details string
  1808  }
  1809  
  1810  // Healthz tells the requester whether we're ready to serve requests.
  1811  func (wfe *WebFrontEndImpl) Healthz(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
  1812  	status := http.StatusOK
  1813  	details := "OK"
  1814  
  1815  	if !wfe.txnBuilder.Ready() {
  1816  		status = http.StatusServiceUnavailable
  1817  		details = "waiting for overrides"
  1818  	}
  1819  
  1820  	jsonResponse, err := json.Marshal(WfeHealthzResponse{Details: details})
  1821  	if err != nil {
  1822  		wfe.log.Warningf("Could not marshal healthz response: %s", err)
  1823  	}
  1824  
  1825  	err = wfe.writeJsonResponse(response, logEvent, status, jsonResponse)
  1826  	if err != nil {
  1827  		wfe.log.Warningf("Could not write response: %s", err)
  1828  	}
  1829  }
  1830  
  1831  // Options responds to an HTTP OPTIONS request.
  1832  func (wfe *WebFrontEndImpl) Options(response http.ResponseWriter, request *http.Request, methodsStr string, methodsMap map[string]bool) {
  1833  	// Every OPTIONS request gets an Allow header with a list of supported methods.
  1834  	response.Header().Set("Allow", methodsStr)
  1835  
  1836  	// CORS preflight requests get additional headers. See
  1837  	// http://www.w3.org/TR/cors/#resource-preflight-requests
  1838  	reqMethod := request.Header.Get("Access-Control-Request-Method")
  1839  	if reqMethod == "" {
  1840  		reqMethod = "GET"
  1841  	}
  1842  	if methodsMap[reqMethod] {
  1843  		wfe.setCORSHeaders(response, request, methodsStr)
  1844  	}
  1845  }
  1846  
  1847  // setCORSHeaders() tells the client that CORS is acceptable for this
  1848  // request. If allowMethods == "" the request is assumed to be a CORS
  1849  // actual request and no Access-Control-Allow-Methods header will be
  1850  // sent.
  1851  func (wfe *WebFrontEndImpl) setCORSHeaders(response http.ResponseWriter, request *http.Request, allowMethods string) {
  1852  	reqOrigin := request.Header.Get("Origin")
  1853  	if reqOrigin == "" {
  1854  		// This is not a CORS request.
  1855  		return
  1856  	}
  1857  
  1858  	// Allow CORS if the current origin (or "*") is listed as an
  1859  	// allowed origin in config. Otherwise, disallow by returning
  1860  	// without setting any CORS headers.
  1861  	allow := false
  1862  	for _, ao := range wfe.AllowOrigins {
  1863  		if ao == "*" {
  1864  			response.Header().Set("Access-Control-Allow-Origin", "*")
  1865  			allow = true
  1866  			break
  1867  		} else if ao == reqOrigin {
  1868  			response.Header().Set("Vary", "Origin")
  1869  			response.Header().Set("Access-Control-Allow-Origin", ao)
  1870  			allow = true
  1871  			break
  1872  		}
  1873  	}
  1874  	if !allow {
  1875  		return
  1876  	}
  1877  
  1878  	if allowMethods != "" {
  1879  		// For an OPTIONS request: allow all methods handled at this URL.
  1880  		response.Header().Set("Access-Control-Allow-Methods", allowMethods)
  1881  	}
  1882  	// NOTE(@cpu): "Content-Type" is considered a 'simple header' that doesn't
  1883  	// need to be explicitly allowed in 'access-control-allow-headers', but only
  1884  	// when the value is one of: `application/x-www-form-urlencoded`,
  1885  	// `multipart/form-data`, or `text/plain`. Since `application/jose+json` is
  1886  	// not one of these values we must be explicit in saying that `Content-Type`
  1887  	// is an allowed header. See MDN for more details:
  1888  	// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
  1889  	response.Header().Set("Access-Control-Allow-Headers", "Content-Type")
  1890  	response.Header().Set("Access-Control-Expose-Headers", "Link, Replay-Nonce, Location")
  1891  	response.Header().Set("Access-Control-Max-Age", "86400")
  1892  }
  1893  
  1894  // KeyRollover allows a user to change their signing key
  1895  func (wfe *WebFrontEndImpl) KeyRollover(
  1896  	ctx context.Context,
  1897  	logEvent *web.RequestEvent,
  1898  	response http.ResponseWriter,
  1899  	request *http.Request) {
  1900  	// Validate the outer JWS on the key rollover in standard fashion using
  1901  	// validPOSTForAccount
  1902  	outerBody, outerJWS, acct, err := wfe.validPOSTForAccount(request, ctx, logEvent)
  1903  	addRequesterHeader(response, logEvent.Requester)
  1904  	if err != nil {
  1905  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to validate JWS"), err)
  1906  		return
  1907  	}
  1908  	oldKey := acct.Key
  1909  
  1910  	// Parse the inner JWS from the validated outer JWS body
  1911  	innerJWS, err := wfe.parseJWS(outerBody)
  1912  	if err != nil {
  1913  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to validate JWS"), err)
  1914  		return
  1915  	}
  1916  
  1917  	// Validate the inner JWS as a key rollover request for the outer JWS
  1918  	rolloverOperation, err := wfe.validKeyRollover(ctx, outerJWS, innerJWS, oldKey)
  1919  	if err != nil {
  1920  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to validate JWS"), err)
  1921  		return
  1922  	}
  1923  	newKey := rolloverOperation.NewKey
  1924  
  1925  	// Check that the rollover request's account URL matches the account URL used
  1926  	// to validate the outer JWS
  1927  	header := outerJWS.Signatures[0].Header
  1928  	if rolloverOperation.Account != header.KeyID {
  1929  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "KeyRolloverMismatchedAccount"}).Inc()
  1930  		wfe.sendError(response, logEvent, probs.Malformed(
  1931  			fmt.Sprintf("Inner key rollover request specified Account %q, but outer JWS has Key ID %q",
  1932  				rolloverOperation.Account, header.KeyID)), nil)
  1933  		return
  1934  	}
  1935  
  1936  	// Check that the new key isn't the same as the old key. This would fail as
  1937  	// part of the subsequent `wfe.SA.GetRegistrationByKey` check since the new key
  1938  	// will find the old account if its equal to the old account key. We
  1939  	// check new key against old key explicitly to save an RPC round trip and a DB
  1940  	// query for this easy rejection case
  1941  	keysEqual, err := core.PublicKeysEqual(newKey.Key, oldKey.Key)
  1942  	if err != nil {
  1943  		// This should not happen - both the old and new key have been validated by now
  1944  		wfe.sendError(response, logEvent, probs.ServerInternal("Unable to compare new and old keys"), err)
  1945  		return
  1946  	}
  1947  	if keysEqual {
  1948  		wfe.stats.joseErrorCount.With(prometheus.Labels{"type": "KeyRolloverUnchangedKey"}).Inc()
  1949  		wfe.sendError(response, logEvent, probs.Malformed(
  1950  			"New key specified by rollover request is the same as the old key"), nil)
  1951  		return
  1952  	}
  1953  
  1954  	// Marshal key to bytes
  1955  	newKeyBytes, err := newKey.MarshalJSON()
  1956  	if err != nil {
  1957  		wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling new key"), err)
  1958  	}
  1959  	// Check that the new key isn't already being used for an existing account
  1960  	existingAcct, err := wfe.sa.GetRegistrationByKey(ctx, &sapb.JSONWebKey{Jwk: newKeyBytes})
  1961  	if err == nil {
  1962  		response.Header().Set("Location",
  1963  			web.RelativeEndpoint(request, fmt.Sprintf("%s%d", acctPath, existingAcct.Id)))
  1964  		wfe.sendError(response, logEvent,
  1965  			probs.Conflict("New key is already in use for a different account"), err)
  1966  		return
  1967  	} else if !errors.Is(err, berrors.NotFound) {
  1968  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Failed to lookup existing keys"), err)
  1969  		return
  1970  	}
  1971  
  1972  	// Update the account key to the new key
  1973  	updatedAcctPb, err := wfe.ra.UpdateRegistrationKey(ctx, &rapb.UpdateRegistrationKeyRequest{RegistrationID: acct.ID, Jwk: newKeyBytes})
  1974  	if err != nil {
  1975  		if errors.Is(err, berrors.Duplicate) {
  1976  			// It is possible that between checking for the existing key, and performing the update
  1977  			// a parallel update or new account request happened and claimed the key. In this case
  1978  			// just retrieve the account again, and return an error as we would above with a Location
  1979  			// header
  1980  			existingAcct, err := wfe.sa.GetRegistrationByKey(ctx, &sapb.JSONWebKey{Jwk: newKeyBytes})
  1981  			if err != nil {
  1982  				wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "looking up account by key"), err)
  1983  				return
  1984  			}
  1985  			response.Header().Set("Location",
  1986  				web.RelativeEndpoint(request, fmt.Sprintf("%s%d", acctPath, existingAcct.Id)))
  1987  			wfe.sendError(response, logEvent,
  1988  				probs.Conflict("New key is already in use for a different account"), err)
  1989  			return
  1990  		}
  1991  		wfe.sendError(response, logEvent,
  1992  			web.ProblemDetailsForError(err, "Unable to update account with new key"), err)
  1993  		return
  1994  	}
  1995  	// Convert proto to registration for display
  1996  	updatedAcct, err := bgrpc.PbToRegistration(updatedAcctPb)
  1997  	if err != nil {
  1998  		wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling proto to registration"), err)
  1999  		return
  2000  	}
  2001  
  2002  	err = wfe.writeJsonResponse(response, logEvent, http.StatusOK, updatedAcct)
  2003  	if err != nil {
  2004  		wfe.sendError(response, logEvent, probs.ServerInternal("Failed to marshal updated account"), err)
  2005  	}
  2006  }
  2007  
  2008  type orderJSON struct {
  2009  	Status         core.AcmeStatus            `json:"status"`
  2010  	Expires        time.Time                  `json:"expires"`
  2011  	Identifiers    identifier.ACMEIdentifiers `json:"identifiers"`
  2012  	Authorizations []string                   `json:"authorizations"`
  2013  	Finalize       string                     `json:"finalize"`
  2014  	Profile        string                     `json:"profile,omitempty"`
  2015  	Certificate    string                     `json:"certificate,omitempty"`
  2016  	Error          *probs.ProblemDetails      `json:"error,omitempty"`
  2017  	Replaces       string                     `json:"replaces,omitempty"`
  2018  }
  2019  
  2020  // orderToOrderJSON converts a *corepb.Order instance into an orderJSON struct
  2021  // that is returned in HTTP API responses. It will convert the order names to
  2022  // DNS type identifiers and additionally create absolute URLs for the finalize
  2023  // URL and the certificate URL as appropriate.
  2024  func (wfe *WebFrontEndImpl) orderToOrderJSON(request *http.Request, order *corepb.Order) orderJSON {
  2025  	finalizeURL := web.RelativeEndpoint(request,
  2026  		fmt.Sprintf("%s%d/%d", finalizeOrderPath, order.RegistrationID, order.Id))
  2027  	respObj := orderJSON{
  2028  		Status:      core.AcmeStatus(order.Status),
  2029  		Expires:     order.Expires.AsTime(),
  2030  		Identifiers: identifier.FromProtoSlice(order.Identifiers),
  2031  		Finalize:    finalizeURL,
  2032  		Profile:     order.CertificateProfileName,
  2033  		Replaces:    order.Replaces,
  2034  	}
  2035  	// If there is an order error, prefix its type with the V2 namespace
  2036  	if order.Error != nil {
  2037  		prob, err := bgrpc.PBToProblemDetails(order.Error)
  2038  		if err != nil {
  2039  			wfe.log.AuditErrf("Internal error converting order ID %d "+
  2040  				"proto buf prob to problem details: %q", order.Id, err)
  2041  		}
  2042  		respObj.Error = prob
  2043  		respObj.Error.Type = probs.ErrorNS + respObj.Error.Type
  2044  	}
  2045  	for _, v2ID := range order.V2Authorizations {
  2046  		respObj.Authorizations = append(respObj.Authorizations, web.RelativeEndpoint(request, fmt.Sprintf("%s%d/%d", authzPath, order.RegistrationID, v2ID)))
  2047  	}
  2048  	if respObj.Status == core.StatusValid {
  2049  		certURL := web.RelativeEndpoint(request,
  2050  			fmt.Sprintf("%s%s", certPath, order.CertificateSerial))
  2051  		respObj.Certificate = certURL
  2052  	}
  2053  	return respObj
  2054  }
  2055  
  2056  // checkNewOrderLimits checks whether sufficient limit quota exists for the
  2057  // creation of a new order. If so, that quota is spent. If an error is
  2058  // encountered during the check, it is logged but not returned. A refund
  2059  // function is returned that can be used to refund the quota if the order is not
  2060  // created, the func will be nil if any error was encountered during the check.
  2061  //
  2062  // Precondition: idents must be a list of identifiers that all pass
  2063  // policy.WellFormedIdentifiers.
  2064  func (wfe *WebFrontEndImpl) checkNewOrderLimits(ctx context.Context, regId int64, idents identifier.ACMEIdentifiers, isRenewal bool) (func(), error) {
  2065  	txns, err := wfe.txnBuilder.NewOrderLimitTransactions(regId, idents, isRenewal)
  2066  	if err != nil {
  2067  		return nil, fmt.Errorf("building new order limit transactions: %w", err)
  2068  	}
  2069  
  2070  	d, err := wfe.limiter.BatchSpend(ctx, txns)
  2071  	if err != nil {
  2072  		return nil, fmt.Errorf("spending new order limits: %w", err)
  2073  	}
  2074  
  2075  	err = d.Result(wfe.clk.Now())
  2076  	if err != nil {
  2077  		return nil, err
  2078  	}
  2079  
  2080  	return func() {
  2081  		_, err := wfe.limiter.BatchRefund(ctx, txns)
  2082  		if err != nil {
  2083  			wfe.log.Warningf("refunding new order limits: %s", err)
  2084  		}
  2085  	}, nil
  2086  }
  2087  
  2088  // orderMatchesReplacement checks if the order matches the provided certificate
  2089  // as identified by the provided ARI CertID. This function ensures that:
  2090  //   - the certificate being replaced exists,
  2091  //   - the requesting account owns that certificate, and
  2092  //   - an identifier in this new order matches an identifier in the certificate being
  2093  //     replaced.
  2094  func (wfe *WebFrontEndImpl) orderMatchesReplacement(ctx context.Context, acct *core.Registration, idents identifier.ACMEIdentifiers, serial string) error {
  2095  	// It's okay to use GetCertificate (vs trying to get a precertificate),
  2096  	// because we don't intend to serve ARI for certs that never made it past
  2097  	// the precert stage.
  2098  	oldCert, err := wfe.sa.GetCertificate(ctx, &sapb.Serial{Serial: serial})
  2099  	if err != nil {
  2100  		if errors.Is(err, berrors.NotFound) {
  2101  			return berrors.NotFoundError("request included `replaces` field, but no current certificate with serial %q exists", serial)
  2102  		}
  2103  		return errors.New("failed to retrieve existing certificate")
  2104  	}
  2105  
  2106  	if oldCert.RegistrationID != acct.ID {
  2107  		return berrors.UnauthorizedError("requester account did not request the certificate being replaced by this order")
  2108  	}
  2109  	parsedCert, err := x509.ParseCertificate(oldCert.Der)
  2110  	if err != nil {
  2111  		return fmt.Errorf("error parsing certificate replaced by this order: %w", err)
  2112  	}
  2113  
  2114  	var identMatch bool
  2115  	for _, ident := range idents {
  2116  		if parsedCert.VerifyHostname(ident.Value) == nil {
  2117  			// At least one identifier in the new order matches an identifier in
  2118  			// the predecessor certificate.
  2119  			identMatch = true
  2120  			break
  2121  		}
  2122  	}
  2123  	if !identMatch {
  2124  		return berrors.MalformedError("identifiers in this order do not match any identifiers in the certificate being replaced")
  2125  	}
  2126  	return nil
  2127  }
  2128  
  2129  func (wfe *WebFrontEndImpl) determineARIWindow(ctx context.Context, serial string) (core.RenewalInfo, error) {
  2130  	// Check if the serial is impacted by an incident.
  2131  	result, err := wfe.sa.IncidentsForSerial(ctx, &sapb.Serial{Serial: serial})
  2132  	if err != nil {
  2133  		return core.RenewalInfo{}, fmt.Errorf("checking if existing certificate is impacted by an incident: %w", err)
  2134  	}
  2135  
  2136  	if len(result.Incidents) > 0 {
  2137  		// Find the earliest incident.
  2138  		var earliest *sapb.Incident
  2139  		for _, incident := range result.Incidents {
  2140  			if earliest == nil || incident.RenewBy.AsTime().Before(earliest.RenewBy.AsTime()) {
  2141  				earliest = incident
  2142  			}
  2143  		}
  2144  		// The existing cert is impacted by an incident, renew immediately.
  2145  		return core.RenewalInfoImmediate(wfe.clk.Now(), earliest.Url), nil
  2146  	}
  2147  
  2148  	// Check if the serial is revoked.
  2149  	status, err := wfe.sa.GetCertificateStatus(ctx, &sapb.Serial{Serial: serial})
  2150  	if err != nil {
  2151  		return core.RenewalInfo{}, fmt.Errorf("checking if existing certificate has been revoked: %w", err)
  2152  	}
  2153  
  2154  	if status.Status == string(core.OCSPStatusRevoked) {
  2155  		// The existing certificate is revoked, renew immediately.
  2156  		return core.RenewalInfoImmediate(wfe.clk.Now(), ""), nil
  2157  	}
  2158  
  2159  	// It's okay to use GetCertificate (vs trying to get a precertificate),
  2160  	// because we don't intend to serve ARI for certs that never made it past
  2161  	// the precert stage.
  2162  	cert, err := wfe.sa.GetCertificate(ctx, &sapb.Serial{Serial: serial})
  2163  	if err != nil {
  2164  		if errors.Is(err, berrors.NotFound) {
  2165  			return core.RenewalInfo{}, err
  2166  		}
  2167  		return core.RenewalInfo{}, fmt.Errorf("failed to retrieve existing certificate: %w", err)
  2168  	}
  2169  
  2170  	return core.RenewalInfoSimple(cert.Issued.AsTime(), cert.Expires.AsTime()), nil
  2171  }
  2172  
  2173  // validateReplacementOrder implements draft-ietf-acme-ari-03. For a new order
  2174  // to be considered a replacement for an existing certificate, the existing
  2175  // certificate:
  2176  //  1. MUST NOT have been replaced by another finalized order,
  2177  //  2. MUST be associated with the same ACME account as this request, and
  2178  //  3. MUST have at least one identifier in common with this request.
  2179  //
  2180  // There are three values returned by this function:
  2181  //   - The first return value is the serial number of the certificate being
  2182  //     replaced. If the order is not a replacement, this value is an empty
  2183  //     string.
  2184  //   - The second return value is a boolean indicating whether the order is
  2185  //     exempt from rate limits. If the order is a replacement and the request
  2186  //     is made within the suggested renewal window, this value is true.
  2187  //     Otherwise, this value is false.
  2188  //   - The last value is an error, this is non-nil unless the order is not a
  2189  //     replacement or there was an error while validating the replacement.
  2190  func (wfe *WebFrontEndImpl) validateReplacementOrder(ctx context.Context, acct *core.Registration, idents identifier.ACMEIdentifiers, replaces string) (string, bool, error) {
  2191  	if replaces == "" {
  2192  		// No replacement indicated.
  2193  		return "", false, nil
  2194  	}
  2195  
  2196  	decodedSerial, err := parseARICertID(replaces, wfe.issuerCertificates)
  2197  	if err != nil {
  2198  		return "", false, fmt.Errorf("while parsing ARI CertID an error occurred: %w", err)
  2199  	}
  2200  
  2201  	exists, err := wfe.sa.ReplacementOrderExists(ctx, &sapb.Serial{Serial: decodedSerial})
  2202  	if err != nil {
  2203  		return "", false, fmt.Errorf("checking replacement status of existing certificate: %w", err)
  2204  	}
  2205  	if exists.Exists {
  2206  		return "", false, berrors.AlreadyReplacedError(
  2207  			"cannot indicate an order replaces certificate with serial %q, which already has a replacement order",
  2208  			decodedSerial,
  2209  		)
  2210  	}
  2211  
  2212  	err = wfe.orderMatchesReplacement(ctx, acct, idents, decodedSerial)
  2213  	if err != nil {
  2214  		// The provided replacement field value failed to meet the required
  2215  		// criteria. We're going to return the error to the caller instead
  2216  		// of trying to create a regular (non-replacement) order.
  2217  		return "", false, fmt.Errorf("while checking that this order is a replacement: %w", err)
  2218  	}
  2219  	// This order is a replacement for an existing certificate.
  2220  	replaces = decodedSerial
  2221  
  2222  	// For an order to be exempt from rate limits, it must be a replacement
  2223  	// and the request must be made within the suggested renewal window.
  2224  	renewalInfo, err := wfe.determineARIWindow(ctx, replaces)
  2225  	if err != nil {
  2226  		return "", false, fmt.Errorf("while determining the current ARI renewal window: %w", err)
  2227  	}
  2228  
  2229  	return replaces, renewalInfo.SuggestedWindow.IsWithin(wfe.clk.Now()), nil
  2230  }
  2231  
  2232  func (wfe *WebFrontEndImpl) validateCertificateProfileName(profile string) error {
  2233  	if profile == "" {
  2234  		// No profile name is specified.
  2235  		return nil
  2236  	}
  2237  	if _, ok := wfe.certProfiles[profile]; !ok {
  2238  		// The profile name is not in the list of configured profiles.
  2239  		return fmt.Errorf("profile name %q not recognized", profile)
  2240  	}
  2241  
  2242  	return nil
  2243  }
  2244  
  2245  func (wfe *WebFrontEndImpl) checkIdentifiersPaused(ctx context.Context, orderIdents identifier.ACMEIdentifiers, regID int64) ([]string, error) {
  2246  	uniqueOrderIdents := identifier.Normalize(orderIdents)
  2247  	var idents []*corepb.Identifier
  2248  	for _, ident := range uniqueOrderIdents {
  2249  		idents = append(idents, &corepb.Identifier{
  2250  			Type:  string(ident.Type),
  2251  			Value: ident.Value,
  2252  		})
  2253  	}
  2254  
  2255  	paused, err := wfe.sa.CheckIdentifiersPaused(ctx, &sapb.PauseRequest{
  2256  		RegistrationID: regID,
  2257  		Identifiers:    idents,
  2258  	})
  2259  	if err != nil {
  2260  		return nil, err
  2261  	}
  2262  	if len(paused.Identifiers) <= 0 {
  2263  		// No identifiers are paused.
  2264  		return nil, nil
  2265  	}
  2266  
  2267  	// At least one of the requested identifiers is paused.
  2268  	pausedValues := make([]string, 0, len(paused.Identifiers))
  2269  	for _, ident := range paused.Identifiers {
  2270  		pausedValues = append(pausedValues, ident.Value)
  2271  	}
  2272  
  2273  	return pausedValues, nil
  2274  }
  2275  
  2276  // NewOrder is used by clients to create a new order object and a set of
  2277  // authorizations to fulfill for issuance.
  2278  func (wfe *WebFrontEndImpl) NewOrder(
  2279  	ctx context.Context,
  2280  	logEvent *web.RequestEvent,
  2281  	response http.ResponseWriter,
  2282  	request *http.Request) {
  2283  	body, _, acct, err := wfe.validPOSTForAccount(request, ctx, logEvent)
  2284  	addRequesterHeader(response, logEvent.Requester)
  2285  	if err != nil {
  2286  		// validPOSTForAccount handles its own setting of logEvent.Errors
  2287  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to validate JWS"), err)
  2288  		return
  2289  	}
  2290  
  2291  	// newOrderRequest is the JSON structure of the request body. We only
  2292  	// support the identifiers and replaces fields. If notBefore or notAfter are
  2293  	// sent we return a probs.Malformed as we do not support them.
  2294  	var newOrderRequest struct {
  2295  		Identifiers identifier.ACMEIdentifiers `json:"identifiers"`
  2296  		NotBefore   string
  2297  		NotAfter    string
  2298  		Replaces    string
  2299  		Profile     string
  2300  	}
  2301  	err = json.Unmarshal(body, &newOrderRequest)
  2302  	if err != nil {
  2303  		wfe.sendError(response, logEvent,
  2304  			probs.Malformed("Unable to unmarshal NewOrder request body"), err)
  2305  		return
  2306  	}
  2307  
  2308  	if len(newOrderRequest.Identifiers) == 0 {
  2309  		wfe.sendError(response, logEvent,
  2310  			probs.Malformed("NewOrder request did not specify any identifiers"), nil)
  2311  		return
  2312  	}
  2313  	if newOrderRequest.NotBefore != "" || newOrderRequest.NotAfter != "" {
  2314  		wfe.sendError(response, logEvent, probs.Malformed("NotBefore and NotAfter are not supported"), nil)
  2315  		return
  2316  	}
  2317  
  2318  	idents := newOrderRequest.Identifiers
  2319  	for _, ident := range idents {
  2320  		if !ident.Type.IsValid() {
  2321  			wfe.sendError(response, logEvent,
  2322  				probs.UnsupportedIdentifier("NewOrder request included unsupported identifier: type %q, value %q",
  2323  					ident.Type, ident.Value),
  2324  				nil)
  2325  			return
  2326  		}
  2327  		if ident.Value == "" {
  2328  			wfe.sendError(response, logEvent, probs.Malformed("NewOrder request included empty identifier"), nil)
  2329  			return
  2330  		}
  2331  	}
  2332  	idents = identifier.Normalize(idents)
  2333  	logEvent.Identifiers = idents
  2334  
  2335  	err = policy.WellFormedIdentifiers(idents)
  2336  	if err != nil {
  2337  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Invalid identifiers requested"), nil)
  2338  		return
  2339  	}
  2340  
  2341  	if features.Get().CheckIdentifiersPaused {
  2342  		pausedValues, err := wfe.checkIdentifiersPaused(ctx, idents, acct.ID)
  2343  		if err != nil {
  2344  			wfe.sendError(response, logEvent, probs.ServerInternal("Failure while checking pause status of identifiers"), err)
  2345  			return
  2346  		}
  2347  		if len(pausedValues) > 0 {
  2348  			jwt, err := unpause.GenerateJWT(wfe.unpauseSigner, acct.ID, pausedValues, wfe.unpauseJWTLifetime, wfe.clk)
  2349  			if err != nil {
  2350  				wfe.sendError(response, logEvent, probs.ServerInternal("Error generating JWT for unpause portal"), err)
  2351  			}
  2352  			msg := fmt.Sprintf(
  2353  				"Your account is temporarily prevented from requesting certificates for %s and possibly others. Please visit: %s",
  2354  				strings.Join(pausedValues, ", "),
  2355  				fmt.Sprintf("%s%s?jwt=%s", wfe.unpauseURL, unpause.GetForm, jwt),
  2356  			)
  2357  			wfe.sendError(response, logEvent, probs.Paused(msg), nil)
  2358  			return
  2359  		}
  2360  	}
  2361  
  2362  	var replacesSerial string
  2363  	var isARIRenewal bool
  2364  	replacesSerial, isARIRenewal, err = wfe.validateReplacementOrder(ctx, acct, idents, newOrderRequest.Replaces)
  2365  	if err != nil {
  2366  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Could not validate ARI 'replaces' field"), err)
  2367  		return
  2368  	}
  2369  
  2370  	var isRenewal bool
  2371  	if !isARIRenewal {
  2372  		// The Subscriber does not have an ARI exemption. However, we can check
  2373  		// if the order is a renewal, and thus exempt from the NewOrdersPerAccount
  2374  		// and CertificatesPerDomain limits.
  2375  		timestamps, err := wfe.sa.FQDNSetTimestampsForWindow(ctx, &sapb.CountFQDNSetsRequest{
  2376  			Identifiers: idents.ToProtoSlice(),
  2377  			Window:      durationpb.New(120 * 24 * time.Hour),
  2378  			Limit:       1,
  2379  		})
  2380  		if err != nil {
  2381  			wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "While checking renewal exemption status"), err)
  2382  			return
  2383  		}
  2384  		isRenewal = len(timestamps.Timestamps) > 0
  2385  	}
  2386  
  2387  	err = wfe.validateCertificateProfileName(newOrderRequest.Profile)
  2388  	if err != nil {
  2389  		// TODO(#7392) Provide link to profile documentation.
  2390  		wfe.sendError(response, logEvent, probs.InvalidProfile(err.Error()), err)
  2391  		return
  2392  	}
  2393  
  2394  	var refundLimits func()
  2395  	if !isARIRenewal {
  2396  		refundLimits, err = wfe.checkNewOrderLimits(ctx, acct.ID, idents, isRenewal)
  2397  		if err != nil {
  2398  			if errors.Is(err, berrors.RateLimit) {
  2399  				wfe.sendError(response, logEvent, probs.RateLimited(err.Error()), err)
  2400  				return
  2401  			} else {
  2402  				// Proceed, since we don't want internal rate limit system failures to
  2403  				// block all issuance.
  2404  				logEvent.IgnoredRateLimitError = err.Error()
  2405  			}
  2406  		}
  2407  	}
  2408  
  2409  	var newOrderSuccessful bool
  2410  	defer func() {
  2411  		wfe.stats.ariReplacementOrders.With(prometheus.Labels{
  2412  			"isReplacement": fmt.Sprintf("%t", replacesSerial != ""),
  2413  			"limitsExempt":  fmt.Sprintf("%t", isARIRenewal),
  2414  		}).Inc()
  2415  
  2416  		if !newOrderSuccessful && refundLimits != nil {
  2417  			go refundLimits()
  2418  		}
  2419  	}()
  2420  
  2421  	order, err := wfe.ra.NewOrder(ctx, &rapb.NewOrderRequest{
  2422  		RegistrationID:         acct.ID,
  2423  		Identifiers:            idents.ToProtoSlice(),
  2424  		CertificateProfileName: newOrderRequest.Profile,
  2425  		Replaces:               newOrderRequest.Replaces,
  2426  		ReplacesSerial:         replacesSerial,
  2427  	})
  2428  
  2429  	if err != nil || core.IsAnyNilOrZero(order, order.Id, order.RegistrationID, order.Identifiers, order.Created, order.Expires) {
  2430  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Error creating new order"), err)
  2431  		return
  2432  	}
  2433  	logEvent.Created = fmt.Sprintf("%d", order.Id)
  2434  
  2435  	orderURL := web.RelativeEndpoint(request,
  2436  		fmt.Sprintf("%s%d/%d", orderPath, acct.ID, order.Id))
  2437  	response.Header().Set("Location", orderURL)
  2438  
  2439  	respObj := wfe.orderToOrderJSON(request, order)
  2440  	err = wfe.writeJsonResponse(response, logEvent, http.StatusCreated, respObj)
  2441  	if err != nil {
  2442  		wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling order"), err)
  2443  		return
  2444  	}
  2445  	newOrderSuccessful = true
  2446  }
  2447  
  2448  // GetOrder is used to retrieve a existing order object
  2449  func (wfe *WebFrontEndImpl) GetOrder(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
  2450  	var requesterAccount *core.Registration
  2451  	// Any POSTs to the Order endpoint should be POST-as-GET requests. There are
  2452  	// no POSTs with a body allowed for this endpoint.
  2453  	if request.Method == http.MethodPost {
  2454  		acct, err := wfe.validPOSTAsGETForAccount(request, ctx, logEvent)
  2455  		if err != nil {
  2456  			wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to validate JWS"), err)
  2457  			return
  2458  		}
  2459  		requesterAccount = acct
  2460  	}
  2461  
  2462  	// Path prefix is stripped, so this should be like "<account ID>/<order ID>"
  2463  	fields := strings.SplitN(request.URL.Path, "/", 2)
  2464  	if len(fields) != 2 {
  2465  		wfe.sendError(response, logEvent, probs.NotFound("Invalid request path"), nil)
  2466  		return
  2467  	}
  2468  	acctID, err := strconv.ParseInt(fields[0], 10, 64)
  2469  	if err != nil {
  2470  		wfe.sendError(response, logEvent, probs.Malformed("Invalid account ID"), err)
  2471  		return
  2472  	}
  2473  	orderID, err := strconv.ParseInt(fields[1], 10, 64)
  2474  	if err != nil {
  2475  		wfe.sendError(response, logEvent, probs.Malformed("Invalid order ID"), err)
  2476  		return
  2477  	}
  2478  
  2479  	order, err := wfe.sa.GetOrder(ctx, &sapb.OrderRequest{Id: orderID})
  2480  	if err != nil {
  2481  		if errors.Is(err, berrors.NotFound) {
  2482  			wfe.sendError(response, logEvent, probs.NotFound(fmt.Sprintf("No order for ID %d", orderID)), nil)
  2483  			return
  2484  		}
  2485  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err,
  2486  			fmt.Sprintf("Failed to retrieve order for ID %d", orderID)), err)
  2487  		return
  2488  	}
  2489  
  2490  	if core.IsAnyNilOrZero(order.Id, order.Status, order.RegistrationID, order.Identifiers, order.Created, order.Expires) {
  2491  		wfe.sendError(response, logEvent, probs.ServerInternal(fmt.Sprintf("Failed to retrieve order for ID %d", orderID)), errIncompleteGRPCResponse)
  2492  		return
  2493  	}
  2494  
  2495  	if order.RegistrationID != acctID {
  2496  		wfe.sendError(response, logEvent, probs.NotFound(fmt.Sprintf("No order found for account ID %d", acctID)), nil)
  2497  		return
  2498  	}
  2499  
  2500  	// If the requesterAccount is not nil then this was an authenticated
  2501  	// POST-as-GET request and we need to verify the requesterAccount is the
  2502  	// order's owner.
  2503  	if requesterAccount != nil && order.RegistrationID != requesterAccount.ID {
  2504  		wfe.sendError(response, logEvent, probs.NotFound(fmt.Sprintf("No order found for account ID %d", acctID)), nil)
  2505  		return
  2506  	}
  2507  
  2508  	respObj := wfe.orderToOrderJSON(request, order)
  2509  
  2510  	if respObj.Status == core.StatusProcessing {
  2511  		response.Header().Set(headerRetryAfter, strconv.Itoa(orderRetryAfter))
  2512  	}
  2513  
  2514  	orderURL := web.RelativeEndpoint(request,
  2515  		fmt.Sprintf("%s%d/%d", orderPath, acctID, order.Id))
  2516  	response.Header().Set("Location", orderURL)
  2517  
  2518  	err = wfe.writeJsonResponse(response, logEvent, http.StatusOK, respObj)
  2519  	if err != nil {
  2520  		wfe.sendError(response, logEvent, probs.ServerInternal("Error marshaling order"), err)
  2521  		return
  2522  	}
  2523  }
  2524  
  2525  // FinalizeOrder is used to request issuance for a existing order object.
  2526  // Most processing of the order details is handled by the RA but
  2527  // we do attempt to throw away requests with invalid CSRs here.
  2528  func (wfe *WebFrontEndImpl) FinalizeOrder(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
  2529  	// Validate the POST body signature and get the authenticated account for this
  2530  	// finalize order request
  2531  	body, _, acct, err := wfe.validPOSTForAccount(request, ctx, logEvent)
  2532  	addRequesterHeader(response, logEvent.Requester)
  2533  	if err != nil {
  2534  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Unable to validate JWS"), err)
  2535  		return
  2536  	}
  2537  
  2538  	// Order URLs are like: /acme/finalize/<account>/<order>/. The prefix is
  2539  	// stripped by the time we get here.
  2540  	fields := strings.SplitN(request.URL.Path, "/", 2)
  2541  	if len(fields) != 2 {
  2542  		wfe.sendError(response, logEvent, probs.NotFound("Invalid request path"), nil)
  2543  		return
  2544  	}
  2545  	acctID, err := strconv.ParseInt(fields[0], 10, 64)
  2546  	if err != nil {
  2547  		wfe.sendError(response, logEvent, probs.Malformed("Invalid account ID"), nil)
  2548  		return
  2549  	}
  2550  	orderID, err := strconv.ParseInt(fields[1], 10, 64)
  2551  	if err != nil {
  2552  		wfe.sendError(response, logEvent, probs.Malformed("Invalid order ID"), nil)
  2553  		return
  2554  	}
  2555  
  2556  	if acct.ID != acctID {
  2557  		wfe.sendError(response, logEvent, probs.Malformed("Mismatched account ID"), nil)
  2558  		return
  2559  	}
  2560  
  2561  	order, err := wfe.sa.GetOrder(ctx, &sapb.OrderRequest{Id: orderID})
  2562  	if err != nil {
  2563  		if errors.Is(err, berrors.NotFound) {
  2564  			wfe.sendError(response, logEvent, probs.NotFound(fmt.Sprintf("No order for ID %d", orderID)), nil)
  2565  			return
  2566  		}
  2567  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err,
  2568  			fmt.Sprintf("Failed to retrieve order for ID %d", orderID)), err)
  2569  		return
  2570  	}
  2571  
  2572  	orderIdents := identifier.FromProtoSlice(order.Identifiers)
  2573  	if core.IsAnyNilOrZero(order.Id, order.Status, order.RegistrationID, orderIdents, order.Created, order.Expires) {
  2574  		wfe.sendError(response, logEvent, probs.ServerInternal(fmt.Sprintf("Failed to retrieve order for ID %d", orderID)), errIncompleteGRPCResponse)
  2575  		return
  2576  	}
  2577  
  2578  	// If the authenticated account ID doesn't match the order's registration ID
  2579  	// pretend it doesn't exist and abort.
  2580  	if acct.ID != order.RegistrationID {
  2581  		wfe.sendError(response, logEvent, probs.NotFound(fmt.Sprintf("No order found for account ID %d", acct.ID)), nil)
  2582  		return
  2583  	}
  2584  
  2585  	// Only ready orders can be finalized.
  2586  	if order.Status != string(core.StatusReady) {
  2587  		wfe.sendError(response, logEvent, probs.OrderNotReady(fmt.Sprintf("Order's status (%q) is not acceptable for finalization", order.Status)), nil)
  2588  		return
  2589  	}
  2590  
  2591  	// If the order is expired we can not finalize it and must return an error
  2592  	orderExpiry := order.Expires.AsTime()
  2593  	if orderExpiry.Before(wfe.clk.Now()) {
  2594  		wfe.sendError(response, logEvent, probs.NotFound(fmt.Sprintf("Order %d is expired", order.Id)), nil)
  2595  		return
  2596  	}
  2597  
  2598  	// Don't finalize orders with profiles we no longer recognize.
  2599  	if order.CertificateProfileName != "" {
  2600  		err = wfe.validateCertificateProfileName(order.CertificateProfileName)
  2601  		if err != nil {
  2602  			// TODO(#7392) Provide link to profile documentation.
  2603  			wfe.sendError(response, logEvent, probs.InvalidProfile(err.Error()), err)
  2604  			return
  2605  		}
  2606  	}
  2607  
  2608  	// The authenticated finalize message body should be an encoded CSR
  2609  	var rawCSR core.RawCertificateRequest
  2610  	err = json.Unmarshal(body, &rawCSR)
  2611  	if err != nil {
  2612  		wfe.sendError(response, logEvent,
  2613  			probs.Malformed("Error unmarshaling finalize order request"), err)
  2614  		return
  2615  	}
  2616  
  2617  	// Check for a malformed CSR early to avoid unnecessary RPCs
  2618  	csr, err := x509.ParseCertificateRequest(rawCSR.CSR)
  2619  	if err != nil {
  2620  		wfe.sendError(response, logEvent, probs.Malformed("Error parsing certificate request: %s", err), err)
  2621  		return
  2622  	}
  2623  
  2624  	logEvent.Identifiers = orderIdents
  2625  	logEvent.Extra["KeyType"] = web.KeyTypeToString(csr.PublicKey)
  2626  
  2627  	updatedOrder, err := wfe.ra.FinalizeOrder(ctx, &rapb.FinalizeOrderRequest{
  2628  		Csr:   rawCSR.CSR,
  2629  		Order: order,
  2630  	})
  2631  	if err != nil {
  2632  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Error finalizing order"), err)
  2633  		return
  2634  	}
  2635  	if core.IsAnyNilOrZero(updatedOrder.Id, updatedOrder.RegistrationID, updatedOrder.Identifiers, updatedOrder.Created, updatedOrder.Expires) {
  2636  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "Error validating order"), errIncompleteGRPCResponse)
  2637  		return
  2638  	}
  2639  
  2640  	// Inc CSR signature algorithm counter
  2641  	wfe.stats.csrSignatureAlgs.With(prometheus.Labels{"type": csr.SignatureAlgorithm.String()}).Inc()
  2642  
  2643  	orderURL := web.RelativeEndpoint(request,
  2644  		fmt.Sprintf("%s%d/%d", orderPath, acct.ID, updatedOrder.Id))
  2645  	response.Header().Set("Location", orderURL)
  2646  
  2647  	respObj := wfe.orderToOrderJSON(request, updatedOrder)
  2648  
  2649  	if respObj.Status == core.StatusProcessing {
  2650  		response.Header().Set(headerRetryAfter, strconv.Itoa(orderRetryAfter))
  2651  	}
  2652  
  2653  	err = wfe.writeJsonResponse(response, logEvent, http.StatusOK, respObj)
  2654  	if err != nil {
  2655  		wfe.sendError(response, logEvent, probs.ServerInternal("Unable to write finalize order response"), err)
  2656  		return
  2657  	}
  2658  }
  2659  
  2660  // parseARICertID parses the "certID", a unique identifier specified in
  2661  // draft-ietf-acme-ari-03. It takes the composite string as input returns a
  2662  // extracted and decoded certificate serial. If the decoded AKID does not match
  2663  // any known issuer or the serial number is not valid, an error is returned. For
  2664  // more details see:
  2665  // https://datatracker.ietf.org/doc/html/draft-ietf-acme-ari-03#section-4.1.
  2666  func parseARICertID(path string, issuerCertificates map[issuance.NameID]*issuance.Certificate) (string, error) {
  2667  	parts := strings.Split(path, ".")
  2668  	if len(parts) != 2 || parts[0] == "" || parts[1] == "" {
  2669  		return "", berrors.MalformedError("Invalid path")
  2670  	}
  2671  
  2672  	akid, err := base64.RawURLEncoding.DecodeString(parts[0])
  2673  	if err != nil {
  2674  		return "", berrors.MalformedError("Authority Key Identifier was not base64url-encoded or contained padding: %s", err)
  2675  	}
  2676  
  2677  	var found bool
  2678  	for _, issuer := range issuerCertificates {
  2679  		if bytes.Equal(issuer.SubjectKeyId, akid) {
  2680  			found = true
  2681  			break
  2682  		}
  2683  	}
  2684  	if !found {
  2685  		return "", berrors.NotFoundError("path contained an Authority Key Identifier that did not match a known issuer")
  2686  	}
  2687  
  2688  	serialNumber, err := base64.RawURLEncoding.DecodeString(parts[1])
  2689  	if err != nil {
  2690  		return "", berrors.NotFoundError("serial number was not base64url-encoded or contained padding: %s", err)
  2691  	}
  2692  
  2693  	return core.SerialToString(new(big.Int).SetBytes(serialNumber)), nil
  2694  }
  2695  
  2696  // RenewalInfo is used to get information about the suggested renewal window
  2697  // for the given certificate. It only accepts unauthenticated GET requests.
  2698  func (wfe *WebFrontEndImpl) RenewalInfo(ctx context.Context, logEvent *web.RequestEvent, response http.ResponseWriter, request *http.Request) {
  2699  	if !features.Get().ServeRenewalInfo {
  2700  		wfe.sendError(response, logEvent, probs.NotFound("Feature not enabled"), nil)
  2701  		return
  2702  	}
  2703  
  2704  	if len(request.URL.Path) == 0 {
  2705  		wfe.sendError(response, logEvent, probs.NotFound("Must specify a request path"), nil)
  2706  		return
  2707  	}
  2708  
  2709  	decodedSerial, err := parseARICertID(request.URL.Path, wfe.issuerCertificates)
  2710  	if err != nil {
  2711  		wfe.sendError(response, logEvent, web.ProblemDetailsForError(err, "While parsing ARI CertID an error occurred"), err)
  2712  		return
  2713  	}
  2714  
  2715  	// We can do all of our processing based just on the serial, because Boulder
  2716  	// does not re-use the same serial across multiple issuers.
  2717  	logEvent.Extra["RequestedSerial"] = decodedSerial
  2718  
  2719  	renewalInfo, err := wfe.determineARIWindow(ctx, decodedSerial)
  2720  	if err != nil {
  2721  		if errors.Is(err, berrors.NotFound) {
  2722  			wfe.sendError(response, logEvent, probs.NotFound("Requested certificate was not found"), nil)
  2723  			return
  2724  		}
  2725  		wfe.sendError(response, logEvent, probs.ServerInternal("Error determining renewal window"), err)
  2726  		return
  2727  	}
  2728  
  2729  	response.Header().Set(headerRetryAfter, fmt.Sprintf("%d", int(6*time.Hour/time.Second)))
  2730  	err = wfe.writeJsonResponse(response, logEvent, http.StatusOK, renewalInfo)
  2731  	if err != nil {
  2732  		wfe.sendError(response, logEvent, probs.ServerInternal("Error marshalling renewalInfo"), err)
  2733  		return
  2734  	}
  2735  }
  2736  
  2737  func urlForAuthz(authz core.Authorization, request *http.Request) string {
  2738  	return web.RelativeEndpoint(request, fmt.Sprintf("%s%d/%s", authzPath, authz.RegistrationID, authz.ID))
  2739  }