github.com/letsencrypt/boulder@v0.20251208.0/va/http.go (about) 1 package va 2 3 import ( 4 "context" 5 "crypto/tls" 6 "errors" 7 "fmt" 8 "io" 9 "net" 10 "net/http" 11 "net/netip" 12 "net/url" 13 "strconv" 14 "strings" 15 "time" 16 "unicode" 17 18 "github.com/letsencrypt/boulder/bdns" 19 "github.com/letsencrypt/boulder/core" 20 berrors "github.com/letsencrypt/boulder/errors" 21 "github.com/letsencrypt/boulder/iana" 22 "github.com/letsencrypt/boulder/identifier" 23 ) 24 25 const ( 26 // maxRedirect is the maximum number of redirects the VA will follow 27 // processing an HTTP-01 challenge. 28 maxRedirect = 10 29 // maxResponseSize holds the maximum number of bytes that will be read from an 30 // HTTP-01 challenge response. The expected payload should be ~87 bytes. Since 31 // it may be padded by whitespace which we previously allowed accept up to 128 32 // bytes before rejecting a response (32 byte b64 encoded token + . + 32 byte 33 // b64 encoded key fingerprint). 34 maxResponseSize = 128 35 // maxPathSize is the maximum number of bytes we will accept in the path of a 36 // redirect URL. 37 maxPathSize = 2000 38 ) 39 40 // preresolvedDialer is a struct type that provides a DialContext function which 41 // will connect to the provided IP and port instead of letting DNS resolve 42 // The hostname of the preresolvedDialer is used to ensure the dial only completes 43 // using the pre-resolved IP/port when used for the correct host. 44 type preresolvedDialer struct { 45 ip netip.Addr 46 port int 47 hostname string 48 timeout time.Duration 49 } 50 51 // a dialerMismatchError is produced when a preresolvedDialer is used to dial 52 // a host other than the dialer's specified hostname. 53 type dialerMismatchError struct { 54 // The original dialer information 55 dialerHost string 56 dialerIP string 57 dialerPort int 58 // The host that the dialer was incorrectly used with 59 host string 60 } 61 62 func (e *dialerMismatchError) Error() string { 63 return fmt.Sprintf( 64 "preresolvedDialer mismatch: dialer is for %q (ip: %q port: %d) not %q", 65 e.dialerHost, e.dialerIP, e.dialerPort, e.host) 66 } 67 68 // DialContext for a preresolvedDialer shaves 10ms off of the context it was 69 // given before calling the default transport DialContext using the pre-resolved 70 // IP and port as the host. If the original host being dialed by DialContext 71 // does not match the expected hostname in the preresolvedDialer an error will 72 // be returned instead. This helps prevents a bug that might use 73 // a preresolvedDialer for the wrong host. 74 // 75 // Shaving the context helps us be able to differentiate between timeouts during 76 // connect and timeouts after connect. 77 // 78 // Using preresolved information for the host argument given to the real 79 // transport dial lets us have fine grained control over IP address resolution for 80 // domain names. 81 func (d *preresolvedDialer) DialContext( 82 ctx context.Context, 83 network, 84 origAddr string) (net.Conn, error) { 85 deadline, ok := ctx.Deadline() 86 if !ok { 87 // Shouldn't happen: All requests should have a deadline by this point. 88 deadline = time.Now().Add(100 * time.Second) 89 } else { 90 // Set the context deadline slightly shorter than the HTTP deadline, so we 91 // get a useful error rather than a generic "deadline exceeded" error. This 92 // lets us give a more specific error to the subscriber. 93 deadline = deadline.Add(-10 * time.Millisecond) 94 } 95 ctx, cancel := context.WithDeadline(ctx, deadline) 96 defer cancel() 97 98 // NOTE(@cpu): I don't capture and check the origPort here because using 99 // `net.SplitHostPort` and also supporting the va's custom httpPort and 100 // httpsPort is cumbersome. The initial origAddr may be "example.com:80" 101 // if the URL used for the dial input was "http://example.com" without an 102 // explicit port. Checking for equality here will fail unless we add 103 // special case logic for converting 80/443 -> httpPort/httpsPort when 104 // configured. This seems more likely to cause bugs than catch them so I'm 105 // ignoring this for now. In the future if we remove the httpPort/httpsPort 106 // (we should!) we can also easily enforce that the preresolved dialer port 107 // matches expected here. 108 origHost, _, err := net.SplitHostPort(origAddr) 109 if err != nil { 110 return nil, err 111 } 112 // If the hostname we're dialing isn't equal to the hostname the dialer was 113 // constructed for then a bug has occurred where we've mismatched the 114 // preresolved dialer. 115 if origHost != d.hostname { 116 return nil, &dialerMismatchError{ 117 dialerHost: d.hostname, 118 dialerIP: d.ip.String(), 119 dialerPort: d.port, 120 host: origHost, 121 } 122 } 123 124 // Make a new dial address using the pre-resolved IP and port. 125 targetAddr := net.JoinHostPort(d.ip.String(), strconv.Itoa(d.port)) 126 127 // Create a throw-away dialer using default values and the dialer timeout 128 // (populated from the VA singleDialTimeout). 129 throwAwayDialer := &net.Dialer{ 130 Timeout: d.timeout, 131 // Default KeepAlive - see Golang src/net/http/transport.go DefaultTransport 132 KeepAlive: 30 * time.Second, 133 } 134 return throwAwayDialer.DialContext(ctx, network, targetAddr) 135 } 136 137 // a dialerFunc meets the function signature requirements of 138 // a http.Transport.DialContext handler. 139 type dialerFunc func(ctx context.Context, network, addr string) (net.Conn, error) 140 141 // httpTransport constructs a HTTP Transport with settings appropriate for 142 // HTTP-01 validation. The provided dialerFunc is used as the Transport's 143 // DialContext handler. 144 func httpTransport(df dialerFunc) *http.Transport { 145 return &http.Transport{ 146 DialContext: df, 147 // We are talking to a client that does not yet have a certificate, 148 // so we accept a temporary, invalid one. 149 TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, 150 // We don't expect to make multiple requests to a client, so close 151 // connection immediately. 152 DisableKeepAlives: true, 153 // We don't want idle connections, but 0 means "unlimited," so we pick 1. 154 MaxIdleConns: 1, 155 IdleConnTimeout: time.Second, 156 TLSHandshakeTimeout: 10 * time.Second, 157 } 158 } 159 160 // httpValidationTarget bundles all of the information needed to make an HTTP-01 161 // validation request against a target. 162 type httpValidationTarget struct { 163 // the host being validated 164 host string 165 // the port for the validation request 166 port int 167 // the path for the validation request 168 path string 169 // query data for validation request (potentially populated when 170 // following redirects) 171 query string 172 // all of the IP addresses available for the host 173 available []netip.Addr 174 // the IP addresses that were tried for validation previously that were cycled 175 // out of cur by calls to nextIP() 176 tried []netip.Addr 177 // the IP addresses that will be drawn from by calls to nextIP() to set curIP 178 next []netip.Addr 179 // the current IP address being used for validation (if any) 180 cur netip.Addr 181 // the DNS resolver(s) that will attempt to fulfill the validation request 182 resolvers bdns.ResolverAddrs 183 } 184 185 // nextIP changes the cur IP by removing the first entry from the next slice and 186 // setting it to cur. If cur was previously set the value will be added to the 187 // tried slice to keep track of IPs that were previously used. If nextIP() is 188 // called but vt.next is empty an error is returned. 189 func (vt *httpValidationTarget) nextIP() error { 190 if len(vt.next) == 0 { 191 return fmt.Errorf( 192 "host %q has no IP addresses remaining to use", 193 vt.host) 194 } 195 vt.tried = append(vt.tried, vt.cur) 196 vt.cur = vt.next[0] 197 vt.next = vt.next[1:] 198 return nil 199 } 200 201 // newHTTPValidationTarget creates a httpValidationTarget for the given host, 202 // port, and path. This involves querying DNS for the IP addresses for the host. 203 // An error is returned if there are no usable IP addresses or if the DNS 204 // lookups fail. 205 func (va *ValidationAuthorityImpl) newHTTPValidationTarget( 206 ctx context.Context, 207 ident identifier.ACMEIdentifier, 208 port int, 209 path string, 210 query string) (*httpValidationTarget, error) { 211 var addrs []netip.Addr 212 var resolvers bdns.ResolverAddrs 213 switch ident.Type { 214 case identifier.TypeDNS: 215 // Resolve IP addresses for the identifier 216 dnsAddrs, dnsResolvers, err := va.getAddrs(ctx, ident.Value) 217 if err != nil { 218 return nil, err 219 } 220 addrs, resolvers = dnsAddrs, dnsResolvers 221 case identifier.TypeIP: 222 netIP, err := netip.ParseAddr(ident.Value) 223 if err != nil { 224 return nil, fmt.Errorf("can't parse IP address %q: %s", ident.Value, err) 225 } 226 addrs = []netip.Addr{netIP} 227 default: 228 return nil, fmt.Errorf("unknown identifier type: %s", ident.Type) 229 } 230 231 target := &httpValidationTarget{ 232 host: ident.Value, 233 port: port, 234 path: path, 235 query: query, 236 available: addrs, 237 resolvers: resolvers, 238 } 239 240 // Separate the addresses into the available v4 and v6 addresses 241 v4Addrs, v6Addrs := availableAddresses(addrs) 242 hasV6Addrs := len(v6Addrs) > 0 243 hasV4Addrs := len(v4Addrs) > 0 244 245 if !hasV6Addrs && !hasV4Addrs { 246 // If there are no v6 addrs and no v4addrs there was a bug with getAddrs or 247 // availableAddresses and we need to return an error. 248 return nil, fmt.Errorf("host %q has no IPv4 or IPv6 addresses", ident.Value) 249 } else if !hasV6Addrs && hasV4Addrs { 250 // If there are no v6 addrs and there are v4 addrs then use the first v4 251 // address. There's no fallback address. 252 target.next = []netip.Addr{v4Addrs[0]} 253 } else if hasV6Addrs && hasV4Addrs { 254 // If there are both v6 addrs and v4 addrs then use the first v6 address and 255 // fallback with the first v4 address. 256 target.next = []netip.Addr{v6Addrs[0], v4Addrs[0]} 257 } else if hasV6Addrs && !hasV4Addrs { 258 // If there are just v6 addrs then use the first v6 address. There's no 259 // fallback address. 260 target.next = []netip.Addr{v6Addrs[0]} 261 } 262 263 // Advance the target using nextIP to populate the cur IP before returning 264 _ = target.nextIP() 265 return target, nil 266 } 267 268 // extractRequestTarget extracts the host and port specified in the provided 269 // HTTP redirect request. If the request's URL's protocol schema is not HTTP or 270 // HTTPS an error is returned. If an explicit port is specified in the request's 271 // URL and it isn't the VA's HTTP or HTTPS port, an error is returned. 272 func (va *ValidationAuthorityImpl) extractRequestTarget(req *http.Request) (identifier.ACMEIdentifier, int, error) { 273 // A nil request is certainly not a valid redirect and has no port to extract. 274 if req == nil { 275 return identifier.ACMEIdentifier{}, 0, fmt.Errorf("redirect HTTP request was nil") 276 } 277 278 reqScheme := req.URL.Scheme 279 280 // The redirect request must use HTTP or HTTPs protocol schemes regardless of the port.. 281 if reqScheme != "http" && reqScheme != "https" { 282 return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError( 283 "Invalid protocol scheme in redirect target. "+ 284 `Only "http" and "https" protocol schemes are supported, not %q`, reqScheme) 285 } 286 287 // Try to parse an explicit port number from the request URL host. If there 288 // is one, we need to make sure its a valid port. If there isn't one we need 289 // to pick the port based on the reqScheme default port. 290 reqHost := req.URL.Hostname() 291 var reqPort int 292 // URL.Port() will return "" for an invalid port, not just an empty port. To 293 // reject invalid ports, we rely on the calling function having used 294 // URL.Parse(), which does enforce validity. 295 if req.URL.Port() != "" { 296 parsedPort, err := strconv.Atoi(req.URL.Port()) 297 if err != nil { 298 return identifier.ACMEIdentifier{}, 0, err 299 } 300 301 // The explicit port must match the VA's configured HTTP or HTTPS port. 302 if parsedPort != va.httpPort && parsedPort != va.httpsPort { 303 return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError( 304 "Invalid port in redirect target. Only ports %d and %d are supported, not %d", 305 va.httpPort, va.httpsPort, parsedPort) 306 } 307 308 reqPort = parsedPort 309 } else if reqScheme == "http" { 310 reqPort = va.httpPort 311 } else if reqScheme == "https" { 312 reqPort = va.httpsPort 313 } else { 314 // This shouldn't happen but defensively return an internal server error in 315 // case it does. 316 return identifier.ACMEIdentifier{}, 0, fmt.Errorf("unable to determine redirect HTTP request port") 317 } 318 319 if reqHost == "" { 320 return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid empty host in redirect target") 321 } 322 323 // Often folks will misconfigure their webserver to send an HTTP redirect 324 // missing a `/' between the FQDN and the path. E.g. in Apache using: 325 // Redirect / https://bad-redirect.org 326 // Instead of 327 // Redirect / https://bad-redirect.org/ 328 // Will produce an invalid HTTP-01 redirect target like: 329 // https://bad-redirect.org.well-known/acme-challenge/xxxx 330 // This happens frequently enough we want to return a distinct error message 331 // for this case by detecting the reqHost ending in ".well-known". 332 if strings.HasSuffix(reqHost, ".well-known") { 333 return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError( 334 "Invalid host in redirect target %q. Check webserver config for missing '/' in redirect target.", 335 reqHost, 336 ) 337 } 338 339 reqIP, err := netip.ParseAddr(reqHost) 340 if err == nil { 341 // Reject IPv6 addresses with a scope zone (RFCs 4007 & 6874) 342 if reqIP.Zone() != "" { 343 return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid host in redirect target: contains scope zone") 344 } 345 err := va.isReservedIPFunc(reqIP) 346 if err != nil { 347 return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid host in redirect target: %s", err) 348 } 349 return identifier.NewIP(reqIP), reqPort, nil 350 } 351 352 if _, err := iana.ExtractSuffix(reqHost); err != nil { 353 return identifier.ACMEIdentifier{}, 0, berrors.ConnectionFailureError("Invalid host in redirect target, must end in IANA registered TLD") 354 } 355 356 return identifier.NewDNS(reqHost), reqPort, nil 357 } 358 359 // setupHTTPValidation sets up a preresolvedDialer and a validation record for 360 // the given request URL and httpValidationTarget. If the req URL is empty, or 361 // the validation target is nil or has no available IP addresses, an error will 362 // be returned. 363 func (va *ValidationAuthorityImpl) setupHTTPValidation( 364 reqURL string, 365 target *httpValidationTarget) (*preresolvedDialer, core.ValidationRecord, error) { 366 if reqURL == "" { 367 return nil, 368 core.ValidationRecord{}, 369 fmt.Errorf("reqURL can not be nil") 370 } 371 if target == nil { 372 // This is the only case where returning an empty validation record makes 373 // sense - we can't construct a better one, something has gone quite wrong. 374 return nil, 375 core.ValidationRecord{}, 376 fmt.Errorf("httpValidationTarget can not be nil") 377 } 378 379 // Construct a base validation record with the validation target's 380 // information. 381 record := core.ValidationRecord{ 382 Hostname: target.host, 383 Port: strconv.Itoa(target.port), 384 AddressesResolved: target.available, 385 URL: reqURL, 386 ResolverAddrs: target.resolvers, 387 } 388 389 // Get the target IP to build a preresolved dialer with 390 targetIP := target.cur 391 if (targetIP == netip.Addr{}) { 392 return nil, 393 record, 394 fmt.Errorf( 395 "host %q has no IP addresses remaining to use", 396 target.host) 397 } 398 399 // This is a backstop check to avoid connecting to reserved IP addresses. 400 // They should have been caught and excluded by `bdns.LookupHost`. 401 err := va.isReservedIPFunc(targetIP) 402 if err != nil { 403 return nil, record, err 404 } 405 406 record.AddressUsed = targetIP 407 408 dialer := &preresolvedDialer{ 409 ip: targetIP, 410 port: target.port, 411 hostname: target.host, 412 timeout: va.singleDialTimeout, 413 } 414 return dialer, record, nil 415 } 416 417 // fallbackErr returns true only for net.OpError instances where the op is equal 418 // to "dial", or url.Error instances wrapping such an error. fallbackErr returns 419 // false for all other errors. By policy, only dial errors (not read or write 420 // errors) are eligible for fallback from an IPv6 to an IPv4 address. 421 func fallbackErr(err error) bool { 422 // Err shouldn't ever be nil if we're considering it for fallback 423 if err == nil { 424 return false 425 } 426 // Net OpErrors are fallback errs only if the operation was a "dial" 427 // All other errs are not fallback errs 428 var netOpError *net.OpError 429 return errors.As(err, &netOpError) && netOpError.Op == "dial" 430 } 431 432 // processHTTPValidation performs an HTTP validation for the given host, port 433 // and path. If successful the body of the HTTP response is returned along with 434 // the validation records created during the validation. If not successful 435 // a non-nil error and potentially some ValidationRecords are returned. 436 func (va *ValidationAuthorityImpl) processHTTPValidation( 437 ctx context.Context, 438 ident identifier.ACMEIdentifier, 439 path string) ([]byte, []core.ValidationRecord, error) { 440 // Create a target for the host, port and path with no query parameters 441 target, err := va.newHTTPValidationTarget(ctx, ident, va.httpPort, path, "") 442 if err != nil { 443 return nil, nil, err 444 } 445 446 // When constructing a URL, bare IPv6 addresses must be enclosed in square 447 // brackets. Otherwise, a colon may be interpreted as a port separator. 448 host := ident.Value 449 if ident.Type == identifier.TypeIP { 450 netipHost, err := netip.ParseAddr(host) 451 if err != nil { 452 return nil, nil, fmt.Errorf("couldn't parse IP address from identifier") 453 } 454 if !netipHost.Is4() { 455 host = "[" + host + "]" 456 } 457 } 458 459 // Create an initial GET Request 460 initialURL := url.URL{ 461 Scheme: "http", 462 Host: host, 463 Path: path, 464 } 465 initialReq, err := http.NewRequest("GET", initialURL.String(), nil) 466 if err != nil { 467 return nil, nil, newIPError(target.cur, err) 468 } 469 470 // Add a context to the request. Shave some time from the 471 // overall context deadline so that we are not racing with gRPC when the 472 // HTTP server is timing out. This avoids returning ServerInternal 473 // errors when we should be returning Connection errors. This may fix a flaky 474 // integration test: https://github.com/letsencrypt/boulder/issues/4087 475 // Note: The gRPC interceptor in grpc/interceptors.go already shaves some time 476 // off RPCs, but this takes off additional time because HTTP-related timeouts 477 // are so common (and because it might fix a flaky build). 478 deadline, ok := ctx.Deadline() 479 if !ok { 480 return nil, nil, fmt.Errorf("processHTTPValidation had no deadline") 481 } else { 482 deadline = deadline.Add(-200 * time.Millisecond) 483 } 484 ctx, cancel := context.WithDeadline(ctx, deadline) 485 defer cancel() 486 initialReq = initialReq.WithContext(ctx) 487 if va.userAgent != "" { 488 initialReq.Header.Set("User-Agent", va.userAgent) 489 } 490 // Some of our users use mod_security. Mod_security sees a lack of Accept 491 // headers as bot behavior and rejects requests. While this is a bug in 492 // mod_security's rules (given that the HTTP specs disagree with that 493 // requirement), we add the Accept header now in order to fix our 494 // mod_security users' mysterious breakages. See 495 // <https://github.com/SpiderLabs/owasp-modsecurity-crs/issues/265> and 496 // <https://github.com/letsencrypt/boulder/issues/1019>. This was done 497 // because it's a one-line fix with no downside. We're not likely to want to 498 // do many more things to satisfy misunderstandings around HTTP. 499 initialReq.Header.Set("Accept", "*/*") 500 501 // Set up the initial validation request and a base validation record 502 dialer, baseRecord, err := va.setupHTTPValidation(initialReq.URL.String(), target) 503 if err != nil { 504 return nil, []core.ValidationRecord{}, newIPError(target.cur, err) 505 } 506 507 // Build a transport for this validation that will use the preresolvedDialer's 508 // DialContext function 509 transport := httpTransport(dialer.DialContext) 510 511 va.log.AuditInfof("Attempting to validate HTTP-01 for %q with GET to %q", 512 initialReq.Host, initialReq.URL.String()) 513 514 // Create a closure around records & numRedirects we can use with a HTTP 515 // client to process redirects per our own policy (e.g. resolving IP 516 // addresses explicitly, not following redirects to ports != [80,443], etc) 517 records := []core.ValidationRecord{baseRecord} 518 numRedirects := 0 519 processRedirect := func(req *http.Request, via []*http.Request) error { 520 va.log.Debugf("processing a HTTP redirect from the server to %q", req.URL.String()) 521 // Only process up to maxRedirect redirects 522 if numRedirects > maxRedirect { 523 return berrors.ConnectionFailureError("Too many redirects") 524 } 525 numRedirects++ 526 va.metrics.http01Redirects.Inc() 527 528 if req.Response.TLS != nil && req.Response.TLS.Version < tls.VersionTLS12 { 529 return berrors.ConnectionFailureError( 530 "validation attempt was redirected to an HTTPS server that doesn't " + 531 "support TLSv1.2 or better. See " + 532 "https://community.letsencrypt.org/t/rejecting-sha-1-csrs-and-validation-using-tls-1-0-1-1-urls/175144") 533 } 534 535 // If the response contains an HTTP 303 or any other forbidden redirect, 536 // do not follow it. The four allowed redirect status codes are defined 537 // explicitly in BRs Section 3.2.2.4.19. Although the go stdlib currently 538 // limits redirects to a set of status codes with only one additional 539 // entry (303), we capture the full list of allowed codes here in case the 540 // go stdlib expands the set of redirects it follows in the future. 541 acceptableRedirects := map[int]struct{}{ 542 301: {}, 302: {}, 307: {}, 308: {}, 543 } 544 if _, present := acceptableRedirects[req.Response.StatusCode]; !present { 545 return berrors.ConnectionFailureError("received disallowed redirect status code") 546 } 547 548 // Lowercase the redirect host immediately, as the dialer and redirect 549 // validation expect it to have been lowercased already. 550 req.URL.Host = strings.ToLower(req.URL.Host) 551 552 // Extract the redirect target's host and port. This will return an error if 553 // the redirect request scheme, host or port is not acceptable. 554 redirHost, redirPort, err := va.extractRequestTarget(req) 555 if err != nil { 556 return err 557 } 558 559 redirPath := req.URL.Path 560 if len(redirPath) > maxPathSize { 561 return berrors.ConnectionFailureError("Redirect target too long") 562 } 563 564 // If the redirect URL has query parameters we need to preserve 565 // those in the redirect path 566 redirQuery := "" 567 if req.URL.RawQuery != "" { 568 redirQuery = req.URL.RawQuery 569 } 570 571 // Check for a redirect loop. If any URL is found twice before the 572 // redirect limit, return error. 573 for _, record := range records { 574 if req.URL.String() == record.URL { 575 return berrors.ConnectionFailureError("Redirect loop detected") 576 } 577 } 578 579 // Create a validation target for the redirect host. This will resolve IP 580 // addresses for the host explicitly. 581 redirTarget, err := va.newHTTPValidationTarget(ctx, redirHost, redirPort, redirPath, redirQuery) 582 if err != nil { 583 return err 584 } 585 586 // Setup validation for the target. This will produce a preresolved dialer we can 587 // assign to the client transport in order to connect to the redirect target using 588 // the IP address we selected. 589 redirDialer, redirRecord, err := va.setupHTTPValidation(req.URL.String(), redirTarget) 590 records = append(records, redirRecord) 591 if err != nil { 592 return err 593 } 594 595 va.log.Debugf("following redirect to host %q url %q", req.Host, req.URL.String()) 596 // Replace the transport's DialContext with the new preresolvedDialer for 597 // the redirect. 598 transport.DialContext = redirDialer.DialContext 599 return nil 600 } 601 602 // Create a new HTTP client configured to use the customized transport and 603 // to check HTTP redirects encountered with processRedirect 604 client := http.Client{ 605 Transport: transport, 606 CheckRedirect: processRedirect, 607 } 608 609 // Make the initial validation request. This may result in redirects being 610 // followed. 611 httpResponse, err := client.Do(initialReq) 612 // If there was an error and its a kind of error we consider a fallback error, 613 // then try to fallback. 614 if err != nil && fallbackErr(err) { 615 // Try to advance to another IP. If there was an error advancing we don't 616 // have a fallback address to use and must return the original error. 617 advanceTargetIPErr := target.nextIP() 618 if advanceTargetIPErr != nil { 619 return nil, records, newIPError(records[len(records)-1].AddressUsed, err) 620 } 621 622 // setup another validation to retry the target with the new IP and append 623 // the retry record. 624 retryDialer, retryRecord, err := va.setupHTTPValidation(initialReq.URL.String(), target) 625 if err != nil { 626 return nil, records, newIPError(records[len(records)-1].AddressUsed, err) 627 } 628 629 records = append(records, retryRecord) 630 va.metrics.http01Fallbacks.Inc() 631 // Replace the transport's dialer with the preresolvedDialer for the retry 632 // host. 633 transport.DialContext = retryDialer.DialContext 634 635 // Perform the retry 636 httpResponse, err = client.Do(initialReq) 637 // If the retry still failed there isn't anything more to do, return the 638 // error immediately. 639 if err != nil { 640 return nil, records, newIPError(records[len(records)-1].AddressUsed, err) 641 } 642 } else if err != nil { 643 // if the error was not a fallbackErr then return immediately. 644 return nil, records, newIPError(records[len(records)-1].AddressUsed, err) 645 } 646 647 if httpResponse.StatusCode != 200 { 648 return nil, records, newIPError(records[len(records)-1].AddressUsed, berrors.UnauthorizedError("Invalid response from %s: %d", 649 records[len(records)-1].URL, httpResponse.StatusCode)) 650 } 651 652 // At this point we've made a successful request (be it from a retry or 653 // otherwise) and can read and process the response body. 654 body, err := io.ReadAll(&io.LimitedReader{R: httpResponse.Body, N: maxResponseSize}) 655 closeErr := httpResponse.Body.Close() 656 if err == nil { 657 err = closeErr 658 } 659 if err != nil { 660 return nil, records, newIPError(records[len(records)-1].AddressUsed, berrors.UnauthorizedError("Error reading HTTP response body: %v", err)) 661 } 662 663 // io.LimitedReader will silently truncate a Reader so if the 664 // resulting payload is the same size as maxResponseSize fail 665 if len(body) >= maxResponseSize { 666 return nil, records, newIPError(records[len(records)-1].AddressUsed, berrors.UnauthorizedError("Invalid response from %s: %q", 667 records[len(records)-1].URL, body)) 668 } 669 670 return body, records, nil 671 } 672 673 func (va *ValidationAuthorityImpl) validateHTTP01(ctx context.Context, ident identifier.ACMEIdentifier, token string, keyAuthorization string) ([]core.ValidationRecord, error) { 674 if ident.Type != identifier.TypeDNS && ident.Type != identifier.TypeIP { 675 va.log.Info(fmt.Sprintf("Identifier type for HTTP-01 challenge was not DNS or IP: %s", ident)) 676 return nil, berrors.MalformedError("Identifier type for HTTP-01 challenge was not DNS or IP") 677 } 678 679 // Perform the fetch 680 path := fmt.Sprintf(".well-known/acme-challenge/%s", token) 681 body, validationRecords, err := va.processHTTPValidation(ctx, ident, "/"+path) 682 if err != nil { 683 return validationRecords, err 684 } 685 payload := strings.TrimRightFunc(string(body), unicode.IsSpace) 686 687 if payload != keyAuthorization { 688 problem := berrors.UnauthorizedError("The key authorization file from the server did not match this challenge. Expected %q (got %q)", 689 keyAuthorization, payload) 690 va.log.Infof("%s for %s", problem, ident) 691 return validationRecords, problem 692 } 693 694 return validationRecords, nil 695 }