...

Source file src/github.com/letsencrypt/boulder/va/http_test.go

Documentation: github.com/letsencrypt/boulder/va

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

View as plain text