Refactor validation return types

Instead of returning a modified challenge, the validate methods now return a
slice of ValidationRecords and a ProblemDetails. These can then be assigned to
the relevant part of the challenge, and the challenge's status updated, in a
single place. This should help avoid errors where local modifications are made
to a challenge and then not returned.
This commit is contained in:
Jacob Hoffman-Andrews 2015-12-14 17:20:32 -08:00
parent a63a88f7a7
commit dc9eb37534
2 changed files with 179 additions and 205 deletions

View File

@ -140,8 +140,7 @@ func (va *ValidationAuthorityImpl) resolveAndConstructDialer(name string, port i
// Validation methods
func (va *ValidationAuthorityImpl) fetchHTTP(identifier core.AcmeIdentifier, path string, useTLS bool, input core.Challenge) ([]byte, core.Challenge, error) {
emptyBody := []byte{}
func (va *ValidationAuthorityImpl) fetchHTTP(identifier core.AcmeIdentifier, path string, useTLS bool, input core.Challenge) ([]byte, []core.ValidationRecord, *probs.ProblemDetails) {
challenge := input
host := identifier.Value
@ -168,13 +167,11 @@ func (va *ValidationAuthorityImpl) fetchHTTP(identifier core.AcmeIdentifier, pat
va.log.Audit(fmt.Sprintf("Attempting to validate %s for %s", challenge.Type, url))
httpRequest, err := http.NewRequest("GET", url.String(), nil)
if err != nil {
challenge.Error = &probs.ProblemDetails{
va.log.Debug(fmt.Sprintf("%s [%s] HTTP failure: %s", challenge.Type, identifier, err))
return nil, nil, &probs.ProblemDetails{
Type: probs.MalformedProblem,
Detail: "URL provided for HTTP was invalid",
}
va.log.Debug(fmt.Sprintf("%s [%s] HTTP failure: %s", challenge.Type, identifier, err))
challenge.Status = core.StatusInvalid
return emptyBody, challenge, err
}
if va.UserAgent != "" {
@ -183,11 +180,9 @@ func (va *ValidationAuthorityImpl) fetchHTTP(identifier core.AcmeIdentifier, pat
dialer, prob := va.resolveAndConstructDialer(host, port)
dialer.record.URL = url.String()
challenge.ValidationRecord = append(challenge.ValidationRecord, dialer.record)
validationRecords := []core.ValidationRecord{dialer.record}
if prob != nil {
challenge.Status = core.StatusInvalid
challenge.Error = prob
return emptyBody, challenge, prob
return nil, validationRecords, prob
}
tr := &http.Transport{
@ -214,7 +209,7 @@ func (va *ValidationAuthorityImpl) fetchHTTP(identifier core.AcmeIdentifier, pat
httpRequest.Header.Set("Accept", "*/*")
logRedirect := func(req *http.Request, via []*http.Request) error {
if len(challenge.ValidationRecord) >= maxRedirect {
if len(validationRecords) >= maxRedirect {
return fmt.Errorf("Too many redirects")
}
@ -244,7 +239,7 @@ func (va *ValidationAuthorityImpl) fetchHTTP(identifier core.AcmeIdentifier, pat
dialer, err := va.resolveAndConstructDialer(reqHost, reqPort)
dialer.record.URL = req.URL.String()
challenge.ValidationRecord = append(challenge.ValidationRecord, dialer.record)
validationRecords = append(validationRecords, dialer.record)
if err != nil {
return err
}
@ -259,44 +254,35 @@ func (va *ValidationAuthorityImpl) fetchHTTP(identifier core.AcmeIdentifier, pat
}
httpResponse, err := client.Do(httpRequest)
if err != nil {
challenge.Status = core.StatusInvalid
challenge.Error = &probs.ProblemDetails{
va.log.Debug(err.Error())
return nil, validationRecords, &probs.ProblemDetails{
Type: parseHTTPConnError(err),
Detail: fmt.Sprintf("Could not connect to %s", url),
}
va.log.Debug(strings.Join([]string{challenge.Error.Error(), err.Error()}, ": "))
return emptyBody, challenge, err
}
defer httpResponse.Body.Close()
if httpResponse.StatusCode != 200 {
challenge.Status = core.StatusInvalid
challenge.Error = &probs.ProblemDetails{
return nil, validationRecords, &probs.ProblemDetails{
Type: probs.UnauthorizedProblem,
Detail: fmt.Sprintf("Invalid response from %s [%s]: %d",
url.String(), dialer.record.AddressUsed, httpResponse.StatusCode),
}
err = challenge.Error
return emptyBody, challenge, err
}
body, err := ioutil.ReadAll(httpResponse.Body)
if err != nil {
challenge.Status = core.StatusInvalid
challenge.Error = &probs.ProblemDetails{
return nil, validationRecords, &probs.ProblemDetails{
Type: probs.UnauthorizedProblem,
Detail: fmt.Sprintf("Error reading HTTP response body: %v", err),
}
return emptyBody, challenge, err
}
return body, challenge, nil
return body, validationRecords, nil
}
func (va *ValidationAuthorityImpl) validateTLSWithZName(identifier core.AcmeIdentifier, input core.Challenge, zName string) (core.Challenge, error) {
challenge := input
func (va *ValidationAuthorityImpl) validateTLSWithZName(identifier core.AcmeIdentifier, challenge core.Challenge, zName string) ([]core.ValidationRecord, *probs.ProblemDetails) {
addr, allAddrs, problem := va.getAddr(identifier.Value)
challenge.ValidationRecord = []core.ValidationRecord{
validationRecords := []core.ValidationRecord{
core.ValidationRecord{
Hostname: identifier.Value,
AddressesResolved: allAddrs,
@ -304,15 +290,13 @@ func (va *ValidationAuthorityImpl) validateTLSWithZName(identifier core.AcmeIden
},
}
if problem != nil {
challenge.Status = core.StatusInvalid
challenge.Error = problem
return challenge, challenge.Error
return validationRecords, problem
}
// Make a connection with SNI = nonceName
portString := strconv.Itoa(va.tlsPort)
hostPort := net.JoinHostPort(addr.String(), portString)
challenge.ValidationRecord[0].Port = portString
validationRecords[0].Port = portString
va.log.Notice(fmt.Sprintf("%s [%s] Attempting to validate for %s %s", challenge.Type, identifier, hostPort, zName))
conn, err := tls.DialWithDialer(&net.Dialer{Timeout: validationTimeout}, "tcp", hostPort, &tls.Config{
ServerName: zName,
@ -320,106 +304,84 @@ func (va *ValidationAuthorityImpl) validateTLSWithZName(identifier core.AcmeIden
})
if err != nil {
challenge.Status = core.StatusInvalid
challenge.Error = &probs.ProblemDetails{
va.log.Debug(fmt.Sprintf("%s [%s] TLS Connection failure: %s", challenge.Type, identifier, err))
return validationRecords, &probs.ProblemDetails{
Type: parseHTTPConnError(err),
Detail: "Failed to connect to host for DVSNI challenge",
}
va.log.Debug(fmt.Sprintf("%s [%s] TLS Connection failure: %s", challenge.Type, identifier, err))
return challenge, err
}
defer conn.Close()
// Check that zName is a dNSName SAN in the server's certificate
certs := conn.ConnectionState().PeerCertificates
if len(certs) == 0 {
challenge.Error = &probs.ProblemDetails{
return validationRecords, &probs.ProblemDetails{
Type: probs.UnauthorizedProblem,
Detail: "No certs presented for TLS SNI challenge",
}
challenge.Status = core.StatusInvalid
return challenge, challenge.Error
}
for _, name := range certs[0].DNSNames {
if subtle.ConstantTimeCompare([]byte(name), []byte(zName)) == 1 {
challenge.Status = core.StatusValid
return challenge, nil
return validationRecords, nil
}
}
challenge.Error = &probs.ProblemDetails{
return validationRecords, &probs.ProblemDetails{
Type: probs.UnauthorizedProblem,
Detail: fmt.Sprintf("Correct zName not found for TLS SNI challenge. Found %s",
strings.Join(certs[0].DNSNames, ", ")),
}
challenge.Status = core.StatusInvalid
return challenge, challenge.Error
}
func (va *ValidationAuthorityImpl) validateHTTP01(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) {
challenge := input
func (va *ValidationAuthorityImpl) validateHTTP01(identifier core.AcmeIdentifier, challenge core.Challenge) ([]core.ValidationRecord, *probs.ProblemDetails) {
if identifier.Type != core.IdentifierDNS {
challenge.Status = core.StatusInvalid
challenge.Error = &probs.ProblemDetails{
va.log.Debug(fmt.Sprintf("%s [%s] Identifier failure", challenge.Type, identifier))
return nil, &probs.ProblemDetails{
Type: probs.MalformedProblem,
Detail: "Identifier type for HTTP validation was not DNS",
}
va.log.Debug(fmt.Sprintf("%s [%s] Identifier failure", challenge.Type, identifier))
return challenge, challenge.Error
}
// Perform the fetch
path := fmt.Sprintf(".well-known/acme-challenge/%s", challenge.Token)
body, challenge, err := va.fetchHTTP(identifier, path, false, challenge)
body, validationRecords, err := va.fetchHTTP(identifier, path, false, challenge)
if err != nil {
return challenge, err
return validationRecords, err
}
payload := strings.TrimRight(string(body), whitespaceCutset)
// Parse body as a key authorization object
serverKeyAuthorization, err := core.NewKeyAuthorizationFromString(payload)
if err != nil {
err = fmt.Errorf("Error parsing key authorization file: %s", err.Error())
va.log.Debug(err.Error())
challenge.Status = core.StatusInvalid
challenge.Error = &probs.ProblemDetails{
serverKeyAuthorization, authErr := core.NewKeyAuthorizationFromString(payload)
if authErr != nil {
va.log.Debug(authErr.Error())
return validationRecords, &probs.ProblemDetails{
Type: probs.UnauthorizedProblem,
Detail: err.Error(),
Detail: fmt.Sprintf("Error parsing key authorization file: %s", authErr.Error()),
}
return challenge, err
}
// Check that the account key for this challenge is authorized by this object
if !serverKeyAuthorization.Match(challenge.Token, challenge.AccountKey) {
err = fmt.Errorf("The key authorization file from the server did not match this challenge [%v] != [%v]",
errString := fmt.Sprintf("The key authorization file from the server did not match this challenge [%v] != [%v]",
challenge.KeyAuthorization.String(), string(body))
va.log.Debug(err.Error())
challenge.Status = core.StatusInvalid
challenge.Error = &probs.ProblemDetails{
va.log.Debug(errString)
return validationRecords, &probs.ProblemDetails{
Type: probs.UnauthorizedProblem,
Detail: err.Error(),
Detail: errString,
}
return challenge, err
}
challenge.Status = core.StatusValid
return challenge, nil
return validationRecords, nil
}
func (va *ValidationAuthorityImpl) validateTLSSNI01(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) {
challenge := input
func (va *ValidationAuthorityImpl) validateTLSSNI01(identifier core.AcmeIdentifier, challenge core.Challenge) ([]core.ValidationRecord, *probs.ProblemDetails) {
if identifier.Type != "dns" {
challenge.Error = &probs.ProblemDetails{
va.log.Debug(fmt.Sprintf("TLS-SNI [%s] Identifier failure", identifier))
return nil, &probs.ProblemDetails{
Type: probs.MalformedProblem,
Detail: "Identifier type for TLS-SNI was not DNS",
}
challenge.Status = core.StatusInvalid
va.log.Debug(fmt.Sprintf("TLS-SNI [%s] Identifier failure", identifier))
return challenge, challenge.Error
}
// Compute the digest that will appear in the certificate
@ -453,17 +415,13 @@ func parseHTTPConnError(err error) probs.ProblemType {
return probs.ConnectionProblem
}
func (va *ValidationAuthorityImpl) validateDNS01(identifier core.AcmeIdentifier, input core.Challenge) (core.Challenge, error) {
challenge := input
func (va *ValidationAuthorityImpl) validateDNS01(identifier core.AcmeIdentifier, challenge core.Challenge) ([]core.ValidationRecord, *probs.ProblemDetails) {
if identifier.Type != core.IdentifierDNS {
challenge.Error = &probs.ProblemDetails{
va.log.Debug(fmt.Sprintf("DNS [%s] Identifier failure", identifier))
return nil, &probs.ProblemDetails{
Type: probs.MalformedProblem,
Detail: "Identifier type for DNS was not itself DNS",
}
va.log.Debug(fmt.Sprintf("DNS [%s] Identifier failure", identifier))
challenge.Status = core.StatusInvalid
return challenge, challenge.Error
}
// Compute the digest of the key authorization file
@ -478,25 +436,21 @@ func (va *ValidationAuthorityImpl) validateDNS01(identifier core.AcmeIdentifier,
va.stats.Inc("VA.DNS.Rate", 1, 1.0)
if err != nil {
challenge.Status = core.StatusInvalid
challenge.Error = bdns.ProblemDetailsFromDNSError(err)
va.log.Debug(fmt.Sprintf("%s [%s] DNS failure: %s", challenge.Type, identifier, err))
return challenge, challenge.Error
return nil, bdns.ProblemDetailsFromDNSError(err)
}
for _, element := range txts {
if subtle.ConstantTimeCompare([]byte(element), []byte(authorizedKeysDigest)) == 1 {
challenge.Status = core.StatusValid
return challenge, nil
// Successful challenge validation
return nil, nil
}
}
challenge.Error = &probs.ProblemDetails{
return nil, &probs.ProblemDetails{
Type: probs.UnauthorizedProblem,
Detail: "Correct value not found for DNS challenge",
}
challenge.Status = core.StatusInvalid
return challenge, challenge.Error
}
func (va *ValidationAuthorityImpl) checkCAA(identifier core.AcmeIdentifier, regID int64) *probs.ProblemDetails {
@ -525,31 +479,25 @@ func (va *ValidationAuthorityImpl) validate(authz core.Authorization, challengeI
Requester: authz.RegistrationID,
RequestTime: va.clk.Now(),
}
if !authz.Challenges[challengeIndex].IsSane(true) {
chall := &authz.Challenges[challengeIndex]
chall.Status = core.StatusInvalid
chall.Error = &probs.ProblemDetails{Type: probs.MalformedProblem,
Detail: fmt.Sprintf("Challenge failed sanity check.")}
logEvent.Challenge = *chall
logEvent.Error = chall.Error.Detail
challenge := &authz.Challenges[challengeIndex]
vStart := va.clk.Now()
validationRecords, prob := va.validateChallengeAndCAA(authz.Identifier, *challenge, authz.RegistrationID)
va.stats.TimingDuration(fmt.Sprintf("VA.Validations.%s.%s", challenge.Type, challenge.Status), time.Since(vStart), 1.0)
challenge.ValidationRecord = validationRecords
if prob != nil {
challenge.Status = core.StatusInvalid
challenge.Error = prob
logEvent.Error = prob.Error()
} else if !authz.Challenges[challengeIndex].RecordsSane() {
challenge.Status = core.StatusInvalid
challenge.Error = &probs.ProblemDetails{Type: probs.ServerInternalProblem,
Detail: "Records for validation failed sanity check"}
logEvent.Error = challenge.Error.Error()
} else {
var err error
vStart := va.clk.Now()
authz.Challenges[challengeIndex], err = va.validateChallengeAndCAA(authz.Identifier, authz.Challenges[challengeIndex], authz.RegistrationID)
va.stats.TimingDuration(fmt.Sprintf("VA.Validations.%s.%s", authz.Challenges[challengeIndex].Type, authz.Challenges[challengeIndex].Status), time.Since(vStart), 1.0)
if err != nil {
logEvent.Error = err.Error()
} else if !authz.Challenges[challengeIndex].RecordsSane() {
chall := &authz.Challenges[challengeIndex]
chall.Status = core.StatusInvalid
chall.Error = &probs.ProblemDetails{Type: probs.ServerInternalProblem,
Detail: "Records for validation failed sanity check"}
logEvent.Error = chall.Error.Detail
}
logEvent.Challenge = authz.Challenges[challengeIndex]
challenge.Status = core.StatusValid
}
logEvent.Challenge = *challenge
// AUDIT[ Certificate Requests ] 11917fa4-10ef-4e0d-9105-bacbe7836a3c
va.log.AuditObject("Validation result", logEvent)
@ -559,27 +507,31 @@ func (va *ValidationAuthorityImpl) validate(authz core.Authorization, challengeI
va.RA.OnValidationUpdate(authz)
}
func (va *ValidationAuthorityImpl) validateChallengeAndCAA(identifier core.AcmeIdentifier, challenge core.Challenge, regID int64) (core.Challenge, error) {
func (va *ValidationAuthorityImpl) validateChallengeAndCAA(identifier core.AcmeIdentifier, challenge core.Challenge, regID int64) ([]core.ValidationRecord, *probs.ProblemDetails) {
ch := make(chan *probs.ProblemDetails, 1)
go func() {
ch <- va.checkCAA(identifier, regID)
}()
result, err := va.validateChallenge(identifier, challenge)
validationRecords, err := va.validateChallenge(identifier, challenge)
if err != nil {
return result, err
return validationRecords, err
}
problemDetails := <-ch
if problemDetails != nil {
result.Error = problemDetails
result.Status = core.StatusInvalid
return result, problemDetails
caaProblem := <-ch
if caaProblem != nil {
return validationRecords, caaProblem
}
return result, nil
return validationRecords, nil
}
func (va *ValidationAuthorityImpl) validateChallenge(identifier core.AcmeIdentifier, challenge core.Challenge) (core.Challenge, error) {
func (va *ValidationAuthorityImpl) validateChallenge(identifier core.AcmeIdentifier, challenge core.Challenge) ([]core.ValidationRecord, *probs.ProblemDetails) {
if !challenge.IsSane(true) {
return nil, &probs.ProblemDetails{
Type: probs.MalformedProblem,
Detail: fmt.Sprintf("Challenge failed sanity check."),
}
}
switch challenge.Type {
case core.ChallengeTypeHTTP01:
return va.validateHTTP01(identifier, challenge)
@ -588,7 +540,10 @@ func (va *ValidationAuthorityImpl) validateChallenge(identifier core.AcmeIdentif
case core.ChallengeTypeDNS01:
return va.validateDNS01(identifier, challenge)
}
return core.Challenge{}, fmt.Errorf("invalid challenge type %s", challenge.Type)
return nil, &probs.ProblemDetails{
Type: probs.MalformedProblem,
Detail: fmt.Sprintf("invalid challenge type %s", challenge.Type),
}
}
// UpdateValidations runs the validate() method asynchronously using goroutines.

View File

@ -194,7 +194,7 @@ func tlssniSrv(t *testing.T, chall core.Challenge) *httptest.Server {
return hs
}
func TestHttp(t *testing.T) {
func TestHTTP(t *testing.T) {
chall := core.HTTPChallenge01(accountKey)
err := setChallengeToken(&chall, expectedToken)
test.AssertNotError(t, err, "Failed to complete HTTP challenge")
@ -222,75 +222,84 @@ func TestHttp(t *testing.T) {
va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: badPort}, nil, stats, clock.Default())
va.DNSResolver = &mocks.DNSResolver{}
invalidChall, err := va.validateHTTP01(ident, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?")
test.AssertEquals(t, invalidChall.Error.Type, probs.ConnectionProblem)
_, prob := va.validateHTTP01(ident, chall)
if prob == nil {
t.Fatalf("Server's down; expected refusal. Where did we connect?")
}
test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
va = NewValidationAuthorityImpl(&PortConfig{HTTPPort: goodPort}, nil, stats, clock.Default())
va.DNSResolver = &mocks.DNSResolver{}
log.Clear()
t.Logf("Trying to validate: %+v\n", chall)
finChall, err := va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, "Error validating http")
_, prob = va.validateHTTP01(ident, 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)
invalidChall, err = va.validateHTTP01(ident, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "Should have found a 404 for the challenge.")
test.AssertEquals(t, invalidChall.Error.Type, probs.UnauthorizedProblem)
_, prob = va.validateHTTP01(ident, 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.
invalidChall, err = va.validateHTTP01(ident, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "Should have found the wrong token value.")
test.AssertEquals(t, invalidChall.Error.Type, probs.UnauthorizedProblem)
_, prob = va.validateHTTP01(ident, 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)
finChall, err = va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, "Failed to follow 301 redirect")
_, prob = va.validateHTTP01(ident, chall)
if prob != nil {
t.Fatalf("Failed to follow 301 redirect")
}
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1)
log.Clear()
setChallengeToken(&chall, pathFound)
finChall, err = va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, "Failed to follow 302 redirect")
_, prob = va.validateHTTP01(ident, chall)
if prob != nil {
t.Fatalf("Failed to follow 302 redirect")
}
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathFound+`" to ".*/`+pathMoved+`"`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1)
ipIdentifier := core.AcmeIdentifier{Type: core.IdentifierType("ip"), Value: "127.0.0.1"}
invalidChall, err = va.validateHTTP01(ipIdentifier, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "IdentifierType IP shouldn't have worked.")
test.AssertEquals(t, invalidChall.Error.Type, probs.MalformedProblem)
_, prob = va.validateHTTP01(ipIdentifier, chall)
if prob == nil {
t.Fatalf("IdentifierType IP shouldn't have worked.")
}
test.AssertEquals(t, prob.Type, probs.MalformedProblem)
invalidChall, err = va.validateHTTP01(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "Domain name is invalid.")
test.AssertEquals(t, invalidChall.Error.Type, probs.UnknownHostProblem)
_, prob = va.validateHTTP01(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall)
if prob == nil {
t.Fatalf("Domain name is invalid.")
}
test.AssertEquals(t, prob.Type, probs.UnknownHostProblem)
setChallengeToken(&chall, pathWaitLong)
started := time.Now()
invalidChall, err = va.validateHTTP01(ident, chall)
_, prob = va.validateHTTP01(ident, chall)
took := time.Since(started)
// Check that the HTTP connection times out after 5 seconds and doesn't block for 10 seconds
test.Assert(t, (took > (time.Second * 5)), "HTTP timed out before 5 seconds")
test.Assert(t, (took < (time.Second * 10)), "HTTP connection didn't timeout after 5 seconds")
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "Connection should've timed out")
test.AssertEquals(t, invalidChall.Error.Type, probs.ConnectionProblem)
if prob == nil {
t.Fatalf("Connection should've timed out")
}
test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
}
func TestHTTPRedirectLookup(t *testing.T) {
@ -308,43 +317,43 @@ func TestHTTPRedirectLookup(t *testing.T) {
log.Clear()
setChallengeToken(&chall, pathMoved)
finChall, err := va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, chall.Token)
_, prob := va.validateHTTP01(ident, chall)
if prob != nil {
t.Fatalf("Unexpected failure in redirect (%s): %s", pathMoved, prob)
}
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 2)
log.Clear()
setChallengeToken(&chall, pathFound)
finChall, err = va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, chall.Token)
_, prob = va.validateHTTP01(ident, chall)
if prob != nil {
t.Fatalf("Unexpected failure in redirect (%s): %s", pathFound, prob)
}
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathFound+`" to ".*/`+pathMoved+`"`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathMoved+`" to ".*/`+pathValid+`"`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 3)
log.Clear()
setChallengeToken(&chall, pathReLookupInvalid)
finChall, err = va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusInvalid)
_, err = va.validateHTTP01(ident, chall)
test.AssertError(t, err, chall.Token)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`No IPv4 addresses found for invalid.invalid`)), 1)
log.Clear()
setChallengeToken(&chall, pathReLookup)
finChall, err = va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, chall.Token)
_, prob = va.validateHTTP01(ident, chall)
if prob != nil {
t.Fatalf("Unexpected error in redirect (%s): %s", pathReLookup, prob)
}
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/`+pathReLookup+`" to ".*other.valid:\d+/path"`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
log.Clear()
setChallengeToken(&chall, pathRedirectPort)
finChall, err = va.validateHTTP01(ident, chall)
fmt.Println(finChall.ValidationRecord)
test.AssertEquals(t, finChall.Status, core.StatusInvalid)
_, err = va.validateHTTP01(ident, chall)
test.AssertError(t, err, chall.Token)
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/port-redirect" to ".*other.valid:8080/path"`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
@ -365,10 +374,10 @@ func TestHTTPRedirectLoop(t *testing.T) {
va.DNSResolver = &mocks.DNSResolver{}
log.Clear()
finChall, err := va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusInvalid)
test.AssertError(t, err, chall.Token)
fmt.Println(finChall)
_, prob := va.validateHTTP01(ident, chall)
if prob == nil {
t.Fatalf("Challenge should have failed for %s", chall.Token)
}
}
func TestHTTPRedirectUserAgent(t *testing.T) {
@ -386,12 +395,16 @@ func TestHTTPRedirectUserAgent(t *testing.T) {
va.UserAgent = rejectUserAgent
setChallengeToken(&chall, pathMoved)
finChall, _ := va.validateHTTP01(ident, chall)
test.AssertNotEquals(t, finChall.Status, core.StatusValid)
_, prob := va.validateHTTP01(ident, chall)
if prob == nil {
t.Fatalf("Challenge with rejectUserAgent should have failed (%s).", pathMoved)
}
setChallengeToken(&chall, pathFound)
finChall, _ = va.validateHTTP01(ident, chall)
test.AssertNotEquals(t, finChall.Status, core.StatusValid)
_, prob = va.validateHTTP01(ident, chall)
if prob == nil {
t.Fatalf("Challenge with rejectUserAgent should have failed (%s).", pathFound)
}
}
func getPort(hs *httptest.Server) (int, error) {
@ -423,25 +436,28 @@ func TestTLSSNI(t *testing.T) {
va.DNSResolver = &mocks.DNSResolver{}
log.Clear()
finChall, err := va.validateTLSSNI01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, "")
_, prob := va.validateTLSSNI01(ident, chall)
if prob != nil {
t.Fatalf("Unexpected failre in validateTLSSNI01: %s", prob)
}
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
log.Clear()
invalidChall, err := va.validateTLSSNI01(core.AcmeIdentifier{
_, prob = va.validateTLSSNI01(core.AcmeIdentifier{
Type: core.IdentifierType("ip"),
Value: net.JoinHostPort("127.0.0.1", fmt.Sprintf("%d", port)),
}, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "IdentifierType IP shouldn't have worked.")
test.AssertEquals(t, invalidChall.Error.Type, probs.MalformedProblem)
if prob == nil {
t.Fatalf("IdentifierType IP shouldn't have worked.")
}
test.AssertEquals(t, prob.Type, probs.MalformedProblem)
log.Clear()
invalidChall, err = va.validateTLSSNI01(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "Domain name was supposed to be invalid.")
test.AssertEquals(t, invalidChall.Error.Type, probs.UnknownHostProblem)
_, prob = va.validateTLSSNI01(core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "always.invalid"}, chall)
if prob == nil {
t.Fatalf("Domain name was supposed to be invalid.")
}
test.AssertEquals(t, prob.Type, probs.UnknownHostProblem)
// Need to create a new authorized keys object to get an unknown SNI (from the signature value)
chall.Token = core.NewToken()
@ -450,22 +466,24 @@ func TestTLSSNI(t *testing.T) {
log.Clear()
started := time.Now()
invalidChall, err = va.validateTLSSNI01(ident, chall)
_, prob = va.validateTLSSNI01(ident, chall)
took := time.Since(started)
// Check that the HTTP connection times out after 5 seconds and doesn't block for 10 seconds
test.Assert(t, (took > (time.Second * 5)), "HTTP timed out before 5 seconds")
test.Assert(t, (took < (time.Second * 10)), "HTTP connection didn't timeout after 5 seconds")
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "Connection should've timed out")
test.AssertEquals(t, invalidChall.Error.Type, probs.ConnectionProblem)
if prob == nil {
t.Fatalf("Connection should've timed out")
}
test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
// Take down validation server and check that validation fails.
hs.Close()
invalidChall, err = va.validateTLSSNI01(ident, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "Server's down; expected refusal. Where did we connect?")
test.AssertEquals(t, invalidChall.Error.Type, probs.ConnectionProblem)
_, err = va.validateTLSSNI01(ident, chall)
if err == nil {
t.Fatalf("Server's down; expected refusal. Where did we connect?")
}
test.AssertEquals(t, prob.Type, probs.ConnectionProblem)
}
func brokenTLSSrv() *httptest.Server {
@ -489,10 +507,11 @@ func TestTLSError(t *testing.T) {
va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, nil, stats, clock.Default())
va.DNSResolver = &mocks.DNSResolver{}
invalidChall, err := va.validateTLSSNI01(ident, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "What cert was used?")
test.AssertEquals(t, invalidChall.Error.Type, probs.TLSProblem)
_, prob := va.validateTLSSNI01(ident, chall)
if prob == nil {
t.Fatalf("TLS validation should have failed: What cert was used?")
}
test.AssertEquals(t, prob.Type, probs.TLSProblem)
}
func TestValidateHTTP(t *testing.T) {