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, ®) 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 }