github.com/letsencrypt/boulder@v0.20251208.0/va/http_test.go (about)

     1  package va
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"errors"
     7  	"fmt"
     8  	mrand "math/rand/v2"
     9  	"net"
    10  	"net/http"
    11  	"net/http/httptest"
    12  	"net/netip"
    13  	"net/url"
    14  	"strconv"
    15  	"strings"
    16  	"time"
    17  	"unicode/utf8"
    18  
    19  	"github.com/letsencrypt/boulder/bdns"
    20  	"github.com/letsencrypt/boulder/core"
    21  	berrors "github.com/letsencrypt/boulder/errors"
    22  	"github.com/letsencrypt/boulder/identifier"
    23  	"github.com/letsencrypt/boulder/must"
    24  	"github.com/letsencrypt/boulder/probs"
    25  	"github.com/letsencrypt/boulder/test"
    26  
    27  	"testing"
    28  )
    29  
    30  // TestDialerMismatchError tests that using a preresolvedDialer for one host for
    31  // a dial to another host produces the expected dialerMismatchError.
    32  func TestDialerMismatchError(t *testing.T) {
    33  	d := preresolvedDialer{
    34  		ip:       netip.MustParseAddr("127.0.0.1"),
    35  		port:     1337,
    36  		hostname: "letsencrypt.org",
    37  	}
    38  
    39  	expectedErr := dialerMismatchError{
    40  		dialerHost: d.hostname,
    41  		dialerIP:   d.ip.String(),
    42  		dialerPort: d.port,
    43  		host:       "lettuceencrypt.org",
    44  	}
    45  
    46  	_, err := d.DialContext(
    47  		context.Background(),
    48  		"tincan-and-string",
    49  		"lettuceencrypt.org:80")
    50  	test.AssertEquals(t, err.Error(), expectedErr.Error())
    51  }
    52  
    53  // dnsMockReturnsUnroutable is a DNSClient mock that always returns an
    54  // unroutable address for LookupHost. This is useful in testing connect
    55  // timeouts.
    56  type dnsMockReturnsUnroutable struct {
    57  	*bdns.MockClient
    58  }
    59  
    60  func (mock dnsMockReturnsUnroutable) LookupHost(_ context.Context, hostname string) ([]netip.Addr, bdns.ResolverAddrs, error) {
    61  	return []netip.Addr{netip.MustParseAddr("64.112.117.254")}, bdns.ResolverAddrs{"dnsMockReturnsUnroutable"}, nil
    62  }
    63  
    64  // TestDialerTimeout tests that the preresolvedDialer's DialContext
    65  // will timeout after the expected singleDialTimeout. This ensures timeouts at
    66  // the TCP level are handled correctly. It also ensures that we show the client
    67  // the appropriate "Timeout during connect" error message, which helps clients
    68  // distinguish between firewall problems and server problems.
    69  func TestDialerTimeout(t *testing.T) {
    70  	va, _ := setup(nil, "", nil, nil)
    71  	// Timeouts below 50ms tend to be flaky.
    72  	va.singleDialTimeout = 50 * time.Millisecond
    73  
    74  	// The context timeout needs to be larger than the singleDialTimeout
    75  	ctxTimeout := 500 * time.Millisecond
    76  	ctx, cancel := context.WithTimeout(context.Background(), ctxTimeout)
    77  	defer cancel()
    78  
    79  	va.dnsClient = dnsMockReturnsUnroutable{&bdns.MockClient{}}
    80  	// NOTE(@jsha): The only method I've found so far to trigger a connect timeout
    81  	// is to connect to an unrouteable IP address. This usually generates
    82  	// a connection timeout, but will rarely return "Network unreachable" instead.
    83  	// If we get that, just retry until we get something other than "Network unreachable".
    84  	var err error
    85  	var took time.Duration
    86  	for range 20 {
    87  		started := time.Now()
    88  		_, _, err = va.processHTTPValidation(ctx, identifier.NewDNS("unroutable.invalid"), "/.well-known/acme-challenge/whatever")
    89  		took = time.Since(started)
    90  		if err != nil && strings.Contains(err.Error(), "network is unreachable") {
    91  			continue
    92  		} else {
    93  			break
    94  		}
    95  	}
    96  	if err == nil {
    97  		t.Fatalf("Connection should've timed out")
    98  	}
    99  
   100  	// Check that the HTTP connection doesn't return too fast, and times
   101  	// out after the expected time
   102  	if took < va.singleDialTimeout {
   103  		t.Fatalf("fetch returned before %s (took: %s) with %q", va.singleDialTimeout, took, err.Error())
   104  	}
   105  	if took > 2*va.singleDialTimeout {
   106  		t.Fatalf("fetch didn't timeout after %s (took: %s)", va.singleDialTimeout, took)
   107  	}
   108  	prob := detailedError(err)
   109  	test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
   110  	test.AssertContains(t, prob.Detail, "Timeout during connect (likely firewall problem)")
   111  }
   112  
   113  func TestHTTPTransport(t *testing.T) {
   114  	dummyDialerFunc := func(_ context.Context, _, _ string) (net.Conn, error) {
   115  		return nil, nil
   116  	}
   117  	transport := httpTransport(dummyDialerFunc)
   118  	// The HTTP Transport should have a TLS config that skips verifying
   119  	// certificates.
   120  	test.AssertEquals(t, transport.TLSClientConfig.InsecureSkipVerify, true)
   121  	// Keep alives should be disabled
   122  	test.AssertEquals(t, transport.DisableKeepAlives, true)
   123  	test.AssertEquals(t, transport.MaxIdleConns, 1)
   124  	test.AssertEquals(t, transport.IdleConnTimeout.String(), "1s")
   125  	test.AssertEquals(t, transport.TLSHandshakeTimeout.String(), "10s")
   126  }
   127  
   128  func TestHTTPValidationTarget(t *testing.T) {
   129  	// NOTE(@cpu): See `bdns/mocks.go` and the mock `LookupHost` function for the
   130  	// hostnames used in this test.
   131  	testCases := []struct {
   132  		Name          string
   133  		Ident         identifier.ACMEIdentifier
   134  		ExpectedError error
   135  		ExpectedIPs   []string
   136  	}{
   137  		{
   138  			Name:          "No IPs for DNS identifier",
   139  			Ident:         identifier.NewDNS("always.invalid"),
   140  			ExpectedError: berrors.DNSError("No valid IP addresses found for always.invalid"),
   141  		},
   142  		{
   143  			Name:        "Only IPv4 addrs for DNS identifier",
   144  			Ident:       identifier.NewDNS("some.example.com"),
   145  			ExpectedIPs: []string{"127.0.0.1"},
   146  		},
   147  		{
   148  			Name:        "Only IPv6 addrs for DNS identifier",
   149  			Ident:       identifier.NewDNS("ipv6.localhost"),
   150  			ExpectedIPs: []string{"::1"},
   151  		},
   152  		{
   153  			Name:  "Both IPv6 and IPv4 addrs for DNS identifier",
   154  			Ident: identifier.NewDNS("ipv4.and.ipv6.localhost"),
   155  			// In this case we expect 1 IPv6 address first, and then 1 IPv4 address
   156  			ExpectedIPs: []string{"::1", "127.0.0.1"},
   157  		},
   158  		{
   159  			Name:        "IPv4 IP address identifier",
   160  			Ident:       identifier.NewIP(netip.MustParseAddr("127.0.0.1")),
   161  			ExpectedIPs: []string{"127.0.0.1"},
   162  		},
   163  		{
   164  			Name:        "IPv6 IP address identifier",
   165  			Ident:       identifier.NewIP(netip.MustParseAddr("::1")),
   166  			ExpectedIPs: []string{"::1"},
   167  		},
   168  	}
   169  
   170  	const (
   171  		examplePort  = 1234
   172  		examplePath  = "/.well-known/path/i/took"
   173  		exampleQuery = "my-path=was&my=own"
   174  	)
   175  
   176  	va, _ := setup(nil, "", nil, nil)
   177  	for _, tc := range testCases {
   178  		t.Run(tc.Name, func(t *testing.T) {
   179  			target, err := va.newHTTPValidationTarget(
   180  				context.Background(),
   181  				tc.Ident,
   182  				examplePort,
   183  				examplePath,
   184  				exampleQuery)
   185  			if err != nil && tc.ExpectedError == nil {
   186  				t.Fatalf("Unexpected error from NewHTTPValidationTarget: %v", err)
   187  			} else if err != nil && tc.ExpectedError != nil {
   188  				test.AssertMarshaledEquals(t, err, tc.ExpectedError)
   189  			} else if err == nil {
   190  				// The target should be populated.
   191  				test.AssertNotEquals(t, target.host, "")
   192  				test.AssertNotEquals(t, target.port, 0)
   193  				test.AssertNotEquals(t, target.path, "")
   194  				// Calling ip() on the target should give the expected IPs in the right
   195  				// order.
   196  				for i, expectedIP := range tc.ExpectedIPs {
   197  					gotIP := target.cur
   198  					if (gotIP == netip.Addr{}) {
   199  						t.Errorf("Expected IP %d to be %s got nil", i, expectedIP)
   200  					} else {
   201  						test.AssertEquals(t, gotIP.String(), expectedIP)
   202  					}
   203  					// Advance to the next IP
   204  					_ = target.nextIP()
   205  				}
   206  			}
   207  		})
   208  	}
   209  }
   210  
   211  func TestExtractRequestTarget(t *testing.T) {
   212  	mustURL := func(rawURL string) *url.URL {
   213  		return must.Do(url.Parse(rawURL))
   214  	}
   215  
   216  	testCases := []struct {
   217  		Name          string
   218  		Req           *http.Request
   219  		ExpectedError error
   220  		ExpectedIdent identifier.ACMEIdentifier
   221  		ExpectedPort  int
   222  	}{
   223  		{
   224  			Name:          "nil input req",
   225  			ExpectedError: fmt.Errorf("redirect HTTP request was nil"),
   226  		},
   227  		{
   228  			Name: "invalid protocol scheme",
   229  			Req: &http.Request{
   230  				URL: mustURL("gopher://letsencrypt.org"),
   231  			},
   232  			ExpectedError: fmt.Errorf("Invalid protocol scheme in redirect target. " +
   233  				`Only "http" and "https" protocol schemes are supported, ` +
   234  				`not "gopher"`),
   235  		},
   236  		{
   237  			Name: "invalid explicit port",
   238  			Req: &http.Request{
   239  				URL: mustURL("https://weird.port.letsencrypt.org:9999"),
   240  			},
   241  			ExpectedError: fmt.Errorf("Invalid port in redirect target. Only ports 80 " +
   242  				"and 443 are supported, not 9999"),
   243  		},
   244  		{
   245  			Name: "invalid empty host",
   246  			Req: &http.Request{
   247  				URL: mustURL("https:///who/needs/a/hostname?not=me"),
   248  			},
   249  			ExpectedError: errors.New("Invalid empty host in redirect target"),
   250  		},
   251  		{
   252  			Name: "invalid .well-known hostname",
   253  			Req: &http.Request{
   254  				URL: mustURL("https://my.webserver.is.misconfigured.well-known/acme-challenge/xxx"),
   255  			},
   256  			ExpectedError: errors.New(`Invalid host in redirect target "my.webserver.is.misconfigured.well-known". Check webserver config for missing '/' in redirect target.`),
   257  		},
   258  		{
   259  			Name: "invalid non-iana hostname",
   260  			Req: &http.Request{
   261  				URL: mustURL("https://my.tld.is.cpu/pretty/cool/right?yeah=Ithoughtsotoo"),
   262  			},
   263  			ExpectedError: errors.New("Invalid host in redirect target, must end in IANA registered TLD"),
   264  		},
   265  		{
   266  			Name: "malformed wildcard-ish IPv4 address",
   267  			Req: &http.Request{
   268  				URL: mustURL("https://10.10.10.*"),
   269  			},
   270  			ExpectedError: errors.New("Invalid host in redirect target, must end in IANA registered TLD"),
   271  		},
   272  		{
   273  			Name: "bare IPv4, implicit port",
   274  			Req: &http.Request{
   275  				URL: mustURL("http://127.0.0.1"),
   276  			},
   277  			ExpectedIdent: identifier.NewIP(netip.MustParseAddr("127.0.0.1")),
   278  			ExpectedPort:  80,
   279  		},
   280  		{
   281  			Name: "bare IPv4, explicit valid port",
   282  			Req: &http.Request{
   283  				URL: mustURL("http://127.0.0.1:80"),
   284  			},
   285  			ExpectedIdent: identifier.NewIP(netip.MustParseAddr("127.0.0.1")),
   286  			ExpectedPort:  80,
   287  		},
   288  		{
   289  			Name: "bare IPv4, explicit invalid port",
   290  			Req: &http.Request{
   291  				URL: mustURL("http://127.0.0.1:9999"),
   292  			},
   293  			ExpectedError: fmt.Errorf("Invalid port in redirect target. Only ports 80 " +
   294  				"and 443 are supported, not 9999"),
   295  		},
   296  		{
   297  			Name: "bare IPv4, HTTPS",
   298  			Req: &http.Request{
   299  				URL: mustURL("https://127.0.0.1"),
   300  			},
   301  			ExpectedIdent: identifier.NewIP(netip.MustParseAddr("127.0.0.1")),
   302  			ExpectedPort:  443,
   303  		},
   304  		{
   305  			Name: "bare IPv4, reserved IP address",
   306  			Req: &http.Request{
   307  				URL: mustURL("http://10.10.10.10"),
   308  			},
   309  			ExpectedError: fmt.Errorf("Invalid host in redirect target: " +
   310  				"IP address is in a reserved address block: [RFC1918]: Private-Use"),
   311  		},
   312  		{
   313  			Name: "bare IPv6, implicit port",
   314  			Req: &http.Request{
   315  				URL: mustURL("http://[::1]"),
   316  			},
   317  			ExpectedIdent: identifier.NewIP(netip.MustParseAddr("::1")),
   318  			ExpectedPort:  80,
   319  		},
   320  		{
   321  			Name: "bare IPv6, explicit valid port",
   322  			Req: &http.Request{
   323  				URL: mustURL("http://[::1]:80"),
   324  			},
   325  			ExpectedIdent: identifier.NewIP(netip.MustParseAddr("::1")),
   326  			ExpectedPort:  80,
   327  		},
   328  		{
   329  			Name: "bare IPv6, explicit invalid port",
   330  			Req: &http.Request{
   331  				URL: mustURL("http://[::1]:9999"),
   332  			},
   333  			ExpectedError: fmt.Errorf("Invalid port in redirect target. Only ports 80 " +
   334  				"and 443 are supported, not 9999"),
   335  		},
   336  		{
   337  			Name: "bare IPv6, HTTPS",
   338  			Req: &http.Request{
   339  				URL: mustURL("https://[::1]"),
   340  			},
   341  			ExpectedIdent: identifier.NewIP(netip.MustParseAddr("::1")),
   342  			ExpectedPort:  443,
   343  		},
   344  		{
   345  			Name: "bare IPv6, reserved IP address",
   346  			Req: &http.Request{
   347  				URL: mustURL("http://[3fff:aaa:aaaa:aaaa:abad:0ff1:cec0:ffee]"),
   348  			},
   349  			ExpectedError: fmt.Errorf("Invalid host in redirect target: " +
   350  				"IP address is in a reserved address block: [RFC9637]: Documentation"),
   351  		},
   352  		{
   353  			Name: "bare IPv6, scope zone",
   354  			Req: &http.Request{
   355  				URL: mustURL("http://[::1%25lo]"),
   356  			},
   357  			ExpectedError: fmt.Errorf("Invalid host in redirect target: " +
   358  				"contains scope zone"),
   359  		},
   360  		{
   361  			Name: "valid HTTP redirect, explicit port",
   362  			Req: &http.Request{
   363  				URL: mustURL("http://cpu.letsencrypt.org:80"),
   364  			},
   365  			ExpectedIdent: identifier.NewDNS("cpu.letsencrypt.org"),
   366  			ExpectedPort:  80,
   367  		},
   368  		{
   369  			Name: "valid HTTP redirect, implicit port",
   370  			Req: &http.Request{
   371  				URL: mustURL("http://cpu.letsencrypt.org"),
   372  			},
   373  			ExpectedIdent: identifier.NewDNS("cpu.letsencrypt.org"),
   374  			ExpectedPort:  80,
   375  		},
   376  		{
   377  			Name: "valid HTTPS redirect, explicit port",
   378  			Req: &http.Request{
   379  				URL: mustURL("https://cpu.letsencrypt.org:443/hello.world"),
   380  			},
   381  			ExpectedIdent: identifier.NewDNS("cpu.letsencrypt.org"),
   382  			ExpectedPort:  443,
   383  		},
   384  		{
   385  			Name: "valid HTTPS redirect, implicit port",
   386  			Req: &http.Request{
   387  				URL: mustURL("https://cpu.letsencrypt.org/hello.world"),
   388  			},
   389  			ExpectedIdent: identifier.NewDNS("cpu.letsencrypt.org"),
   390  			ExpectedPort:  443,
   391  		},
   392  	}
   393  
   394  	va, _ := setup(nil, "", nil, nil)
   395  	for _, tc := range testCases {
   396  		t.Run(tc.Name, func(t *testing.T) {
   397  			host, port, err := va.extractRequestTarget(tc.Req)
   398  			if err != nil && tc.ExpectedError == nil {
   399  				t.Errorf("Expected nil err got %v", err)
   400  			} else if err != nil && tc.ExpectedError != nil {
   401  				test.AssertEquals(t, err.Error(), tc.ExpectedError.Error())
   402  			} else if err == nil && tc.ExpectedError != nil {
   403  				t.Errorf("Expected err %v, got nil", tc.ExpectedError)
   404  			} else {
   405  				test.AssertEquals(t, host, tc.ExpectedIdent)
   406  				test.AssertEquals(t, port, tc.ExpectedPort)
   407  			}
   408  		})
   409  	}
   410  }
   411  
   412  func TestSetupHTTPValidation(t *testing.T) {
   413  	va, _ := setup(nil, "", nil, nil)
   414  
   415  	mustTarget := func(t *testing.T, host string, port int, path string) *httpValidationTarget {
   416  		target, err := va.newHTTPValidationTarget(
   417  			context.Background(),
   418  			identifier.NewDNS(host),
   419  			port,
   420  			path,
   421  			"")
   422  		if err != nil {
   423  			t.Fatalf("Failed to construct httpValidationTarget for %q", host)
   424  			return nil
   425  		}
   426  		return target
   427  	}
   428  
   429  	httpInputURL := "http://ipv4.and.ipv6.localhost/yellow/brick/road"
   430  	httpsInputURL := "https://ipv4.and.ipv6.localhost/yellow/brick/road"
   431  
   432  	testCases := []struct {
   433  		Name           string
   434  		InputURL       string
   435  		InputTarget    *httpValidationTarget
   436  		ExpectedRecord core.ValidationRecord
   437  		ExpectedDialer *preresolvedDialer
   438  		ExpectedError  error
   439  	}{
   440  		{
   441  			Name:          "nil target",
   442  			InputURL:      httpInputURL,
   443  			ExpectedError: fmt.Errorf("httpValidationTarget can not be nil"),
   444  		},
   445  		{
   446  			Name:          "empty input URL",
   447  			InputTarget:   &httpValidationTarget{},
   448  			ExpectedError: fmt.Errorf("reqURL can not be nil"),
   449  		},
   450  		{
   451  			Name:     "target with no IPs",
   452  			InputURL: httpInputURL,
   453  			InputTarget: &httpValidationTarget{
   454  				host: "ipv4.and.ipv6.localhost",
   455  				port: va.httpPort,
   456  				path: "idk",
   457  			},
   458  			ExpectedRecord: core.ValidationRecord{
   459  				URL:      "http://ipv4.and.ipv6.localhost/yellow/brick/road",
   460  				Hostname: "ipv4.and.ipv6.localhost",
   461  				Port:     strconv.Itoa(va.httpPort),
   462  			},
   463  			ExpectedError: fmt.Errorf(`host "ipv4.and.ipv6.localhost" has no IP addresses remaining to use`),
   464  		},
   465  		{
   466  			Name:        "HTTP input req",
   467  			InputTarget: mustTarget(t, "ipv4.and.ipv6.localhost", va.httpPort, "/yellow/brick/road"),
   468  			InputURL:    httpInputURL,
   469  			ExpectedRecord: core.ValidationRecord{
   470  				Hostname:          "ipv4.and.ipv6.localhost",
   471  				Port:              strconv.Itoa(va.httpPort),
   472  				URL:               "http://ipv4.and.ipv6.localhost/yellow/brick/road",
   473  				AddressesResolved: []netip.Addr{netip.MustParseAddr("::1"), netip.MustParseAddr("127.0.0.1")},
   474  				AddressUsed:       netip.MustParseAddr("::1"),
   475  				ResolverAddrs:     []string{"MockClient"},
   476  			},
   477  			ExpectedDialer: &preresolvedDialer{
   478  				ip:      netip.MustParseAddr("::1"),
   479  				port:    va.httpPort,
   480  				timeout: va.singleDialTimeout,
   481  			},
   482  		},
   483  		{
   484  			Name:        "HTTPS input req",
   485  			InputTarget: mustTarget(t, "ipv4.and.ipv6.localhost", va.httpsPort, "/yellow/brick/road"),
   486  			InputURL:    httpsInputURL,
   487  			ExpectedRecord: core.ValidationRecord{
   488  				Hostname:          "ipv4.and.ipv6.localhost",
   489  				Port:              strconv.Itoa(va.httpsPort),
   490  				URL:               "https://ipv4.and.ipv6.localhost/yellow/brick/road",
   491  				AddressesResolved: []netip.Addr{netip.MustParseAddr("::1"), netip.MustParseAddr("127.0.0.1")},
   492  				AddressUsed:       netip.MustParseAddr("::1"),
   493  				ResolverAddrs:     []string{"MockClient"},
   494  			},
   495  			ExpectedDialer: &preresolvedDialer{
   496  				ip:      netip.MustParseAddr("::1"),
   497  				port:    va.httpsPort,
   498  				timeout: va.singleDialTimeout,
   499  			},
   500  		},
   501  	}
   502  
   503  	for _, tc := range testCases {
   504  		t.Run(tc.Name, func(t *testing.T) {
   505  			outDialer, outRecord, err := va.setupHTTPValidation(tc.InputURL, tc.InputTarget)
   506  			if err != nil && tc.ExpectedError == nil {
   507  				t.Errorf("Expected nil error, got %v", err)
   508  			} else if err == nil && tc.ExpectedError != nil {
   509  				t.Errorf("Expected %v error, got nil", tc.ExpectedError)
   510  			} else if err != nil && tc.ExpectedError != nil {
   511  				test.AssertEquals(t, err.Error(), tc.ExpectedError.Error())
   512  			}
   513  			if tc.ExpectedDialer == nil && outDialer != nil {
   514  				t.Errorf("Expected nil dialer, got %v", outDialer)
   515  			} else if tc.ExpectedDialer != nil {
   516  				test.AssertMarshaledEquals(t, outDialer, tc.ExpectedDialer)
   517  			}
   518  			// In all cases we expect there to have been a validation record
   519  			test.AssertMarshaledEquals(t, outRecord, tc.ExpectedRecord)
   520  		})
   521  	}
   522  }
   523  
   524  // A more concise version of httpSrv() that supports http.go tests
   525  func httpTestSrv(t *testing.T, ipv6 bool) *httptest.Server {
   526  	t.Helper()
   527  	mux := http.NewServeMux()
   528  	server := httptest.NewUnstartedServer(mux)
   529  
   530  	if ipv6 {
   531  		l, err := net.Listen("tcp", "[::1]:0")
   532  		if err != nil {
   533  			panic(fmt.Sprintf("httptest: failed to listen on a port: %v", err))
   534  		}
   535  		server.Listener = l
   536  	}
   537  
   538  	server.Start()
   539  	httpPort := getPort(server)
   540  
   541  	// A path that always returns an OK response
   542  	mux.HandleFunc("/ok", func(resp http.ResponseWriter, req *http.Request) {
   543  		resp.WriteHeader(http.StatusOK)
   544  		fmt.Fprint(resp, "ok")
   545  	})
   546  
   547  	// A path that always times out by sleeping longer than the validation context
   548  	// allows
   549  	mux.HandleFunc("/timeout", func(resp http.ResponseWriter, req *http.Request) {
   550  		time.Sleep(time.Second)
   551  		resp.WriteHeader(http.StatusOK)
   552  		fmt.Fprint(resp, "sorry, I'm a slow server")
   553  	})
   554  
   555  	// A path that always redirects to itself, creating a loop that will terminate
   556  	// when detected.
   557  	mux.HandleFunc("/loop", func(resp http.ResponseWriter, req *http.Request) {
   558  		http.Redirect(
   559  			resp,
   560  			req,
   561  			fmt.Sprintf("http://example.com:%d/loop", httpPort),
   562  			http.StatusMovedPermanently)
   563  	})
   564  
   565  	// A path that sequentially redirects, creating an incrementing redirect
   566  	// that will terminate when the redirect limit is reached and ensures each
   567  	// URL is different than the last.
   568  	for i := range maxRedirect + 2 {
   569  		mux.HandleFunc(fmt.Sprintf("/max-redirect/%d", i),
   570  			func(resp http.ResponseWriter, req *http.Request) {
   571  				http.Redirect(
   572  					resp,
   573  					req,
   574  					fmt.Sprintf("http://example.com:%d/max-redirect/%d", httpPort, i+1),
   575  					http.StatusMovedPermanently,
   576  				)
   577  			})
   578  	}
   579  
   580  	// A path that always redirects to a URL with a non-HTTP/HTTPs protocol scheme
   581  	mux.HandleFunc("/redir-bad-proto", func(resp http.ResponseWriter, req *http.Request) {
   582  		http.Redirect(
   583  			resp,
   584  			req,
   585  			"gopher://example.com",
   586  			http.StatusMovedPermanently,
   587  		)
   588  	})
   589  
   590  	// A path that always redirects to a URL with a port other than the configured
   591  	// HTTP/HTTPS port
   592  	mux.HandleFunc("/redir-bad-port", func(resp http.ResponseWriter, req *http.Request) {
   593  		http.Redirect(
   594  			resp,
   595  			req,
   596  			"https://example.com:1987",
   597  			http.StatusMovedPermanently,
   598  		)
   599  	})
   600  
   601  	// A path that always redirects to a URL with a bare IP address
   602  	mux.HandleFunc("/redir-bare-ipv4", func(resp http.ResponseWriter, req *http.Request) {
   603  		http.Redirect(
   604  			resp,
   605  			req,
   606  			"http://127.0.0.1/ok",
   607  			http.StatusMovedPermanently,
   608  		)
   609  	})
   610  
   611  	mux.HandleFunc("/redir-bare-ipv6", func(resp http.ResponseWriter, req *http.Request) {
   612  		http.Redirect(
   613  			resp,
   614  			req,
   615  			"http://[::1]/ok",
   616  			http.StatusMovedPermanently,
   617  		)
   618  	})
   619  
   620  	mux.HandleFunc("/bad-status-code", func(resp http.ResponseWriter, req *http.Request) {
   621  		resp.WriteHeader(http.StatusGone)
   622  		fmt.Fprint(resp, "sorry, I'm gone")
   623  	})
   624  
   625  	// A path that always responds with a 303 redirect
   626  	mux.HandleFunc("/303-see-other", func(resp http.ResponseWriter, req *http.Request) {
   627  		http.Redirect(
   628  			resp,
   629  			req,
   630  			"http://example.org/303-see-other",
   631  			http.StatusSeeOther,
   632  		)
   633  	})
   634  
   635  	tooLargeBuf := bytes.NewBuffer([]byte{})
   636  	for range maxResponseSize + 10 {
   637  		tooLargeBuf.WriteByte(byte(97))
   638  	}
   639  	mux.HandleFunc("/resp-too-big", func(resp http.ResponseWriter, req *http.Request) {
   640  		resp.WriteHeader(http.StatusOK)
   641  		fmt.Fprint(resp, tooLargeBuf)
   642  	})
   643  
   644  	// Create a buffer that starts with invalid UTF8 and is bigger than
   645  	// maxResponseSize
   646  	tooLargeInvalidUTF8 := bytes.NewBuffer([]byte{})
   647  	tooLargeInvalidUTF8.WriteString("f\xffoo")
   648  	tooLargeInvalidUTF8.Write(tooLargeBuf.Bytes())
   649  	// invalid-utf8-body Responds with body that is larger than
   650  	// maxResponseSize and starts with an invalid UTF8 string. This is to
   651  	// test the codepath where invalid UTF8 is converted to valid UTF8
   652  	// that can be passed as an error message via grpc.
   653  	mux.HandleFunc("/invalid-utf8-body", func(resp http.ResponseWriter, req *http.Request) {
   654  		resp.WriteHeader(http.StatusOK)
   655  		fmt.Fprint(resp, tooLargeInvalidUTF8)
   656  	})
   657  
   658  	mux.HandleFunc("/redir-path-too-long", func(resp http.ResponseWriter, req *http.Request) {
   659  		http.Redirect(
   660  			resp,
   661  			req,
   662  			"https://example.com/this-is-too-long-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789",
   663  			http.StatusMovedPermanently)
   664  	})
   665  
   666  	// A path that redirects to an uppercase public suffix (#4215)
   667  	mux.HandleFunc("/redir-uppercase-publicsuffix", func(resp http.ResponseWriter, req *http.Request) {
   668  		http.Redirect(
   669  			resp,
   670  			req,
   671  			"http://example.COM/ok",
   672  			http.StatusMovedPermanently)
   673  	})
   674  
   675  	// A path that returns a body containing printf formatting verbs
   676  	mux.HandleFunc("/printf-verbs", func(resp http.ResponseWriter, req *http.Request) {
   677  		resp.WriteHeader(http.StatusOK)
   678  		fmt.Fprint(resp, "%"+"2F.well-known%"+"2F"+tooLargeBuf.String())
   679  	})
   680  
   681  	return server
   682  }
   683  
   684  type testNetErr struct{}
   685  
   686  func (e *testNetErr) Error() string {
   687  	return "testNetErr"
   688  }
   689  
   690  func (e *testNetErr) Temporary() bool {
   691  	return false
   692  }
   693  
   694  func (e *testNetErr) Timeout() bool {
   695  	return false
   696  }
   697  
   698  func TestFallbackErr(t *testing.T) {
   699  	untypedErr := errors.New("the least interesting kind of error")
   700  	berr := berrors.InternalServerError("code violet: class neptune")
   701  	netOpErr := &net.OpError{
   702  		Op:  "siphon",
   703  		Err: fmt.Errorf("port was clogged. please empty packets"),
   704  	}
   705  	netDialOpErr := &net.OpError{
   706  		Op:  "dial",
   707  		Err: fmt.Errorf("your call is important to us - please stay on the line"),
   708  	}
   709  	netErr := &testNetErr{}
   710  
   711  	testCases := []struct {
   712  		Name           string
   713  		Err            error
   714  		ExpectFallback bool
   715  	}{
   716  		{
   717  			Name: "Nil error",
   718  			Err:  nil,
   719  		},
   720  		{
   721  			Name: "Standard untyped error",
   722  			Err:  untypedErr,
   723  		},
   724  		{
   725  			Name: "A Boulder error instance",
   726  			Err:  berr,
   727  		},
   728  		{
   729  			Name: "A non-dial net.OpError instance",
   730  			Err:  netOpErr,
   731  		},
   732  		{
   733  			Name:           "A dial net.OpError instance",
   734  			Err:            netDialOpErr,
   735  			ExpectFallback: true,
   736  		},
   737  		{
   738  			Name: "A generic net.Error instance",
   739  			Err:  netErr,
   740  		},
   741  		{
   742  			Name: "A URL error wrapping a standard error",
   743  			Err: &url.Error{
   744  				Op:  "ivy",
   745  				URL: "https://en.wikipedia.org/wiki/Operation_Ivy_(band)",
   746  				Err: errors.New("take warning"),
   747  			},
   748  		},
   749  		{
   750  			Name: "A URL error wrapping a nil error",
   751  			Err: &url.Error{
   752  				Err: nil,
   753  			},
   754  		},
   755  		{
   756  			Name: "A URL error wrapping a Boulder error instance",
   757  			Err: &url.Error{
   758  				Err: berr,
   759  			},
   760  		},
   761  		{
   762  			Name: "A URL error wrapping a non-dial net OpError",
   763  			Err: &url.Error{
   764  				Err: netOpErr,
   765  			},
   766  		},
   767  		{
   768  			Name: "A URL error wrapping a dial net.OpError",
   769  			Err: &url.Error{
   770  				Err: netDialOpErr,
   771  			},
   772  			ExpectFallback: true,
   773  		},
   774  		{
   775  			Name: "A URL error wrapping a generic net Error",
   776  			Err: &url.Error{
   777  				Err: netErr,
   778  			},
   779  		},
   780  	}
   781  
   782  	for _, tc := range testCases {
   783  		t.Run(tc.Name, func(t *testing.T) {
   784  			if isFallback := fallbackErr(tc.Err); isFallback != tc.ExpectFallback {
   785  				t.Errorf(
   786  					"Expected fallbackErr for %t to be %v was %v\n",
   787  					tc.Err, tc.ExpectFallback, isFallback)
   788  			}
   789  		})
   790  	}
   791  }
   792  
   793  func TestFetchHTTP(t *testing.T) {
   794  	// Create test servers
   795  	testSrvIPv4 := httpTestSrv(t, false)
   796  	defer testSrvIPv4.Close()
   797  	testSrvIPv6 := httpTestSrv(t, true)
   798  	defer testSrvIPv6.Close()
   799  
   800  	// Setup VAs. By providing the testSrv to setup the VA will use the testSrv's
   801  	// randomly assigned port as its HTTP port.
   802  	vaIPv4, _ := setup(testSrvIPv4, "", nil, nil)
   803  	vaIPv6, _ := setup(testSrvIPv6, "", nil, nil)
   804  
   805  	// We need to know the randomly assigned HTTP port for testcases as well
   806  	httpPortIPv4 := getPort(testSrvIPv4)
   807  	httpPortIPv6 := getPort(testSrvIPv6)
   808  
   809  	// For the looped test case we expect one validation record per redirect
   810  	// until boulder detects that a url has been used twice indicating a
   811  	// redirect loop. Because it is hitting the /loop endpoint it will encounter
   812  	// this scenario after the base url and fail on the second time hitting the
   813  	// redirect with a port definition. On i=0 it will encounter the first
   814  	// redirect to the url with a port definition and on i=1 it will encounter
   815  	// the second redirect to the url with the port and get an expected error.
   816  	expectedLoopRecords := []core.ValidationRecord{}
   817  	for i := range 2 {
   818  		// The first request will not have a port # in the URL.
   819  		url := "http://example.com/loop"
   820  		if i != 0 {
   821  			url = fmt.Sprintf("http://example.com:%d/loop", httpPortIPv4)
   822  		}
   823  		expectedLoopRecords = append(expectedLoopRecords,
   824  			core.ValidationRecord{
   825  				Hostname:          "example.com",
   826  				Port:              strconv.Itoa(httpPortIPv4),
   827  				URL:               url,
   828  				AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
   829  				AddressUsed:       netip.MustParseAddr("127.0.0.1"),
   830  				ResolverAddrs:     []string{"MockClient"},
   831  			})
   832  	}
   833  
   834  	// For the too many redirect test case we expect one validation record per
   835  	// redirect up to maxRedirect (inclusive). There is also +1 record for the
   836  	// base lookup, giving a termination criteria of > maxRedirect+1
   837  	expectedTooManyRedirRecords := []core.ValidationRecord{}
   838  	for i := range maxRedirect + 2 {
   839  		// The first request will not have a port # in the URL.
   840  		url := "http://example.com/max-redirect/0"
   841  		if i != 0 {
   842  			url = fmt.Sprintf("http://example.com:%d/max-redirect/%d", httpPortIPv4, i)
   843  		}
   844  		expectedTooManyRedirRecords = append(expectedTooManyRedirRecords,
   845  			core.ValidationRecord{
   846  				Hostname:          "example.com",
   847  				Port:              strconv.Itoa(httpPortIPv4),
   848  				URL:               url,
   849  				AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
   850  				AddressUsed:       netip.MustParseAddr("127.0.0.1"),
   851  				ResolverAddrs:     []string{"MockClient"},
   852  			})
   853  	}
   854  
   855  	expectedTruncatedResp := bytes.NewBuffer([]byte{})
   856  	for range maxResponseSize {
   857  		expectedTruncatedResp.WriteByte(byte(97))
   858  	}
   859  
   860  	testCases := []struct {
   861  		Name            string
   862  		IPv6            bool
   863  		Ident           identifier.ACMEIdentifier
   864  		Path            string
   865  		ExpectedBody    string
   866  		ExpectedRecords []core.ValidationRecord
   867  		ExpectedProblem *probs.ProblemDetails
   868  	}{
   869  		{
   870  			Name:  "No IPs for host",
   871  			Ident: identifier.NewDNS("always.invalid"),
   872  			Path:  "/.well-known/whatever",
   873  			ExpectedProblem: probs.DNS(
   874  				"No valid IP addresses found for always.invalid"),
   875  			// There are no validation records in this case because the base record
   876  			// is only constructed once a URL is made.
   877  			ExpectedRecords: nil,
   878  		},
   879  		{
   880  			Name:  "Timeout for host with standard ACME allowed port",
   881  			Ident: identifier.NewDNS("example.com"),
   882  			Path:  "/timeout",
   883  			ExpectedProblem: probs.Connection(
   884  				"127.0.0.1: Fetching http://example.com/timeout: " +
   885  					"Timeout after connect (your server may be slow or overloaded)"),
   886  			ExpectedRecords: []core.ValidationRecord{
   887  				{
   888  					Hostname:          "example.com",
   889  					Port:              strconv.Itoa(httpPortIPv4),
   890  					URL:               "http://example.com/timeout",
   891  					AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
   892  					AddressUsed:       netip.MustParseAddr("127.0.0.1"),
   893  					ResolverAddrs:     []string{"MockClient"},
   894  				},
   895  			},
   896  		},
   897  		{
   898  			Name:  "Redirect loop",
   899  			Ident: identifier.NewDNS("example.com"),
   900  			Path:  "/loop",
   901  			ExpectedProblem: probs.Connection(fmt.Sprintf(
   902  				"127.0.0.1: Fetching http://example.com:%d/loop: Redirect loop detected", httpPortIPv4)),
   903  			ExpectedRecords: expectedLoopRecords,
   904  		},
   905  		{
   906  			Name:  "Too many redirects",
   907  			Ident: identifier.NewDNS("example.com"),
   908  			Path:  "/max-redirect/0",
   909  			ExpectedProblem: probs.Connection(fmt.Sprintf(
   910  				"127.0.0.1: Fetching http://example.com:%d/max-redirect/12: Too many redirects", httpPortIPv4)),
   911  			ExpectedRecords: expectedTooManyRedirRecords,
   912  		},
   913  		{
   914  			Name:  "Redirect to bad protocol",
   915  			Ident: identifier.NewDNS("example.com"),
   916  			Path:  "/redir-bad-proto",
   917  			ExpectedProblem: probs.Connection(
   918  				"127.0.0.1: Fetching gopher://example.com: Invalid protocol scheme in " +
   919  					`redirect target. Only "http" and "https" protocol schemes ` +
   920  					`are supported, not "gopher"`),
   921  			ExpectedRecords: []core.ValidationRecord{
   922  				{
   923  					Hostname:          "example.com",
   924  					Port:              strconv.Itoa(httpPortIPv4),
   925  					URL:               "http://example.com/redir-bad-proto",
   926  					AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
   927  					AddressUsed:       netip.MustParseAddr("127.0.0.1"),
   928  					ResolverAddrs:     []string{"MockClient"},
   929  				},
   930  			},
   931  		},
   932  		{
   933  			Name:  "Redirect to bad port",
   934  			Ident: identifier.NewDNS("example.com"),
   935  			Path:  "/redir-bad-port",
   936  			ExpectedProblem: probs.Connection(fmt.Sprintf(
   937  				"127.0.0.1: Fetching https://example.com:1987: Invalid port in redirect target. "+
   938  					"Only ports %d and 443 are supported, not 1987", httpPortIPv4)),
   939  			ExpectedRecords: []core.ValidationRecord{
   940  				{
   941  					Hostname:          "example.com",
   942  					Port:              strconv.Itoa(httpPortIPv4),
   943  					URL:               "http://example.com/redir-bad-port",
   944  					AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
   945  					AddressUsed:       netip.MustParseAddr("127.0.0.1"),
   946  					ResolverAddrs:     []string{"MockClient"},
   947  				},
   948  			},
   949  		},
   950  		{
   951  			Name:         "Redirect to bare IPv4 address",
   952  			Ident:        identifier.NewDNS("example.com"),
   953  			Path:         "/redir-bare-ipv4",
   954  			ExpectedBody: "ok",
   955  			ExpectedRecords: []core.ValidationRecord{
   956  				{
   957  					Hostname:          "example.com",
   958  					Port:              strconv.Itoa(httpPortIPv4),
   959  					URL:               "http://example.com/redir-bare-ipv4",
   960  					AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
   961  					AddressUsed:       netip.MustParseAddr("127.0.0.1"),
   962  					ResolverAddrs:     []string{"MockClient"},
   963  				},
   964  				{
   965  					Hostname:          "127.0.0.1",
   966  					Port:              strconv.Itoa(httpPortIPv4),
   967  					URL:               "http://127.0.0.1/ok",
   968  					AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
   969  					AddressUsed:       netip.MustParseAddr("127.0.0.1"),
   970  				},
   971  			},
   972  		}, {
   973  			Name:         "Redirect to bare IPv6 address",
   974  			IPv6:         true,
   975  			Ident:        identifier.NewDNS("ipv6.localhost"),
   976  			Path:         "/redir-bare-ipv6",
   977  			ExpectedBody: "ok",
   978  			ExpectedRecords: []core.ValidationRecord{
   979  				{
   980  					Hostname:          "ipv6.localhost",
   981  					Port:              strconv.Itoa(httpPortIPv6),
   982  					URL:               "http://ipv6.localhost/redir-bare-ipv6",
   983  					AddressesResolved: []netip.Addr{netip.MustParseAddr("::1")},
   984  					AddressUsed:       netip.MustParseAddr("::1"),
   985  					ResolverAddrs:     []string{"MockClient"},
   986  				},
   987  				{
   988  					Hostname:          "::1",
   989  					Port:              strconv.Itoa(httpPortIPv6),
   990  					URL:               "http://[::1]/ok",
   991  					AddressesResolved: []netip.Addr{netip.MustParseAddr("::1")},
   992  					AddressUsed:       netip.MustParseAddr("::1"),
   993  				},
   994  			},
   995  		},
   996  		{
   997  			Name:  "Redirect to long path",
   998  			Ident: identifier.NewDNS("example.com"),
   999  			Path:  "/redir-path-too-long",
  1000  			ExpectedProblem: probs.Connection(
  1001  				"127.0.0.1: Fetching https://example.com/this-is-too-long-01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789: Redirect target too long"),
  1002  			ExpectedRecords: []core.ValidationRecord{
  1003  				{
  1004  					Hostname:          "example.com",
  1005  					Port:              strconv.Itoa(httpPortIPv4),
  1006  					URL:               "http://example.com/redir-path-too-long",
  1007  					AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
  1008  					AddressUsed:       netip.MustParseAddr("127.0.0.1"),
  1009  					ResolverAddrs:     []string{"MockClient"},
  1010  				},
  1011  			},
  1012  		},
  1013  		{
  1014  			Name:  "Wrong HTTP status code",
  1015  			Ident: identifier.NewDNS("example.com"),
  1016  			Path:  "/bad-status-code",
  1017  			ExpectedProblem: probs.Unauthorized(
  1018  				"127.0.0.1: Invalid response from http://example.com/bad-status-code: 410"),
  1019  			ExpectedRecords: []core.ValidationRecord{
  1020  				{
  1021  					Hostname:          "example.com",
  1022  					Port:              strconv.Itoa(httpPortIPv4),
  1023  					URL:               "http://example.com/bad-status-code",
  1024  					AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
  1025  					AddressUsed:       netip.MustParseAddr("127.0.0.1"),
  1026  					ResolverAddrs:     []string{"MockClient"},
  1027  				},
  1028  			},
  1029  		},
  1030  		{
  1031  			Name:  "HTTP status code 303 redirect",
  1032  			Ident: identifier.NewDNS("example.com"),
  1033  			Path:  "/303-see-other",
  1034  			ExpectedProblem: probs.Connection(
  1035  				"127.0.0.1: Fetching http://example.org/303-see-other: received disallowed redirect status code"),
  1036  			ExpectedRecords: []core.ValidationRecord{
  1037  				{
  1038  					Hostname:          "example.com",
  1039  					Port:              strconv.Itoa(httpPortIPv4),
  1040  					URL:               "http://example.com/303-see-other",
  1041  					AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
  1042  					AddressUsed:       netip.MustParseAddr("127.0.0.1"),
  1043  					ResolverAddrs:     []string{"MockClient"},
  1044  				},
  1045  			},
  1046  		},
  1047  		{
  1048  			Name:  "Response too large",
  1049  			Ident: identifier.NewDNS("example.com"),
  1050  			Path:  "/resp-too-big",
  1051  			ExpectedProblem: probs.Unauthorized(fmt.Sprintf(
  1052  				"127.0.0.1: Invalid response from http://example.com/resp-too-big: %q", expectedTruncatedResp.String(),
  1053  			)),
  1054  			ExpectedRecords: []core.ValidationRecord{
  1055  				{
  1056  					Hostname:          "example.com",
  1057  					Port:              strconv.Itoa(httpPortIPv4),
  1058  					URL:               "http://example.com/resp-too-big",
  1059  					AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
  1060  					AddressUsed:       netip.MustParseAddr("127.0.0.1"),
  1061  					ResolverAddrs:     []string{"MockClient"},
  1062  				},
  1063  			},
  1064  		},
  1065  		{
  1066  			Name:  "Broken IPv6 only",
  1067  			Ident: identifier.NewDNS("ipv6.localhost"),
  1068  			Path:  "/ok",
  1069  			ExpectedProblem: probs.Connection(
  1070  				"::1: Fetching http://ipv6.localhost/ok: Connection refused"),
  1071  			ExpectedRecords: []core.ValidationRecord{
  1072  				{
  1073  					Hostname:          "ipv6.localhost",
  1074  					Port:              strconv.Itoa(httpPortIPv4),
  1075  					URL:               "http://ipv6.localhost/ok",
  1076  					AddressesResolved: []netip.Addr{netip.MustParseAddr("::1")},
  1077  					AddressUsed:       netip.MustParseAddr("::1"),
  1078  					ResolverAddrs:     []string{"MockClient"},
  1079  				},
  1080  			},
  1081  		},
  1082  		{
  1083  			Name:         "Dual homed w/ broken IPv6, working IPv4",
  1084  			Ident:        identifier.NewDNS("ipv4.and.ipv6.localhost"),
  1085  			Path:         "/ok",
  1086  			ExpectedBody: "ok",
  1087  			ExpectedRecords: []core.ValidationRecord{
  1088  				{
  1089  					Hostname:          "ipv4.and.ipv6.localhost",
  1090  					Port:              strconv.Itoa(httpPortIPv4),
  1091  					URL:               "http://ipv4.and.ipv6.localhost/ok",
  1092  					AddressesResolved: []netip.Addr{netip.MustParseAddr("::1"), netip.MustParseAddr("127.0.0.1")},
  1093  					// The first validation record should have used the IPv6 addr
  1094  					AddressUsed:   netip.MustParseAddr("::1"),
  1095  					ResolverAddrs: []string{"MockClient"},
  1096  				},
  1097  				{
  1098  					Hostname:          "ipv4.and.ipv6.localhost",
  1099  					Port:              strconv.Itoa(httpPortIPv4),
  1100  					URL:               "http://ipv4.and.ipv6.localhost/ok",
  1101  					AddressesResolved: []netip.Addr{netip.MustParseAddr("::1"), netip.MustParseAddr("127.0.0.1")},
  1102  					// The second validation record should have used the IPv4 addr as a fallback
  1103  					AddressUsed:   netip.MustParseAddr("127.0.0.1"),
  1104  					ResolverAddrs: []string{"MockClient"},
  1105  				},
  1106  			},
  1107  		},
  1108  		{
  1109  			Name:         "Working IPv4 only",
  1110  			Ident:        identifier.NewDNS("example.com"),
  1111  			Path:         "/ok",
  1112  			ExpectedBody: "ok",
  1113  			ExpectedRecords: []core.ValidationRecord{
  1114  				{
  1115  					Hostname:          "example.com",
  1116  					Port:              strconv.Itoa(httpPortIPv4),
  1117  					URL:               "http://example.com/ok",
  1118  					AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
  1119  					AddressUsed:       netip.MustParseAddr("127.0.0.1"),
  1120  					ResolverAddrs:     []string{"MockClient"},
  1121  				},
  1122  			},
  1123  		},
  1124  		{
  1125  			Name:         "Redirect to uppercase Public Suffix",
  1126  			Ident:        identifier.NewDNS("example.com"),
  1127  			Path:         "/redir-uppercase-publicsuffix",
  1128  			ExpectedBody: "ok",
  1129  			ExpectedRecords: []core.ValidationRecord{
  1130  				{
  1131  					Hostname:          "example.com",
  1132  					Port:              strconv.Itoa(httpPortIPv4),
  1133  					URL:               "http://example.com/redir-uppercase-publicsuffix",
  1134  					AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
  1135  					AddressUsed:       netip.MustParseAddr("127.0.0.1"),
  1136  					ResolverAddrs:     []string{"MockClient"},
  1137  				},
  1138  				{
  1139  					Hostname:          "example.com",
  1140  					Port:              strconv.Itoa(httpPortIPv4),
  1141  					URL:               "http://example.com/ok",
  1142  					AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
  1143  					AddressUsed:       netip.MustParseAddr("127.0.0.1"),
  1144  					ResolverAddrs:     []string{"MockClient"},
  1145  				},
  1146  			},
  1147  		},
  1148  		{
  1149  			Name:  "Reflected response body containing printf verbs",
  1150  			Ident: identifier.NewDNS("example.com"),
  1151  			Path:  "/printf-verbs",
  1152  			ExpectedProblem: &probs.ProblemDetails{
  1153  				Type: probs.UnauthorizedProblem,
  1154  				Detail: fmt.Sprintf("127.0.0.1: Invalid response from http://example.com/printf-verbs: %q",
  1155  					("%2F.well-known%2F" + expectedTruncatedResp.String())[:maxResponseSize]),
  1156  				HTTPStatus: http.StatusForbidden,
  1157  			},
  1158  			ExpectedRecords: []core.ValidationRecord{
  1159  				{
  1160  					Hostname:          "example.com",
  1161  					Port:              strconv.Itoa(httpPortIPv4),
  1162  					URL:               "http://example.com/printf-verbs",
  1163  					AddressesResolved: []netip.Addr{netip.MustParseAddr("127.0.0.1")},
  1164  					AddressUsed:       netip.MustParseAddr("127.0.0.1"),
  1165  					ResolverAddrs:     []string{"MockClient"},
  1166  				},
  1167  			},
  1168  		},
  1169  	}
  1170  
  1171  	for _, tc := range testCases {
  1172  		t.Run(tc.Name, func(t *testing.T) {
  1173  			ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
  1174  			defer cancel()
  1175  			var body []byte
  1176  			var records []core.ValidationRecord
  1177  			var err error
  1178  			if tc.IPv6 {
  1179  				body, records, err = vaIPv6.processHTTPValidation(ctx, tc.Ident, tc.Path)
  1180  			} else {
  1181  				body, records, err = vaIPv4.processHTTPValidation(ctx, tc.Ident, tc.Path)
  1182  			}
  1183  			if tc.ExpectedProblem == nil {
  1184  				test.AssertNotError(t, err, "expected nil prob")
  1185  			} else {
  1186  				test.AssertError(t, err, "expected non-nil prob")
  1187  				prob := detailedError(err)
  1188  				test.AssertMarshaledEquals(t, prob, tc.ExpectedProblem)
  1189  			}
  1190  			if tc.ExpectedBody != "" {
  1191  				test.AssertEquals(t, string(body), tc.ExpectedBody)
  1192  			}
  1193  			// in all cases we expect validation records to be present and matching expected
  1194  			test.AssertMarshaledEquals(t, records, tc.ExpectedRecords)
  1195  		})
  1196  	}
  1197  }
  1198  
  1199  // All paths that get assigned to tokens MUST be valid tokens
  1200  const pathWrongToken = "i6lNAC4lOOLYCl-A08VJt9z_tKYvVk63Dumo8icsBjQ"
  1201  const path404 = "404"
  1202  const path500 = "500"
  1203  const pathFound = "GBq8SwWq3JsbREFdCamk5IX3KLsxW5ULeGs98Ajl_UM"
  1204  const pathMoved = "5J4FIMrWNfmvHZo-QpKZngmuhqZGwRm21-oEgUDstJM"
  1205  const pathRedirectInvalidPort = "port-redirect"
  1206  const pathWait = "wait"
  1207  const pathWaitLong = "wait-long"
  1208  const pathReLookup = "7e-P57coLM7D3woNTp_xbJrtlkDYy6PWf3mSSbLwCr4"
  1209  const pathReLookupInvalid = "re-lookup-invalid"
  1210  const pathRedirectToFailingURL = "re-to-failing-url"
  1211  const pathLooper = "looper"
  1212  const pathValid = "valid"
  1213  const rejectUserAgent = "rejectMe"
  1214  
  1215  func httpSrv(t *testing.T, token string, ipv6 bool) *httptest.Server {
  1216  	m := http.NewServeMux()
  1217  
  1218  	server := httptest.NewUnstartedServer(m)
  1219  
  1220  	defaultToken := token
  1221  	currentToken := defaultToken
  1222  
  1223  	m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  1224  		if strings.HasSuffix(r.URL.Path, path404) {
  1225  			t.Logf("HTTPSRV: Got a 404 req\n")
  1226  			http.NotFound(w, r)
  1227  		} else if strings.HasSuffix(r.URL.Path, path500) {
  1228  			t.Logf("HTTPSRV: Got a 500 req\n")
  1229  			http.Error(w, "Internal Server Error", http.StatusInternalServerError)
  1230  		} else if strings.HasSuffix(r.URL.Path, pathMoved) {
  1231  			t.Logf("HTTPSRV: Got a http.StatusMovedPermanently redirect req\n")
  1232  			if currentToken == defaultToken {
  1233  				currentToken = pathMoved
  1234  			}
  1235  			http.Redirect(w, r, pathValid, http.StatusMovedPermanently)
  1236  		} else if strings.HasSuffix(r.URL.Path, pathFound) {
  1237  			t.Logf("HTTPSRV: Got a http.StatusFound redirect req\n")
  1238  			if currentToken == defaultToken {
  1239  				currentToken = pathFound
  1240  			}
  1241  			http.Redirect(w, r, pathMoved, http.StatusFound)
  1242  		} else if strings.HasSuffix(r.URL.Path, pathWait) {
  1243  			t.Logf("HTTPSRV: Got a wait req\n")
  1244  			time.Sleep(time.Second * 3)
  1245  		} else if strings.HasSuffix(r.URL.Path, pathWaitLong) {
  1246  			t.Logf("HTTPSRV: Got a wait-long req\n")
  1247  			time.Sleep(time.Second * 10)
  1248  		} else if strings.HasSuffix(r.URL.Path, pathReLookup) {
  1249  			t.Logf("HTTPSRV: Got a redirect req to a valid hostname\n")
  1250  			if currentToken == defaultToken {
  1251  				currentToken = pathReLookup
  1252  			}
  1253  			port := getPort(server)
  1254  			http.Redirect(w, r, fmt.Sprintf("http://other.valid.com:%d/path", port), http.StatusFound)
  1255  		} else if strings.HasSuffix(r.URL.Path, pathReLookupInvalid) {
  1256  			t.Logf("HTTPSRV: Got a redirect req to an invalid host\n")
  1257  			http.Redirect(w, r, "http://invalid.invalid/path", http.StatusFound)
  1258  		} else if strings.HasSuffix(r.URL.Path, pathRedirectToFailingURL) {
  1259  			t.Logf("HTTPSRV: Redirecting to a URL that will fail\n")
  1260  			port := getPort(server)
  1261  			http.Redirect(w, r, fmt.Sprintf("http://other.valid.com:%d/%s", port, path500), http.StatusMovedPermanently)
  1262  		} else if strings.HasSuffix(r.URL.Path, pathLooper) {
  1263  			t.Logf("HTTPSRV: Got a loop req\n")
  1264  			http.Redirect(w, r, r.URL.String(), http.StatusMovedPermanently)
  1265  		} else if strings.HasSuffix(r.URL.Path, pathRedirectInvalidPort) {
  1266  			t.Logf("HTTPSRV: Got a port redirect req\n")
  1267  			// Port 8080 is not the VA's httpPort or httpsPort and should be rejected
  1268  			http.Redirect(w, r, "http://other.valid.com:8080/path", http.StatusFound)
  1269  		} else if r.Header.Get("User-Agent") == rejectUserAgent {
  1270  			w.WriteHeader(http.StatusBadRequest)
  1271  			w.Write([]byte("found trap User-Agent"))
  1272  		} else {
  1273  			t.Logf("HTTPSRV: Got a valid req\n")
  1274  			t.Logf("HTTPSRV: Path = %s\n", r.URL.Path)
  1275  
  1276  			ch := core.Challenge{Token: currentToken}
  1277  			keyAuthz, _ := ch.ExpectedKeyAuthorization(accountKey)
  1278  			t.Logf("HTTPSRV: Key Authz = '%s%s'\n", keyAuthz, "\\n\\r \\t")
  1279  
  1280  			fmt.Fprint(w, keyAuthz, "\n\r \t")
  1281  			currentToken = defaultToken
  1282  		}
  1283  	})
  1284  
  1285  	if ipv6 {
  1286  		l, err := net.Listen("tcp", "[::1]:0")
  1287  		if err != nil {
  1288  			panic(fmt.Sprintf("httptest: failed to listen on a port: %v", err))
  1289  		}
  1290  		server.Listener = l
  1291  	}
  1292  
  1293  	server.Start()
  1294  	return server
  1295  }
  1296  
  1297  func TestHTTPBadPort(t *testing.T) {
  1298  	hs := httpSrv(t, expectedToken, false)
  1299  	defer hs.Close()
  1300  
  1301  	va, _ := setup(hs, "", nil, nil)
  1302  
  1303  	// Pick a random port between 40000 and 65000 - with great certainty we won't
  1304  	// have an HTTP server listening on this port and the test will fail as
  1305  	// intended
  1306  	badPort := 40000 + mrand.IntN(25000)
  1307  	va.httpPort = badPort
  1308  
  1309  	_, err := va.validateHTTP01(ctx, identifier.NewDNS("localhost"), expectedToken, expectedKeyAuthorization)
  1310  	if err == nil {
  1311  		t.Fatalf("Server's down; expected refusal. Where did we connect?")
  1312  	}
  1313  	prob := detailedError(err)
  1314  	test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
  1315  	if !strings.Contains(prob.Detail, "Connection refused") {
  1316  		t.Errorf("Expected a connection refused error, got %q", prob.Detail)
  1317  	}
  1318  }
  1319  
  1320  func TestHTTPBadIdentifier(t *testing.T) {
  1321  	hs := httpSrv(t, expectedToken, false)
  1322  	defer hs.Close()
  1323  
  1324  	va, _ := setup(hs, "", nil, nil)
  1325  
  1326  	_, err := va.validateHTTP01(ctx, identifier.ACMEIdentifier{Type: "smime", Value: "dobber@bad.horse"}, expectedToken, expectedKeyAuthorization)
  1327  	if err == nil {
  1328  		t.Fatalf("Server accepted a hypothetical S/MIME identifier")
  1329  	}
  1330  	prob := detailedError(err)
  1331  	test.AssertEquals(t, prob.Type, probs.MalformedProblem)
  1332  	if !strings.Contains(prob.Detail, "Identifier type for HTTP-01 challenge was not DNS or IP") {
  1333  		t.Errorf("Expected an identifier type error, got %q", prob.Detail)
  1334  	}
  1335  }
  1336  
  1337  func TestHTTPKeyAuthorizationFileMismatch(t *testing.T) {
  1338  	m := http.NewServeMux()
  1339  	hs := httptest.NewUnstartedServer(m)
  1340  	m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  1341  		w.Write([]byte("\xef\xffAABBCC"))
  1342  	})
  1343  	hs.Start()
  1344  
  1345  	va, _ := setup(hs, "", nil, nil)
  1346  	_, err := va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), expectedToken, expectedKeyAuthorization)
  1347  
  1348  	if err == nil {
  1349  		t.Fatalf("Expected validation to fail when file mismatched.")
  1350  	}
  1351  	expected := fmt.Sprintf(`The key authorization file from the server did not match this challenge. Expected "%s" (got "\xef\xffAABBCC")`, expectedKeyAuthorization)
  1352  	if err.Error() != expected {
  1353  		t.Errorf("validation failed with %s, expected %s", err, expected)
  1354  	}
  1355  }
  1356  
  1357  func TestHTTP(t *testing.T) {
  1358  	hs := httpSrv(t, expectedToken, false)
  1359  	defer hs.Close()
  1360  
  1361  	va, log := setup(hs, "", nil, nil)
  1362  
  1363  	_, err := va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), expectedToken, expectedKeyAuthorization)
  1364  	if err != nil {
  1365  		t.Errorf("Unexpected failure in HTTP validation for DNS: %s", err)
  1366  	}
  1367  	test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1)
  1368  
  1369  	log.Clear()
  1370  	_, err = va.validateHTTP01(ctx, identifier.NewIP(netip.MustParseAddr("127.0.0.1")), expectedToken, expectedKeyAuthorization)
  1371  	if err != nil {
  1372  		t.Errorf("Unexpected failure in HTTP validation for IPv4: %s", err)
  1373  	}
  1374  	test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1)
  1375  
  1376  	log.Clear()
  1377  	_, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), path404, ka(path404))
  1378  	if err == nil {
  1379  		t.Fatalf("Should have found a 404 for the challenge.")
  1380  	}
  1381  	test.AssertErrorIs(t, err, berrors.Unauthorized)
  1382  	test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1)
  1383  
  1384  	log.Clear()
  1385  	// The "wrong token" will actually be the expectedToken.  It's wrong
  1386  	// because it doesn't match pathWrongToken.
  1387  	_, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathWrongToken, ka(pathWrongToken))
  1388  	if err == nil {
  1389  		t.Fatalf("Should have found the wrong token value.")
  1390  	}
  1391  	prob := detailedError(err)
  1392  	test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
  1393  	test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1)
  1394  
  1395  	log.Clear()
  1396  	_, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathMoved, ka(pathMoved))
  1397  	if err != nil {
  1398  		t.Fatalf("Failed to follow http.StatusMovedPermanently redirect")
  1399  	}
  1400  	redirectValid := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathValid + `"`
  1401  	matchedValidRedirect := log.GetAllMatching(redirectValid)
  1402  	test.AssertEquals(t, len(matchedValidRedirect), 1)
  1403  
  1404  	log.Clear()
  1405  	_, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathFound, ka(pathFound))
  1406  	if err != nil {
  1407  		t.Fatalf("Failed to follow http.StatusFound redirect")
  1408  	}
  1409  	redirectMoved := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathMoved + `"`
  1410  	matchedMovedRedirect := log.GetAllMatching(redirectMoved)
  1411  	test.AssertEquals(t, len(matchedValidRedirect), 1)
  1412  	test.AssertEquals(t, len(matchedMovedRedirect), 1)
  1413  
  1414  	_, err = va.validateHTTP01(ctx, identifier.NewDNS("always.invalid"), pathFound, ka(pathFound))
  1415  	if err == nil {
  1416  		t.Fatalf("Domain name is invalid.")
  1417  	}
  1418  	prob = detailedError(err)
  1419  	test.AssertEquals(t, prob.Type, probs.DNSProblem)
  1420  }
  1421  
  1422  func TestHTTPIPv6(t *testing.T) {
  1423  	hs := httpSrv(t, expectedToken, true)
  1424  	defer hs.Close()
  1425  
  1426  	va, log := setup(hs, "", nil, nil)
  1427  
  1428  	_, err := va.validateHTTP01(ctx, identifier.NewIP(netip.MustParseAddr("::1")), expectedToken, expectedKeyAuthorization)
  1429  	if err != nil {
  1430  		t.Errorf("Unexpected failure in HTTP validation for IPv6: %s", err)
  1431  	}
  1432  	test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1)
  1433  }
  1434  
  1435  func TestHTTPTimeout(t *testing.T) {
  1436  	hs := httpSrv(t, expectedToken, false)
  1437  	defer hs.Close()
  1438  
  1439  	va, _ := setup(hs, "", nil, nil)
  1440  
  1441  	started := time.Now()
  1442  	timeout := 250 * time.Millisecond
  1443  	ctx, cancel := context.WithTimeout(context.Background(), timeout)
  1444  	defer cancel()
  1445  	_, err := va.validateHTTP01(ctx, identifier.NewDNS("localhost"), pathWaitLong, ka(pathWaitLong))
  1446  	if err == nil {
  1447  		t.Fatalf("Connection should've timed out")
  1448  	}
  1449  
  1450  	took := time.Since(started)
  1451  	// Check that the HTTP connection doesn't return before a timeout, and times
  1452  	// out after the expected time
  1453  	if took < timeout-200*time.Millisecond {
  1454  		t.Fatalf("HTTP timed out before %s: %s with %s", timeout, took, err)
  1455  	}
  1456  	if took > 2*timeout {
  1457  		t.Fatalf("HTTP connection didn't timeout after %s", timeout)
  1458  	}
  1459  	prob := detailedError(err)
  1460  	test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
  1461  	test.AssertEquals(t, prob.Detail, "127.0.0.1: Fetching http://localhost/.well-known/acme-challenge/wait-long: Timeout after connect (your server may be slow or overloaded)")
  1462  }
  1463  
  1464  func TestHTTPRedirectLookup(t *testing.T) {
  1465  	hs := httpSrv(t, expectedToken, false)
  1466  	defer hs.Close()
  1467  	va, log := setup(hs, "", nil, nil)
  1468  
  1469  	_, err := va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathMoved, ka(pathMoved))
  1470  	if err != nil {
  1471  		t.Fatalf("Unexpected failure in redirect (%s): %s", pathMoved, err)
  1472  	}
  1473  	redirectValid := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathValid + `"`
  1474  	matchedValidRedirect := log.GetAllMatching(redirectValid)
  1475  	test.AssertEquals(t, len(matchedValidRedirect), 1)
  1476  	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 2)
  1477  
  1478  	log.Clear()
  1479  	_, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathFound, ka(pathFound))
  1480  	if err != nil {
  1481  		t.Fatalf("Unexpected failure in redirect (%s): %s", pathFound, err)
  1482  	}
  1483  	redirectMoved := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathMoved + `"`
  1484  	matchedMovedRedirect := log.GetAllMatching(redirectMoved)
  1485  	test.AssertEquals(t, len(matchedMovedRedirect), 1)
  1486  	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 3)
  1487  
  1488  	log.Clear()
  1489  	_, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathReLookupInvalid, ka(pathReLookupInvalid))
  1490  	test.AssertError(t, err, "error for pathReLookupInvalid should not be nil")
  1491  	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 1)
  1492  	prob := detailedError(err)
  1493  	test.AssertDeepEquals(t, prob, probs.Connection(`127.0.0.1: Fetching http://invalid.invalid/path: Invalid host in redirect target, must end in IANA registered TLD`))
  1494  
  1495  	log.Clear()
  1496  	_, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathReLookup, ka(pathReLookup))
  1497  	if err != nil {
  1498  		t.Fatalf("Unexpected error in redirect (%s): %s", pathReLookup, err)
  1499  	}
  1500  	redirectPattern := `following redirect to host "" url "http://other.valid.com:\d+/path"`
  1501  	test.AssertEquals(t, len(log.GetAllMatching(redirectPattern)), 1)
  1502  	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 1)
  1503  	test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid.com: \[127.0.0.1\]`)), 1)
  1504  
  1505  	log.Clear()
  1506  	_, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathRedirectInvalidPort, ka(pathRedirectInvalidPort))
  1507  	test.AssertNotNil(t, err, "error for pathRedirectInvalidPort should not be nil")
  1508  	prob = detailedError(err)
  1509  	test.AssertEquals(t, prob.Detail, fmt.Sprintf(
  1510  		"127.0.0.1: Fetching http://other.valid.com:8080/path: Invalid port in redirect target. "+
  1511  			"Only ports %d and %d are supported, not 8080", va.httpPort, va.httpsPort))
  1512  
  1513  	// This case will redirect from a valid host to a host that is throwing
  1514  	// HTTP 500 errors. The test case is ensuring that the connection error
  1515  	// is referencing the redirected to host, instead of the original host.
  1516  	log.Clear()
  1517  	_, err = va.validateHTTP01(ctx, identifier.NewDNS("localhost.com"), pathRedirectToFailingURL, ka(pathRedirectToFailingURL))
  1518  	test.AssertNotNil(t, err, "err should not be nil")
  1519  	prob = detailedError(err)
  1520  	test.AssertDeepEquals(t, prob,
  1521  		probs.Unauthorized(
  1522  			fmt.Sprintf("127.0.0.1: Invalid response from http://other.valid.com:%d/500: 500",
  1523  				va.httpPort)))
  1524  }
  1525  
  1526  func TestHTTPRedirectLoop(t *testing.T) {
  1527  	hs := httpSrv(t, expectedToken, false)
  1528  	defer hs.Close()
  1529  	va, _ := setup(hs, "", nil, nil)
  1530  
  1531  	_, prob := va.validateHTTP01(ctx, identifier.NewDNS("localhost"), "looper", ka("looper"))
  1532  	if prob == nil {
  1533  		t.Fatalf("Challenge should have failed for looper")
  1534  	}
  1535  }
  1536  
  1537  func TestHTTPRedirectUserAgent(t *testing.T) {
  1538  	hs := httpSrv(t, expectedToken, false)
  1539  	defer hs.Close()
  1540  	va, _ := setup(hs, "", nil, nil)
  1541  	va.userAgent = rejectUserAgent
  1542  
  1543  	_, prob := va.validateHTTP01(ctx, identifier.NewDNS("localhost"), pathMoved, ka(pathMoved))
  1544  	if prob == nil {
  1545  		t.Fatalf("Challenge with rejectUserAgent should have failed (%s).", pathMoved)
  1546  	}
  1547  
  1548  	_, prob = va.validateHTTP01(ctx, identifier.NewDNS("localhost"), pathFound, ka(pathFound))
  1549  	if prob == nil {
  1550  		t.Fatalf("Challenge with rejectUserAgent should have failed (%s).", pathFound)
  1551  	}
  1552  }
  1553  
  1554  func getPort(hs *httptest.Server) int {
  1555  	url, err := url.Parse(hs.URL)
  1556  	if err != nil {
  1557  		panic(fmt.Sprintf("Failed to parse hs URL: %q - %s", hs.URL, err.Error()))
  1558  	}
  1559  	_, portString, err := net.SplitHostPort(url.Host)
  1560  	if err != nil {
  1561  		panic(fmt.Sprintf("Failed to split hs URL host: %q - %s", url.Host, err.Error()))
  1562  	}
  1563  	port, err := strconv.ParseInt(portString, 10, 64)
  1564  	if err != nil {
  1565  		panic(fmt.Sprintf("Failed to parse hs URL port: %q - %s", portString, err.Error()))
  1566  	}
  1567  	return int(port)
  1568  }
  1569  
  1570  func TestValidateHTTP(t *testing.T) {
  1571  	token := core.NewToken()
  1572  
  1573  	hs := httpSrv(t, token, false)
  1574  	defer hs.Close()
  1575  
  1576  	va, _ := setup(hs, "", nil, nil)
  1577  
  1578  	_, prob := va.validateHTTP01(ctx, identifier.NewDNS("localhost"), token, ka(token))
  1579  	test.Assert(t, prob == nil, "validation failed")
  1580  }
  1581  
  1582  func TestLimitedReader(t *testing.T) {
  1583  	token := core.NewToken()
  1584  
  1585  	hs := httpSrv(t, "012345\xff67890123456789012345678901234567890123456789012345678901234567890123456789", false)
  1586  	va, _ := setup(hs, "", nil, nil)
  1587  	defer hs.Close()
  1588  
  1589  	_, err := va.validateHTTP01(ctx, identifier.NewDNS("localhost"), token, ka(token))
  1590  
  1591  	prob := detailedError(err)
  1592  	test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
  1593  	test.Assert(t, strings.HasPrefix(prob.Detail, "127.0.0.1: Invalid response from "),
  1594  		"Expected failure due to truncation")
  1595  
  1596  	if !utf8.ValidString(err.Error()) {
  1597  		t.Errorf("Problem Detail contained an invalid UTF-8 string")
  1598  	}
  1599  }
  1600  
  1601  type hostHeaderHandler struct {
  1602  	host string
  1603  }
  1604  
  1605  func (handler *hostHeaderHandler) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
  1606  	handler.host = req.Host
  1607  }
  1608  
  1609  // TestHTTPHostHeader tests compliance with RFC 8555, Sec. 8.3 & RFC 8738, Sec.
  1610  // 5.
  1611  func TestHTTPHostHeader(t *testing.T) {
  1612  	testCases := []struct {
  1613  		Name  string
  1614  		Ident identifier.ACMEIdentifier
  1615  		IPv6  bool
  1616  		want  string
  1617  	}{
  1618  		{
  1619  			Name:  "DNS name",
  1620  			Ident: identifier.NewDNS("example.com"),
  1621  			want:  "example.com",
  1622  		},
  1623  		{
  1624  			Name:  "IPv4 address",
  1625  			Ident: identifier.NewIP(netip.MustParseAddr("127.0.0.1")),
  1626  			want:  "127.0.0.1",
  1627  		},
  1628  		{
  1629  			Name:  "IPv6 address",
  1630  			Ident: identifier.NewIP(netip.MustParseAddr("::1")),
  1631  			IPv6:  true,
  1632  			want:  "[::1]",
  1633  		},
  1634  	}
  1635  
  1636  	for _, tc := range testCases {
  1637  		t.Run(tc.Name, func(t *testing.T) {
  1638  			ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
  1639  			defer cancel()
  1640  
  1641  			handler := hostHeaderHandler{}
  1642  			testSrv := httptest.NewUnstartedServer(&handler)
  1643  
  1644  			if tc.IPv6 {
  1645  				l, err := net.Listen("tcp", "[::1]:0")
  1646  				if err != nil {
  1647  					panic(fmt.Sprintf("httptest: failed to listen on a port: %v", err))
  1648  				}
  1649  				testSrv.Listener = l
  1650  			}
  1651  
  1652  			testSrv.Start()
  1653  			defer testSrv.Close()
  1654  
  1655  			// Setup VA. By providing the testSrv to setup the VA will use the
  1656  			// testSrv's randomly assigned port as its HTTP port.
  1657  			va, _ := setup(testSrv, "", nil, nil)
  1658  
  1659  			var got string
  1660  			_, _, _ = va.processHTTPValidation(ctx, tc.Ident, "/ok")
  1661  			got = handler.host
  1662  			if got != tc.want {
  1663  				t.Errorf("Got host %#v, but want %#v", got, tc.want)
  1664  			}
  1665  		})
  1666  	}
  1667  }