va: split unit tests from va_test.go
This commit is contained in:
parent
95ea5679ce
commit
6ff03951c3
|
@ -0,0 +1,269 @@
|
|||
package va
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/jmhodges/clock"
|
||||
"github.com/letsencrypt/boulder/bdns"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/metrics"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
func TestDNSValidationEmpty(t *testing.T) {
|
||||
va, _ := setup(nil, 0, "", nil)
|
||||
|
||||
chalDNS := createChallenge(core.ChallengeTypeDNS01)
|
||||
_, prob := va.PerformValidation(
|
||||
context.Background(),
|
||||
"empty-txts.com",
|
||||
chalDNS,
|
||||
core.Authorization{})
|
||||
test.AssertEquals(t, prob.Error(), "unauthorized :: No TXT record found at _acme-challenge.empty-txts.com")
|
||||
|
||||
samples := test.CountHistogramSamples(va.metrics.validationTime.With(prometheus.Labels{
|
||||
"type": "dns-01",
|
||||
"result": "invalid",
|
||||
"problemType": "unauthorized",
|
||||
}))
|
||||
if samples != 1 {
|
||||
t.Errorf("Wrong number of samples for invalid validation. Expected 1, got %d", samples)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSValidationWrong(t *testing.T) {
|
||||
va, _ := setup(nil, 0, "", nil)
|
||||
|
||||
chalDNS := createChallenge(core.ChallengeTypeDNS01)
|
||||
_, prob := va.PerformValidation(
|
||||
context.Background(),
|
||||
"wrong-dns01.com",
|
||||
chalDNS,
|
||||
core.Authorization{})
|
||||
if prob == nil {
|
||||
t.Fatalf("Successful DNS validation with wrong TXT record")
|
||||
}
|
||||
test.AssertEquals(t, prob.Error(), "unauthorized :: Incorrect TXT record \"a\" found at _acme-challenge.wrong-dns01.com")
|
||||
}
|
||||
|
||||
func TestDNSValidationWrongMany(t *testing.T) {
|
||||
va, _ := setup(nil, 0, "", nil)
|
||||
|
||||
chalDNS := createChallenge(core.ChallengeTypeDNS01)
|
||||
_, prob := va.PerformValidation(
|
||||
context.Background(),
|
||||
"wrong-many-dns01.com",
|
||||
chalDNS,
|
||||
core.Authorization{})
|
||||
if prob == nil {
|
||||
t.Fatalf("Successful DNS validation with wrong TXT record")
|
||||
}
|
||||
test.AssertEquals(t, prob.Error(), "unauthorized :: Incorrect TXT record \"a\" (and 4 more) found at _acme-challenge.wrong-many-dns01.com")
|
||||
}
|
||||
|
||||
func TestDNSValidationWrongLong(t *testing.T) {
|
||||
va, _ := setup(nil, 0, "", nil)
|
||||
|
||||
chalDNS := createChallenge(core.ChallengeTypeDNS01)
|
||||
_, prob := va.PerformValidation(
|
||||
context.Background(),
|
||||
"long-dns01.com",
|
||||
chalDNS,
|
||||
core.Authorization{})
|
||||
if prob == nil {
|
||||
t.Fatalf("Successful DNS validation with wrong TXT record")
|
||||
}
|
||||
test.AssertEquals(t, prob.Error(), "unauthorized :: Incorrect TXT record \"aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...\" found at _acme-challenge.long-dns01.com")
|
||||
}
|
||||
|
||||
func TestDNSValidationFailure(t *testing.T) {
|
||||
va, _ := setup(nil, 0, "", nil)
|
||||
|
||||
chalDNS := createChallenge(core.ChallengeTypeDNS01)
|
||||
|
||||
_, prob := va.validateChallenge(ctx, dnsi("localhost"), chalDNS)
|
||||
|
||||
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
|
||||
}
|
||||
|
||||
func TestDNSValidationInvalid(t *testing.T) {
|
||||
var notDNS = core.AcmeIdentifier{
|
||||
Type: core.IdentifierType("iris"),
|
||||
Value: "790DB180-A274-47A4-855F-31C428CB1072",
|
||||
}
|
||||
|
||||
chalDNS := core.DNSChallenge01("")
|
||||
chalDNS.ProvidedKeyAuthorization = expectedKeyAuthorization
|
||||
|
||||
va, _ := setup(nil, 0, "", nil)
|
||||
|
||||
_, prob := va.validateChallenge(ctx, notDNS, chalDNS)
|
||||
|
||||
test.AssertEquals(t, prob.Type, probs.MalformedProblem)
|
||||
}
|
||||
|
||||
func TestDNSValidationNotSane(t *testing.T) {
|
||||
va, _ := setup(nil, 0, "", nil)
|
||||
|
||||
chal0 := core.DNSChallenge01("")
|
||||
chal0.Token = ""
|
||||
|
||||
chal1 := core.DNSChallenge01("")
|
||||
chal1.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_"
|
||||
|
||||
chal2 := core.DNSChallenge01("")
|
||||
chal2.ProvidedKeyAuthorization = "a"
|
||||
|
||||
var authz = core.Authorization{
|
||||
ID: core.NewToken(),
|
||||
RegistrationID: 1,
|
||||
Identifier: dnsi("localhost"),
|
||||
Challenges: []core.Challenge{chal0, chal1, chal2},
|
||||
}
|
||||
|
||||
for i := 0; i < len(authz.Challenges); i++ {
|
||||
_, prob := va.validateChallenge(ctx, dnsi("localhost"), authz.Challenges[i])
|
||||
if prob.Type != probs.MalformedProblem {
|
||||
t.Errorf("Got wrong error type for %d: expected %s, got %s",
|
||||
i, prob.Type, probs.MalformedProblem)
|
||||
}
|
||||
if !strings.Contains(prob.Error(), "Challenge failed consistency check:") {
|
||||
t.Errorf("Got wrong error: %s", prob.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDNSValidationServFail(t *testing.T) {
|
||||
va, _ := setup(nil, 0, "", nil)
|
||||
|
||||
chalDNS := createChallenge(core.ChallengeTypeDNS01)
|
||||
|
||||
_, prob := va.validateChallenge(ctx, dnsi("servfail.com"), chalDNS)
|
||||
|
||||
test.AssertEquals(t, prob.Type, probs.DNSProblem)
|
||||
}
|
||||
|
||||
func TestDNSValidationNoServer(t *testing.T) {
|
||||
va, _ := setup(nil, 0, "", nil)
|
||||
va.dnsClient = bdns.NewTestDNSClientImpl(
|
||||
time.Second*5,
|
||||
nil,
|
||||
metrics.NewNoopScope(),
|
||||
clock.Default(),
|
||||
1)
|
||||
|
||||
chalDNS := createChallenge(core.ChallengeTypeDNS01)
|
||||
|
||||
_, prob := va.validateChallenge(ctx, dnsi("localhost"), chalDNS)
|
||||
|
||||
test.AssertEquals(t, prob.Type, probs.DNSProblem)
|
||||
}
|
||||
|
||||
func TestDNSValidationOK(t *testing.T) {
|
||||
va, _ := setup(nil, 0, "", nil)
|
||||
|
||||
// create a challenge with well known token
|
||||
chalDNS := core.DNSChallenge01("")
|
||||
chalDNS.Token = expectedToken
|
||||
chalDNS.ProvidedKeyAuthorization = expectedKeyAuthorization
|
||||
|
||||
_, prob := va.validateChallenge(ctx, dnsi("good-dns01.com"), chalDNS)
|
||||
|
||||
test.Assert(t, prob == nil, "Should be valid.")
|
||||
}
|
||||
|
||||
func TestDNSValidationNoAuthorityOK(t *testing.T) {
|
||||
va, _ := setup(nil, 0, "", nil)
|
||||
|
||||
// create a challenge with well known token
|
||||
chalDNS := core.DNSChallenge01("")
|
||||
chalDNS.Token = expectedToken
|
||||
|
||||
chalDNS.ProvidedKeyAuthorization = expectedKeyAuthorization
|
||||
|
||||
_, prob := va.validateChallenge(ctx, dnsi("no-authority-dns01.com"), chalDNS)
|
||||
|
||||
test.Assert(t, prob == nil, "Should be valid.")
|
||||
}
|
||||
|
||||
func TestAvailableAddresses(t *testing.T) {
|
||||
v6a := net.ParseIP("::1")
|
||||
v6b := net.ParseIP("2001:db8::2:1") // 2001:DB8 is reserved for docs (RFC 3849)
|
||||
v4a := net.ParseIP("127.0.0.1")
|
||||
v4b := net.ParseIP("192.0.2.1") // 192.0.2.0/24 is reserved for docs (RFC 5737)
|
||||
|
||||
testcases := []struct {
|
||||
input []net.IP
|
||||
v4 []net.IP
|
||||
v6 []net.IP
|
||||
}{
|
||||
// An empty validation record
|
||||
{
|
||||
[]net.IP{},
|
||||
[]net.IP{},
|
||||
[]net.IP{},
|
||||
},
|
||||
// A validation record with one IPv4 address
|
||||
{
|
||||
[]net.IP{v4a},
|
||||
[]net.IP{v4a},
|
||||
[]net.IP{},
|
||||
},
|
||||
// A dual homed record with an IPv4 and IPv6 address
|
||||
{
|
||||
[]net.IP{v4a, v6a},
|
||||
[]net.IP{v4a},
|
||||
[]net.IP{v6a},
|
||||
},
|
||||
// The same as above but with the v4/v6 order flipped
|
||||
{
|
||||
[]net.IP{v6a, v4a},
|
||||
[]net.IP{v4a},
|
||||
[]net.IP{v6a},
|
||||
},
|
||||
// A validation record with just IPv6 addresses
|
||||
{
|
||||
[]net.IP{v6a, v6b},
|
||||
[]net.IP{},
|
||||
[]net.IP{v6a, v6b},
|
||||
},
|
||||
// A validation record with interleaved IPv4/IPv6 records
|
||||
{
|
||||
[]net.IP{v6a, v4a, v6b, v4b},
|
||||
[]net.IP{v4a, v4b},
|
||||
[]net.IP{v6a, v6b},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testcases {
|
||||
// Split the input record into v4/v6 addresses
|
||||
v4result, v6result := availableAddresses(tc.input)
|
||||
|
||||
// Test that we got the right number of v4 results
|
||||
test.Assert(t, len(tc.v4) == len(v4result),
|
||||
fmt.Sprintf("Wrong # of IPv4 results: expected %d, got %d", len(tc.v4), len(v4result)))
|
||||
|
||||
// Check that all of the v4 results match expected values
|
||||
for i, v4addr := range tc.v4 {
|
||||
test.Assert(t, v4addr.String() == v4result[i].String(),
|
||||
fmt.Sprintf("Wrong v4 result index %d: expected %q got %q", i, v4addr.String(), v4result[i].String()))
|
||||
}
|
||||
|
||||
// Test that we got the right number of v6 results
|
||||
test.Assert(t, len(tc.v6) == len(v6result),
|
||||
fmt.Sprintf("Wrong # of IPv6 results: expected %d, got %d", len(tc.v6), len(v6result)))
|
||||
|
||||
// Check that all of the v6 results match expected values
|
||||
for i, v6addr := range tc.v6 {
|
||||
test.Assert(t, v6addr.String() == v6result[i].String(),
|
||||
fmt.Sprintf("Wrong v6 result index %d: expected %q got %q", i, v6addr.String(), v6result[i].String()))
|
||||
}
|
||||
}
|
||||
}
|
446
va/http_test.go
446
va/http_test.go
|
@ -5,6 +5,7 @@ import (
|
|||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
mrand "math/rand"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
|
@ -13,6 +14,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/letsencrypt/boulder/bdns"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
|
@ -843,3 +845,447 @@ func TestFetchHTTP(t *testing.T) {
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
// All paths that get assigned to tokens MUST be valid tokens
|
||||
const expectedToken = "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0"
|
||||
const expectedKeyAuthorization = "LoqXcYV8q5ONbJQxbmR7SCTNo3tiAXDfowyjxAjEuX0.9jg46WB3rR_AHD-EBXdN7cBkH1WOu0tA3M9fm21mqTI"
|
||||
const pathWrongToken = "i6lNAC4lOOLYCl-A08VJt9z_tKYvVk63Dumo8icsBjQ"
|
||||
const path404 = "404"
|
||||
const path500 = "500"
|
||||
const pathFound = "GBq8SwWq3JsbREFdCamk5IX3KLsxW5ULeGs98Ajl_UM"
|
||||
const pathMoved = "5J4FIMrWNfmvHZo-QpKZngmuhqZGwRm21-oEgUDstJM"
|
||||
const pathRedirectInvalidPort = "port-redirect"
|
||||
const pathWait = "wait"
|
||||
const pathWaitLong = "wait-long"
|
||||
const pathReLookup = "7e-P57coLM7D3woNTp_xbJrtlkDYy6PWf3mSSbLwCr4"
|
||||
const pathReLookupInvalid = "re-lookup-invalid"
|
||||
const pathRedirectToFailingURL = "re-to-failing-url"
|
||||
const pathLooper = "looper"
|
||||
const pathValid = "valid"
|
||||
const rejectUserAgent = "rejectMe"
|
||||
|
||||
func httpSrv(t *testing.T, token string) *httptest.Server {
|
||||
m := http.NewServeMux()
|
||||
|
||||
server := httptest.NewUnstartedServer(m)
|
||||
|
||||
defaultToken := token
|
||||
currentToken := defaultToken
|
||||
|
||||
m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
if strings.HasSuffix(r.URL.Path, path404) {
|
||||
t.Logf("HTTPSRV: Got a 404 req\n")
|
||||
http.NotFound(w, r)
|
||||
} else if strings.HasSuffix(r.URL.Path, path500) {
|
||||
t.Logf("HTTPSRV: Got a 500 req\n")
|
||||
http.Error(w, "Internal Server Error", http.StatusInternalServerError)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathMoved) {
|
||||
t.Logf("HTTPSRV: Got a 301 redirect req\n")
|
||||
if currentToken == defaultToken {
|
||||
currentToken = pathMoved
|
||||
}
|
||||
http.Redirect(w, r, pathValid, 301)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathFound) {
|
||||
t.Logf("HTTPSRV: Got a 302 redirect req\n")
|
||||
if currentToken == defaultToken {
|
||||
currentToken = pathFound
|
||||
}
|
||||
http.Redirect(w, r, pathMoved, 302)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathWait) {
|
||||
t.Logf("HTTPSRV: Got a wait req\n")
|
||||
time.Sleep(time.Second * 3)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathWaitLong) {
|
||||
t.Logf("HTTPSRV: Got a wait-long req\n")
|
||||
time.Sleep(time.Second * 10)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathReLookup) {
|
||||
t.Logf("HTTPSRV: Got a redirect req to a valid hostname\n")
|
||||
if currentToken == defaultToken {
|
||||
currentToken = pathReLookup
|
||||
}
|
||||
port := getPort(server)
|
||||
http.Redirect(w, r, fmt.Sprintf("http://other.valid.com:%d/path", port), 302)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathReLookupInvalid) {
|
||||
t.Logf("HTTPSRV: Got a redirect req to an invalid hostname\n")
|
||||
http.Redirect(w, r, "http://invalid.invalid/path", 302)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathRedirectToFailingURL) {
|
||||
t.Logf("HTTPSRV: Redirecting to a URL that will fail\n")
|
||||
port := getPort(server)
|
||||
http.Redirect(w, r, fmt.Sprintf("http://other.valid.com:%d/%s", port, path500), 301)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathLooper) {
|
||||
t.Logf("HTTPSRV: Got a loop req\n")
|
||||
http.Redirect(w, r, r.URL.String(), 301)
|
||||
} else if strings.HasSuffix(r.URL.Path, pathRedirectInvalidPort) {
|
||||
t.Logf("HTTPSRV: Got a port redirect req\n")
|
||||
// Port 8080 is not the VA's httpPort or httpsPort and should be rejected
|
||||
http.Redirect(w, r, "http://other.valid.com:8080/path", 302)
|
||||
} else if r.Header.Get("User-Agent") == rejectUserAgent {
|
||||
w.WriteHeader(http.StatusBadRequest)
|
||||
w.Write([]byte("found trap User-Agent"))
|
||||
} else {
|
||||
t.Logf("HTTPSRV: Got a valid req\n")
|
||||
t.Logf("HTTPSRV: Path = %s\n", r.URL.Path)
|
||||
|
||||
ch := core.Challenge{Token: currentToken}
|
||||
keyAuthz, _ := ch.ExpectedKeyAuthorization(accountKey)
|
||||
t.Logf("HTTPSRV: Key Authz = '%s%s'\n", keyAuthz, "\\n\\r \\t")
|
||||
|
||||
fmt.Fprint(w, keyAuthz, "\n\r \t")
|
||||
currentToken = defaultToken
|
||||
}
|
||||
})
|
||||
|
||||
server.Start()
|
||||
return server
|
||||
}
|
||||
|
||||
func TestHTTPBadPort(t *testing.T) {
|
||||
chall := core.HTTPChallenge01("")
|
||||
setChallengeToken(&chall, expectedToken)
|
||||
|
||||
hs := httpSrv(t, chall.Token)
|
||||
defer hs.Close()
|
||||
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
|
||||
// Pick a random port between 40000 and 65000 - with great certainty we won't
|
||||
// have an HTTP server listening on this port and the test will fail as
|
||||
// intended
|
||||
badPort := 40000 + mrand.Intn(25000)
|
||||
va.httpPort = badPort
|
||||
|
||||
_, prob := va.validateHTTP01(ctx, dnsi("localhost"), chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("Server's down; expected refusal. Where did we connect?")
|
||||
}
|
||||
test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
|
||||
if !strings.Contains(prob.Detail, "Connection refused") {
|
||||
t.Errorf("Expected a connection refused error, got %q", prob.Detail)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTP(t *testing.T) {
|
||||
chall := core.HTTPChallenge01("")
|
||||
setChallengeToken(&chall, expectedToken)
|
||||
|
||||
// NOTE: We do not attempt to shut down the server. The problem is that the
|
||||
// "wait-long" handler sleeps for ten seconds, but this test finishes in less
|
||||
// than that. So if we try to call hs.Close() at the end of the test, we'll be
|
||||
// closing the test server while a request is still pending. Unfortunately,
|
||||
// there appears to be an issue in httptest that trips Go's race detector when
|
||||
// that happens, failing the test. So instead, we live with leaving the server
|
||||
// around till the process exits.
|
||||
// TODO(#1989): close hs
|
||||
hs := httpSrv(t, chall.Token)
|
||||
|
||||
va, log := setup(hs, 0, "", nil)
|
||||
|
||||
log.Clear()
|
||||
t.Logf("Trying to validate: %+v\n", chall)
|
||||
_, prob := va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
|
||||
if prob != nil {
|
||||
t.Errorf("Unexpected failure in HTTP validation: %s", prob)
|
||||
}
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1)
|
||||
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, path404)
|
||||
_, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("Should have found a 404 for the challenge.")
|
||||
}
|
||||
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1)
|
||||
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, pathWrongToken)
|
||||
// The "wrong token" will actually be the expectedToken. It's wrong
|
||||
// because it doesn't match pathWrongToken.
|
||||
_, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("Should have found the wrong token value.")
|
||||
}
|
||||
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`\[AUDIT\] `)), 1)
|
||||
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, pathMoved)
|
||||
_, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
|
||||
if prob != nil {
|
||||
t.Fatalf("Failed to follow 301 redirect")
|
||||
}
|
||||
redirectValid := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathValid + `"`
|
||||
matchedValidRedirect := log.GetAllMatching(redirectValid)
|
||||
test.AssertEquals(t, len(matchedValidRedirect), 1)
|
||||
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, pathFound)
|
||||
_, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
|
||||
if prob != nil {
|
||||
t.Fatalf("Failed to follow 302 redirect")
|
||||
}
|
||||
redirectMoved := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathMoved + `"`
|
||||
matchedMovedRedirect := log.GetAllMatching(redirectMoved)
|
||||
test.AssertEquals(t, len(matchedValidRedirect), 1)
|
||||
test.AssertEquals(t, len(matchedMovedRedirect), 1)
|
||||
|
||||
ipIdentifier := core.AcmeIdentifier{Type: core.IdentifierType("ip"), Value: "127.0.0.1"}
|
||||
_, prob = va.validateHTTP01(ctx, ipIdentifier, chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("IdentifierType IP shouldn't have worked.")
|
||||
}
|
||||
test.AssertEquals(t, prob.Type, probs.MalformedProblem)
|
||||
|
||||
_, prob = va.validateHTTP01(ctx, core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("Domain name is invalid.")
|
||||
}
|
||||
test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
|
||||
}
|
||||
|
||||
func TestHTTPTimeout(t *testing.T) {
|
||||
chall := core.HTTPChallenge01("")
|
||||
setChallengeToken(&chall, expectedToken)
|
||||
|
||||
hs := httpSrv(t, chall.Token)
|
||||
// TODO(#1989): close hs
|
||||
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
setChallengeToken(&chall, pathWaitLong)
|
||||
|
||||
expectMatch := regexp.MustCompile(
|
||||
"Fetching http://localhost/.well-known/acme-challenge/wait-long: Timeout after connect")
|
||||
|
||||
started := time.Now()
|
||||
timeout := 250 * time.Millisecond
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
_, prob := va.validateHTTP01(ctx, dnsi("localhost"), chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("Connection should've timed out")
|
||||
}
|
||||
|
||||
took := time.Since(started)
|
||||
// Check that the HTTP connection doesn't return before a timeout, and times
|
||||
// out after the expected time
|
||||
if took < timeout-200*time.Millisecond {
|
||||
t.Fatalf("HTTP timed out before %s: %s with %s", timeout, took, prob)
|
||||
}
|
||||
if took > 2*timeout {
|
||||
t.Fatalf("HTTP connection didn't timeout after %s", timeout)
|
||||
}
|
||||
test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
|
||||
|
||||
if !expectMatch.MatchString(prob.Detail) {
|
||||
t.Errorf("Problem details incorrect. Got %q, expected to match %q",
|
||||
prob.Detail, expectMatch)
|
||||
}
|
||||
}
|
||||
|
||||
// dnsMockReturnsUnroutable is a DNSClient mock that always returns an
|
||||
// unroutable address for LookupHost. This is useful in testing connect
|
||||
// timeouts.
|
||||
type dnsMockReturnsUnroutable struct {
|
||||
*bdns.MockDNSClient
|
||||
}
|
||||
|
||||
func (mock dnsMockReturnsUnroutable) LookupHost(_ context.Context, hostname string) ([]net.IP, error) {
|
||||
return []net.IP{net.ParseIP("198.51.100.1")}, nil
|
||||
}
|
||||
|
||||
// TestHTTPDialTimeout tests that we give the proper "Timeout during connect"
|
||||
// error when dial fails. We do this by using a mock DNS client that resolves
|
||||
// everything to an unroutable IP address.
|
||||
func TestHTTPDialTimeout(t *testing.T) {
|
||||
va, _ := setup(nil, 0, "", nil)
|
||||
|
||||
started := time.Now()
|
||||
timeout := 250 * time.Millisecond
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
va.dnsClient = dnsMockReturnsUnroutable{&bdns.MockDNSClient{}}
|
||||
// The only method I've found so far to trigger a connect timeout is to
|
||||
// connect to an unrouteable IP address. This usuall generates a connection
|
||||
// timeout, but will rarely return "Network unreachable" instead. If we get
|
||||
// that, just retry until we get something other than "Network unreachable".
|
||||
var prob *probs.ProblemDetails
|
||||
for i := 0; i < 20; i++ {
|
||||
_, prob = va.validateHTTP01(ctx, dnsi("unroutable.invalid"), core.HTTPChallenge01(""))
|
||||
if prob != nil && strings.Contains(prob.Detail, "Network unreachable") {
|
||||
continue
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if prob == nil {
|
||||
t.Fatalf("Connection should've timed out")
|
||||
}
|
||||
took := time.Since(started)
|
||||
// Check that the HTTP connection doesn't return too fast, and times
|
||||
// out after the expected time
|
||||
if took < (timeout-200*time.Millisecond)/2 {
|
||||
t.Fatalf("HTTP returned before %s (%s) with %#v", timeout, took, prob)
|
||||
}
|
||||
if took > 2*timeout {
|
||||
t.Fatalf("HTTP connection didn't timeout after %s seconds", timeout)
|
||||
}
|
||||
test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
|
||||
expectMatch := regexp.MustCompile(
|
||||
"Fetching http://unroutable.invalid/.well-known/acme-challenge/.*: Timeout during connect")
|
||||
if !expectMatch.MatchString(prob.Detail) {
|
||||
t.Errorf("Problem details incorrect. Got %q, expected to match %q",
|
||||
prob.Detail, expectMatch)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPRedirectLookup(t *testing.T) {
|
||||
chall := core.HTTPChallenge01("")
|
||||
setChallengeToken(&chall, expectedToken)
|
||||
|
||||
hs := httpSrv(t, expectedToken)
|
||||
defer hs.Close()
|
||||
va, log := setup(hs, 0, "", nil)
|
||||
|
||||
setChallengeToken(&chall, pathMoved)
|
||||
_, prob := va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
|
||||
if prob != nil {
|
||||
t.Fatalf("Unexpected failure in redirect (%s): %s", pathMoved, prob)
|
||||
}
|
||||
redirectValid := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathValid + `"`
|
||||
matchedValidRedirect := log.GetAllMatching(redirectValid)
|
||||
test.AssertEquals(t, len(matchedValidRedirect), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 2)
|
||||
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, pathFound)
|
||||
_, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
|
||||
if prob != nil {
|
||||
t.Fatalf("Unexpected failure in redirect (%s): %s", pathFound, prob)
|
||||
}
|
||||
redirectMoved := `following redirect to host "" url "http://localhost.com/.well-known/acme-challenge/` + pathMoved + `"`
|
||||
matchedMovedRedirect := log.GetAllMatching(redirectMoved)
|
||||
test.AssertEquals(t, len(matchedMovedRedirect), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 3)
|
||||
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, pathReLookupInvalid)
|
||||
_, err := va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
|
||||
test.AssertError(t, err, chall.Token)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 1)
|
||||
test.AssertDeepEquals(t, err, probs.ConnectionFailure("Fetching http://invalid.invalid/path: Invalid hostname in redirect target, must end in IANA registered TLD"))
|
||||
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, pathReLookup)
|
||||
_, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
|
||||
if prob != nil {
|
||||
t.Fatalf("Unexpected error in redirect (%s): %s", pathReLookup, prob)
|
||||
}
|
||||
redirectPattern := `following redirect to host "" url "http://other.valid.com:\d+/path"`
|
||||
test.AssertEquals(t, len(log.GetAllMatching(redirectPattern)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost.com: \[127.0.0.1\]`)), 1)
|
||||
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid.com: \[127.0.0.1\]`)), 1)
|
||||
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, pathRedirectInvalidPort)
|
||||
_, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
|
||||
test.AssertNotNil(t, prob, "Problem details for pathRedirectInvalidPort should not be nil")
|
||||
test.AssertEquals(t, prob.Detail, fmt.Sprintf(
|
||||
"Fetching http://other.valid.com:8080/path: Invalid port in redirect target. "+
|
||||
"Only ports %d and %d are supported, not 8080", va.httpPort, va.httpsPort))
|
||||
|
||||
// This case will redirect from a valid host to a host that is throwing
|
||||
// HTTP 500 errors. The test case is ensuring that the connection error
|
||||
// is referencing the redirected to host, instead of the original host.
|
||||
log.Clear()
|
||||
setChallengeToken(&chall, pathRedirectToFailingURL)
|
||||
_, prob = va.validateHTTP01(ctx, dnsi("localhost.com"), chall)
|
||||
test.AssertNotNil(t, prob, "Problem Details should not be nil")
|
||||
test.AssertDeepEquals(t, prob,
|
||||
probs.Unauthorized(
|
||||
fmt.Sprintf("Invalid response from http://other.valid.com:%d/500 [127.0.0.1]: 500",
|
||||
va.httpPort)))
|
||||
}
|
||||
|
||||
func TestHTTPRedirectLoop(t *testing.T) {
|
||||
chall := core.HTTPChallenge01("")
|
||||
setChallengeToken(&chall, "looper")
|
||||
|
||||
hs := httpSrv(t, expectedToken)
|
||||
defer hs.Close()
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
|
||||
_, prob := va.validateHTTP01(ctx, dnsi("localhost"), chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("Challenge should have failed for %s", chall.Token)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHTTPRedirectUserAgent(t *testing.T) {
|
||||
chall := core.HTTPChallenge01("")
|
||||
setChallengeToken(&chall, expectedToken)
|
||||
|
||||
hs := httpSrv(t, expectedToken)
|
||||
defer hs.Close()
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
va.userAgent = rejectUserAgent
|
||||
|
||||
setChallengeToken(&chall, pathMoved)
|
||||
_, prob := va.validateHTTP01(ctx, dnsi("localhost"), chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("Challenge with rejectUserAgent should have failed (%s).", pathMoved)
|
||||
}
|
||||
|
||||
setChallengeToken(&chall, pathFound)
|
||||
_, prob = va.validateHTTP01(ctx, dnsi("localhost"), chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("Challenge with rejectUserAgent should have failed (%s).", pathFound)
|
||||
}
|
||||
}
|
||||
|
||||
func getPort(hs *httptest.Server) int {
|
||||
url, err := url.Parse(hs.URL)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to parse hs URL: %q - %s", hs.URL, err.Error()))
|
||||
}
|
||||
_, portString, err := net.SplitHostPort(url.Host)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to split hs URL host: %q - %s", url.Host, err.Error()))
|
||||
}
|
||||
port, err := strconv.ParseInt(portString, 10, 64)
|
||||
if err != nil {
|
||||
panic(fmt.Sprintf("Failed to parse hs URL port: %q - %s", portString, err.Error()))
|
||||
}
|
||||
return int(port)
|
||||
}
|
||||
|
||||
func TestValidateHTTP(t *testing.T) {
|
||||
chall := core.HTTPChallenge01("")
|
||||
setChallengeToken(&chall, core.NewToken())
|
||||
|
||||
hs := httpSrv(t, chall.Token)
|
||||
defer hs.Close()
|
||||
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
|
||||
_, prob := va.validateChallenge(ctx, dnsi("localhost"), chall)
|
||||
test.Assert(t, prob == nil, "validation failed")
|
||||
}
|
||||
|
||||
func TestLimitedReader(t *testing.T) {
|
||||
chall := core.HTTPChallenge01("")
|
||||
setChallengeToken(&chall, core.NewToken())
|
||||
|
||||
hs := httpSrv(t, "012345\xff67890123456789012345678901234567890123456789012345678901234567890123456789")
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
defer hs.Close()
|
||||
|
||||
_, prob := va.validateChallenge(ctx, dnsi("localhost"), chall)
|
||||
|
||||
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
|
||||
test.Assert(t, strings.HasPrefix(prob.Detail, "Invalid response from "),
|
||||
"Expected failure due to truncation")
|
||||
|
||||
if !utf8.ValidString(prob.Detail) {
|
||||
t.Errorf("Problem Detail contained an invalid UTF-8 string")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,415 @@
|
|||
package va
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/rand"
|
||||
"crypto/sha256"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"crypto/x509/pkix"
|
||||
"encoding/asn1"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"net"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/letsencrypt/boulder/bdns"
|
||||
"github.com/letsencrypt/boulder/core"
|
||||
"github.com/letsencrypt/boulder/probs"
|
||||
"github.com/letsencrypt/boulder/test"
|
||||
)
|
||||
|
||||
func tlsCertTemplate(names []string) *x509.Certificate {
|
||||
return &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1337),
|
||||
Subject: pkix.Name{
|
||||
Organization: []string{"tests"},
|
||||
},
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(0, 0, 1),
|
||||
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
|
||||
DNSNames: names,
|
||||
}
|
||||
}
|
||||
|
||||
func makeACert(names []string) *tls.Certificate {
|
||||
template := tlsCertTemplate(names)
|
||||
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
|
||||
return &tls.Certificate{
|
||||
Certificate: [][]byte{certBytes},
|
||||
PrivateKey: &TheKey,
|
||||
}
|
||||
}
|
||||
|
||||
// tlssniSrvWithNames is kept around for the use of TestValidateTLSALPN01UnawareSrv
|
||||
func tlssniSrvWithNames(t *testing.T, chall core.Challenge, names ...string) *httptest.Server {
|
||||
cert := makeACert(names)
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{*cert},
|
||||
ClientAuth: tls.NoClientCert,
|
||||
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return cert, nil
|
||||
},
|
||||
NextProtos: []string{"http/1.1"},
|
||||
}
|
||||
|
||||
hs := httptest.NewUnstartedServer(http.DefaultServeMux)
|
||||
hs.TLS = tlsConfig
|
||||
hs.StartTLS()
|
||||
return hs
|
||||
}
|
||||
|
||||
func tlsalpn01Srv(t *testing.T, chall core.Challenge, oid asn1.ObjectIdentifier, names ...string) *httptest.Server {
|
||||
template := tlsCertTemplate(names)
|
||||
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
|
||||
cert := &tls.Certificate{
|
||||
Certificate: [][]byte{certBytes},
|
||||
PrivateKey: &TheKey,
|
||||
}
|
||||
|
||||
shasum := sha256.Sum256([]byte(chall.ProvidedKeyAuthorization))
|
||||
encHash, _ := asn1.Marshal(shasum[:])
|
||||
acmeExtension := pkix.Extension{
|
||||
Id: oid,
|
||||
Critical: true,
|
||||
Value: encHash,
|
||||
}
|
||||
|
||||
template.ExtraExtensions = []pkix.Extension{acmeExtension}
|
||||
certBytes, _ = x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
|
||||
acmeCert := &tls.Certificate{
|
||||
Certificate: [][]byte{certBytes},
|
||||
PrivateKey: &TheKey,
|
||||
}
|
||||
|
||||
tlsConfig := &tls.Config{
|
||||
Certificates: []tls.Certificate{},
|
||||
ClientAuth: tls.NoClientCert,
|
||||
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
if clientHello.ServerName != names[0] {
|
||||
return nil, nil
|
||||
}
|
||||
if len(clientHello.SupportedProtos) == 1 && clientHello.SupportedProtos[0] == ACMETLS1Protocol {
|
||||
return acmeCert, nil
|
||||
}
|
||||
return cert, nil
|
||||
},
|
||||
NextProtos: []string{"http/1.1", ACMETLS1Protocol},
|
||||
}
|
||||
|
||||
hs := httptest.NewUnstartedServer(http.DefaultServeMux)
|
||||
hs.TLS = tlsConfig
|
||||
hs.Config.TLSNextProto = map[string]func(*http.Server, *tls.Conn, http.Handler){
|
||||
ACMETLS1Protocol: func(_ *http.Server, conn *tls.Conn, _ http.Handler) {
|
||||
_ = conn.Close()
|
||||
},
|
||||
}
|
||||
hs.StartTLS()
|
||||
return hs
|
||||
}
|
||||
|
||||
func TestTLSALPN01FailIP(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeTLSALPN01)
|
||||
hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, "localhost")
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
|
||||
port := getPort(hs)
|
||||
_, prob := va.validateTLSALPN01(ctx, core.AcmeIdentifier{
|
||||
Type: core.IdentifierType("ip"),
|
||||
Value: net.JoinHostPort("127.0.0.1", strconv.Itoa(port)),
|
||||
}, chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("IdentifierType IP shouldn't have worked.")
|
||||
}
|
||||
test.AssertEquals(t, prob.Type, probs.MalformedProblem)
|
||||
}
|
||||
|
||||
func slowTLSSrv() *httptest.Server {
|
||||
server := httptest.NewUnstartedServer(http.DefaultServeMux)
|
||||
server.TLS = &tls.Config{
|
||||
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
time.Sleep(100 * time.Millisecond)
|
||||
return makeACert([]string{"nomatter"}), nil
|
||||
},
|
||||
}
|
||||
server.StartTLS()
|
||||
return server
|
||||
}
|
||||
|
||||
func TestTLSALPNTimeoutAfterConnect(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeTLSALPN01)
|
||||
hs := slowTLSSrv()
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
|
||||
timeout := 50 * time.Millisecond
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
started := time.Now()
|
||||
_, prob := va.validateTLSALPN01(ctx, dnsi("slow.server"), chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("Validation should've failed")
|
||||
}
|
||||
// Check that the TLS connection doesn't return before a timeout, and times
|
||||
// out after the expected time
|
||||
took := time.Since(started)
|
||||
// Check that the HTTP connection doesn't return too fast, and times
|
||||
// out after the expected time
|
||||
if took < timeout/2 {
|
||||
t.Fatalf("TLSSNI returned before %s (%s) with %#v", timeout, took, prob)
|
||||
}
|
||||
if took > 2*timeout {
|
||||
t.Fatalf("TLSSNI didn't timeout after %s (took %s to return %#v)", timeout,
|
||||
took, prob)
|
||||
}
|
||||
if prob == nil {
|
||||
t.Fatalf("Connection should've timed out")
|
||||
}
|
||||
test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
|
||||
expected := "Timeout during read (your server may be slow or overloaded)"
|
||||
if prob.Detail != expected {
|
||||
t.Errorf("Wrong error detail. Expected %q, got %q", expected, prob.Detail)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSALPN01DialTimeout(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeTLSALPN01)
|
||||
hs := slowTLSSrv()
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
va.dnsClient = dnsMockReturnsUnroutable{&bdns.MockDNSClient{}}
|
||||
started := time.Now()
|
||||
|
||||
timeout := 50 * time.Millisecond
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
// The only method I've found so far to trigger a connect timeout is to
|
||||
// connect to an unrouteable IP address. This usuall generates a connection
|
||||
// timeout, but will rarely return "Network unreachable" instead. If we get
|
||||
// that, just retry until we get something other than "Network unreachable".
|
||||
var prob *probs.ProblemDetails
|
||||
for i := 0; i < 20; i++ {
|
||||
_, prob = va.validateTLSALPN01(ctx, dnsi("unroutable.invalid"), chall)
|
||||
if prob != nil && strings.Contains(prob.Detail, "Network unreachable") {
|
||||
continue
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if prob == nil {
|
||||
t.Fatalf("Validation should've failed")
|
||||
}
|
||||
// Check that the TLS connection doesn't return before a timeout, and times
|
||||
// out after the expected time
|
||||
took := time.Since(started)
|
||||
// Check that the HTTP connection doesn't return too fast, and times
|
||||
// out after the expected time
|
||||
if took < timeout/2 {
|
||||
t.Fatalf("TLSSNI returned before %s (%s) with %#v", timeout, took, prob)
|
||||
}
|
||||
if took > 2*timeout {
|
||||
t.Fatalf("TLSSNI didn't timeout after %s", timeout)
|
||||
}
|
||||
if prob == nil {
|
||||
t.Fatalf("Connection should've timed out")
|
||||
}
|
||||
test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
|
||||
expected := "Timeout during connect (likely firewall problem)"
|
||||
if prob.Detail != expected {
|
||||
t.Errorf("Wrong error detail. Expected %q, got %q", expected, prob.Detail)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSALPN01Refused(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeTLSALPN01)
|
||||
hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, "localhost")
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
// Take down validation server and check that validation fails.
|
||||
hs.Close()
|
||||
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("Server's down; expected refusal. Where did we connect?")
|
||||
}
|
||||
test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
|
||||
expected := "Connection refused"
|
||||
if prob.Detail != expected {
|
||||
t.Errorf("Wrong error detail. Expected %q, got %q", expected, prob.Detail)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTLSALPN01TalkingToHTTP(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeTLSALPN01)
|
||||
hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, "localhost")
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
httpOnly := httpSrv(t, "")
|
||||
va.tlsPort = getPort(httpOnly)
|
||||
|
||||
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
|
||||
test.AssertError(t, prob, "TLS-SNI-01 validation passed when talking to a HTTP-only server")
|
||||
expected := "Server only speaks HTTP, not TLS"
|
||||
if !strings.HasSuffix(prob.Detail, expected) {
|
||||
t.Errorf("Got wrong error detail. Expected %q, got %q", expected, prob.Detail)
|
||||
}
|
||||
}
|
||||
|
||||
func brokenTLSSrv() *httptest.Server {
|
||||
server := httptest.NewUnstartedServer(http.DefaultServeMux)
|
||||
server.TLS = &tls.Config{
|
||||
GetCertificate: func(*tls.ClientHelloInfo) (*tls.Certificate, error) {
|
||||
return nil, fmt.Errorf("Failing on purpose")
|
||||
},
|
||||
}
|
||||
server.StartTLS()
|
||||
return server
|
||||
}
|
||||
|
||||
func TestTLSError(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeTLSALPN01)
|
||||
hs := brokenTLSSrv()
|
||||
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
|
||||
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("TLS validation should have failed: What cert was used?")
|
||||
}
|
||||
if prob.Type != probs.TLSProblem {
|
||||
t.Errorf("Wrong problem type: got %s, expected type %s",
|
||||
prob, probs.TLSProblem)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCertNames(t *testing.T) {
|
||||
// We duplicate names inside the SAN set
|
||||
names := []string{
|
||||
"hello.world", "goodbye.world",
|
||||
"hello.world", "goodbye.world",
|
||||
"bonjour.le.monde", "au.revoir.le.monde",
|
||||
"bonjour.le.monde", "au.revoir.le.monde",
|
||||
"f\xffoo", "f\xffoo",
|
||||
}
|
||||
// We expect only unique names, in sorted order and with any invalid utf-8
|
||||
// replaced.
|
||||
expected := []string{
|
||||
"au.revoir.le.monde", "bonjour.le.monde",
|
||||
"f\ufffdoo", "goodbye.world", "hello.world",
|
||||
}
|
||||
template := &x509.Certificate{
|
||||
SerialNumber: big.NewInt(1337),
|
||||
NotBefore: time.Now(),
|
||||
NotAfter: time.Now().AddDate(0, 0, 1),
|
||||
KeyUsage: x509.KeyUsageDigitalSignature | x509.KeyUsageCertSign,
|
||||
ExtKeyUsage: []x509.ExtKeyUsage{x509.ExtKeyUsageServerAuth},
|
||||
BasicConstraintsValid: true,
|
||||
|
||||
Subject: pkix.Name{
|
||||
// We also duplicate a name from the SANs as the CN
|
||||
CommonName: names[0],
|
||||
},
|
||||
DNSNames: names,
|
||||
}
|
||||
|
||||
// Create the certificate, check that certNames provides the expected result
|
||||
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
|
||||
cert, _ := x509.ParseCertificate(certBytes)
|
||||
actual := certNames(cert)
|
||||
test.AssertDeepEquals(t, actual, expected)
|
||||
}
|
||||
|
||||
func TestTLSALPN01Success(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeTLSALPN01)
|
||||
hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, "localhost")
|
||||
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
|
||||
_, prob := va.validateChallenge(ctx, dnsi("localhost"), chall)
|
||||
if prob != nil {
|
||||
t.Errorf("Validation failed: %v", prob)
|
||||
}
|
||||
test.AssertEquals(t, test.CountCounterVec("oid", IdPeAcmeIdentifier.String(), va.metrics.tlsALPNOIDCounter), 1)
|
||||
|
||||
hs.Close()
|
||||
chall = createChallenge(core.ChallengeTypeTLSALPN01)
|
||||
hs = tlsalpn01Srv(t, chall, IdPeAcmeIdentifierV1Obsolete, "localhost")
|
||||
|
||||
va, _ = setup(hs, 0, "", nil)
|
||||
|
||||
_, prob = va.validateChallenge(ctx, dnsi("localhost"), chall)
|
||||
if prob != nil {
|
||||
t.Errorf("Validation failed: %v", prob)
|
||||
}
|
||||
test.AssertEquals(t, test.CountCounterVec("oid", IdPeAcmeIdentifierV1Obsolete.String(), va.metrics.tlsALPNOIDCounter), 1)
|
||||
}
|
||||
|
||||
func TestValidateTLSALPN01BadChallenge(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeTLSALPN01)
|
||||
chall2 := chall
|
||||
setChallengeToken(&chall2, "bad token")
|
||||
|
||||
hs := tlsalpn01Srv(t, chall2, IdPeAcmeIdentifier, "localhost")
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
|
||||
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
|
||||
|
||||
if prob == nil {
|
||||
t.Fatalf("TLS ALPN validation should have failed.")
|
||||
}
|
||||
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
|
||||
}
|
||||
|
||||
func TestValidateTLSALPN01BrokenSrv(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeTLSALPN01)
|
||||
hs := brokenTLSSrv()
|
||||
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
|
||||
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("TLS ALPN validation should have failed.")
|
||||
}
|
||||
test.AssertEquals(t, prob.Type, probs.TLSProblem)
|
||||
}
|
||||
|
||||
func TestValidateTLSALPN01UnawareSrv(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeTLSALPN01)
|
||||
hs := tlssniSrvWithNames(t, chall, "localhost")
|
||||
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
|
||||
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("TLS ALPN validation should have failed.")
|
||||
}
|
||||
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
|
||||
}
|
||||
|
||||
// TestValidateTLSALPN01BadUTFSrv tests that validating TLS-ALPN-01 against
|
||||
// a host that returns a certificate with a SAN/CN that contains invalid UTF-8
|
||||
// will result in a problem with the invalid UTF-8 replaced.
|
||||
func TestValidateTLSALPN01BadUTFSrv(t *testing.T) {
|
||||
chall := createChallenge(core.ChallengeTypeTLSALPN01)
|
||||
hs := tlsalpn01Srv(t, chall, IdPeAcmeIdentifier, "localhost", "\xf0\x28\x8c\xbc")
|
||||
port := getPort(hs)
|
||||
va, _ := setup(hs, 0, "", nil)
|
||||
|
||||
_, prob := va.validateTLSALPN01(ctx, dnsi("localhost"), chall)
|
||||
if prob == nil {
|
||||
t.Fatalf("TLS ALPN validation should have failed.")
|
||||
}
|
||||
test.AssertEquals(t, prob.Type, probs.UnauthorizedProblem)
|
||||
test.AssertEquals(t, prob.Detail, fmt.Sprintf(
|
||||
"Incorrect validation certificate for tls-alpn-01 challenge. "+
|
||||
"Requested localhost from 127.0.0.1:%d. Received 1 certificate(s), "+
|
||||
`first certificate had names "localhost, %s"`,
|
||||
port, "\ufffd(\ufffd\ufffd"))
|
||||
}
|
1096
va/va_test.go
1096
va/va_test.go
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue