diff --git a/va/http.go b/va/http.go index 435af86e2..eac1ed3b4 100644 --- a/va/http.go +++ b/va/http.go @@ -75,14 +75,15 @@ func newHTTPClient(checkRedirect redirectChecker) http.Client { // httpValidationURL constructs a URL for the given IP address, path and port // combination. The port is omitted from the URL if it is the default HTTP -// port. It is only used for constructing initial HTTP-01 validation requests -// and so does not need to support constructing a URL with an HTTPS scheme. -func httpValidationURL(validationIP net.IP, path string, port int) *url.URL { +// port or the default HTTPS port. The protocol scheme of the URL is HTTP unless +// useHTTPS is true. UseHTTPS should only be true when constructing validation +// URLs based on a redirect from an initial HTTP validation request. +func httpValidationURL(validationIP net.IP, path string, port int, useHTTPS bool) *url.URL { urlHost := validationIP.String() - // If the port is something other than the conventional HTTP port, put it in - // the URL. - if port != 80 { + // If the port is something other than the conventional HTTP or HTTPS port, + // put it in the URL explicitly using `net.JoinHostPort`. + if port != 80 && port != 443 { urlHost = net.JoinHostPort(validationIP.String(), strconv.Itoa(port)) } @@ -90,12 +91,17 @@ func httpValidationURL(validationIP net.IP, path string, port int) *url.URL { // `net.JoinHostPort` then we have to manually surround the IPv6 address // with square brackets to make a valid IPv6 URL (e.g "http://[::1]/foo" not // "http://::1/foo") - if port == 80 && validationIP.To4() == nil { + if (port == 80 || port == 443) && validationIP.To4() == nil { urlHost = fmt.Sprintf("[%s]", urlHost) } + scheme := "http" + if useHTTPS { + scheme = "https" + } + return &url.URL{ - Scheme: "http", + Scheme: scheme, Host: urlHost, Path: path, } @@ -287,8 +293,17 @@ func (va *ValidationAuthorityImpl) setupHTTPValidation( "host %q has no IP addresses remaining to use", target.host) } + + var useHTTPS bool + // If we are mutating an existing redirected request and the original request + // URL uses HTTPS then we must construct a validation URL using HTTPS. In all + // other cases we construct an HTTP URL. + if req != nil && req.URL.Scheme == "https" { + useHTTPS = true + } + record.AddressUsed = targetIP - url := httpValidationURL(targetIP, target.path, target.port) + url := httpValidationURL(targetIP, target.path, target.port, useHTTPS) record.URL = url.String() // If there's no provided HTTP request to mutate (e.g. a redirect request diff --git a/va/http_test.go b/va/http_test.go index f337ecc54..57484e6ba 100644 --- a/va/http_test.go +++ b/va/http_test.go @@ -52,36 +52,69 @@ func TestHTTPValidationURL(t *testing.T) { IP string Path string Port int + UseHTTPS bool ExpectedURL string }{ { - Name: "IPv4 Standard port", + Name: "IPv4 Standard HTTP port", IP: "10.10.10.10", Path: egPath, Port: 80, ExpectedURL: fmt.Sprintf("http://10.10.10.10%s", egPath), }, { - Name: "IPv4 Non-standard port", + Name: "IPv4 Non-standard HTTP port", IP: "15.15.15.15", Path: egPath, Port: 8080, ExpectedURL: fmt.Sprintf("http://15.15.15.15:8080%s", egPath), }, { - Name: "IPv6 Standard port", + Name: "IPv6 Standard HTTP port", IP: "::1", Path: egPath, Port: 80, ExpectedURL: fmt.Sprintf("http://[::1]%s", egPath), }, { - Name: "IPv6 Non-standard port", + Name: "IPv6 Non-standard HTTP port", IP: "::1", Path: egPath, Port: 8080, ExpectedURL: fmt.Sprintf("http://[::1]:8080%s", egPath), }, + { + Name: "IPv4 Standard HTTPS port", + IP: "10.10.10.10", + Path: egPath, + Port: 443, + UseHTTPS: true, + ExpectedURL: fmt.Sprintf("https://10.10.10.10%s", egPath), + }, + { + Name: "IPv4 Non-standard HTTPS port", + IP: "15.15.15.15", + Path: egPath, + Port: 4443, + UseHTTPS: true, + ExpectedURL: fmt.Sprintf("https://15.15.15.15:4443%s", egPath), + }, + { + Name: "IPv6 Standard HTTPS port", + IP: "::1", + Path: egPath, + Port: 443, + UseHTTPS: true, + ExpectedURL: fmt.Sprintf("https://[::1]%s", egPath), + }, + { + Name: "IPv6 Non-standard HTTPS port", + IP: "::1", + Path: egPath, + Port: 4443, + UseHTTPS: true, + ExpectedURL: fmt.Sprintf("https://[::1]:4443%s", egPath), + }, } for _, tc := range testCases { @@ -90,7 +123,7 @@ func TestHTTPValidationURL(t *testing.T) { if ipAddr == nil { t.Fatalf("Failed to parse test case %q IP %q", tc.Name, tc.IP) } - url := httpValidationURL(ipAddr, tc.Path, tc.Port) + url := httpValidationURL(ipAddr, tc.Path, tc.Port, tc.UseHTTPS) test.AssertEquals(t, url.String(), tc.ExpectedURL) }) } @@ -281,9 +314,13 @@ func TestSetupHTTPValidation(t *testing.T) { return target } - inputURL, err := url.Parse("http://ipv4.and.ipv6.localhost/yellow/brick/road") + httpInputURL, err := url.Parse("http://ipv4.and.ipv6.localhost/yellow/brick/road") if err != nil { - t.Fatalf("Failed to construct test inputURL") + t.Fatalf("Failed to construct test httpInputURL") + } + httpsInputURL, err := url.Parse("https://ipv4.and.ipv6.localhost/yellow/brick/road") + if err != nil { + t.Fatalf("Failed to construct test httpsInputURL") } testCases := []struct { @@ -328,10 +365,10 @@ func TestSetupHTTPValidation(t *testing.T) { }, }, { - Name: "non-nil input req", + Name: "non-nil non-standard port input req", InputTarget: mustTarget(t, "ipv4.and.ipv6.localhost", 808, "/yellow/brick/road"), InputReq: &http.Request{ - URL: inputURL, + URL: httpInputURL, }, ExpectedRequestHost: "ipv4.and.ipv6.localhost", ExpectedRequestURL: "http://[::1]:808/yellow/brick/road", @@ -343,6 +380,38 @@ func TestSetupHTTPValidation(t *testing.T) { AddressUsed: net.ParseIP("::1"), }, }, + { + Name: "non-nil HTTP input req", + InputTarget: mustTarget(t, "ipv4.and.ipv6.localhost", va.httpPort, "/yellow/brick/road"), + InputReq: &http.Request{ + URL: httpInputURL, + }, + ExpectedRequestHost: "ipv4.and.ipv6.localhost", + ExpectedRequestURL: "http://[::1]/yellow/brick/road", + ExpectedRecord: core.ValidationRecord{ + Hostname: "ipv4.and.ipv6.localhost", + Port: strconv.Itoa(va.httpPort), + URL: "http://[::1]/yellow/brick/road", + AddressesResolved: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")}, + AddressUsed: net.ParseIP("::1"), + }, + }, + { + Name: "non-nil HTTPS input req", + InputTarget: mustTarget(t, "ipv4.and.ipv6.localhost", va.httpsPort, "/yellow/brick/road"), + InputReq: &http.Request{ + URL: httpsInputURL, + }, + ExpectedRequestHost: "ipv4.and.ipv6.localhost", + ExpectedRequestURL: "https://[::1]/yellow/brick/road", + ExpectedRecord: core.ValidationRecord{ + Hostname: "ipv4.and.ipv6.localhost", + Port: strconv.Itoa(va.httpsPort), + URL: "https://[::1]/yellow/brick/road", + AddressesResolved: []net.IP{net.ParseIP("::1"), net.ParseIP("127.0.0.1")}, + AddressUsed: net.ParseIP("::1"), + }, + }, } for _, tc := range testCases {