boulder/va/validation-authority_test.go

1331 lines
48 KiB
Go

// Copyright 2014 ISRG. All rights reserved
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at http://mozilla.org/MPL/2.0/.
package va
import (
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/tls"
"crypto/x509"
"crypto/x509/pkix"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"log/syslog"
"math/big"
"net"
"net/http"
"net/http/httptest"
"net/url"
"strconv"
"strings"
"testing"
"time"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/cactus/go-statsd-client/statsd"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/jmhodges/clock"
"github.com/letsencrypt/boulder/Godeps/_workspace/src/github.com/letsencrypt/go-jose"
"github.com/letsencrypt/boulder/core"
"github.com/letsencrypt/boulder/mocks"
"github.com/letsencrypt/boulder/test"
)
func bigIntFromB64(b64 string) *big.Int {
bytes, _ := base64.URLEncoding.DecodeString(b64)
x := big.NewInt(0)
x.SetBytes(bytes)
return x
}
func intFromB64(b64 string) int {
return int(bigIntFromB64(b64).Int64())
}
var n = bigIntFromB64("n4EPtAOCc9AlkeQHPzHStgAbgs7bTZLwUBZdR8_KuKPEHLd4rHVTeT-O-XV2jRojdNhxJWTDvNd7nqQ0VEiZQHz_AJmSCpMaJMRBSFKrKb2wqVwGU_NsYOYL-QtiWN2lbzcEe6XC0dApr5ydQLrHqkHHig3RBordaZ6Aj-oBHqFEHYpPe7Tpe-OfVfHd1E6cS6M1FZcD1NNLYD5lFHpPI9bTwJlsde3uhGqC0ZCuEHg8lhzwOHrtIQbS0FVbb9k3-tVTU4fg_3L_vniUFAKwuCLqKnS2BYwdq_mzSnbLY7h_qixoR7jig3__kRhuaxwUkRz5iaiQkqgc5gHdrNP5zw==")
var e = intFromB64("AQAB")
var d = bigIntFromB64("bWUC9B-EFRIo8kpGfh0ZuyGPvMNKvYWNtB_ikiH9k20eT-O1q_I78eiZkpXxXQ0UTEs2LsNRS-8uJbvQ-A1irkwMSMkK1J3XTGgdrhCku9gRldY7sNA_AKZGh-Q661_42rINLRCe8W-nZ34ui_qOfkLnK9QWDDqpaIsA-bMwWWSDFu2MUBYwkHTMEzLYGqOe04noqeq1hExBTHBOBdkMXiuFhUq1BU6l-DqEiWxqg82sXt2h-LMnT3046AOYJoRioz75tSUQfGCshWTBnP5uDjd18kKhyv07lhfSJdrPdM5Plyl21hsFf4L_mHCuoFau7gdsPfHPxxjVOcOpBrQzwQ==")
var p = bigIntFromB64("uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc=")
var q = bigIntFromB64("uKE2dh-cTf6ERF4k4e_jy78GfPYUIaUyoSSJuBzp3Cubk3OCqs6grT8bR_cu0Dm1MZwWmtdqDyI95HrUeq3MP15vMMON8lHTeZu2lmKvwqW7anV5UzhM1iZ7z4yMkuUwFWoBvyY898EXvRD-hdqRxHlSqAZ192zB3pVFJ0s7pFc=")
var TheKey = rsa.PrivateKey{
PublicKey: rsa.PublicKey{N: n, E: e},
D: d,
Primes: []*big.Int{p, q},
}
var accountKey = &jose.JsonWebKey{Key: TheKey.Public()}
var ident = core.AcmeIdentifier{Type: core.IdentifierDNS, Value: "localhost"}
var log = mocks.UseMockLog()
const expectedToken = "THETOKEN"
const pathWrongToken = "wrongtoken"
const path404 = "404"
const pathFound = "302"
const pathMoved = "301"
const pathRedirectLookup = "re-lookup"
const pathRedirectLookupInvalid = "re-lookup-invalid"
const pathRedirectPort = "port-redirect"
const pathWait = "wait"
const pathWaitLong = "wait-long"
const pathReLookup = "re-lookup"
const pathReLookupInvalid = "re-lookup-invalid"
const pathLooper = "looper"
const pathValid = "valid"
//-----BEGIN TO DELETE-----
func createValidation(token string, enableTLS bool) string {
payload, _ := json.Marshal(map[string]interface{}{
"type": "simpleHttp",
"token": token,
"tls": enableTLS,
})
signer, _ := jose.NewSigner(jose.RS256, &TheKey)
obj, _ := signer.Sign(payload, "")
return obj.FullSerialize()
}
func simpleSrv(t *testing.T, token string, enableTLS bool) *httptest.Server {
m := http.NewServeMux()
defaultToken := token
currentToken := defaultToken
m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(r.Host, "localhost:") && r.Host != "other.valid" && r.Host != "other.valid:8080" {
t.Errorf("Bad Host header: " + r.Host)
}
if strings.HasSuffix(r.URL.Path, path404) {
t.Logf("SIMPLESRV: Got a 404 req\n")
http.NotFound(w, r)
} else if strings.HasSuffix(r.URL.Path, pathMoved) {
t.Logf("SIMPLESRV: Got a 301 redirect req\n")
if currentToken == defaultToken {
currentToken = pathMoved
}
http.Redirect(w, r, "valid", 301)
} else if strings.HasSuffix(r.URL.Path, pathFound) {
t.Logf("SIMPLESRV: Got a 302 redirect req\n")
if currentToken == defaultToken {
currentToken = pathFound
}
http.Redirect(w, r, pathMoved, 302)
} else if strings.HasSuffix(r.URL.Path, "wait") {
t.Logf("SIMPLESRV: Got a wait req\n")
time.Sleep(time.Second * 3)
} else if strings.HasSuffix(r.URL.Path, "wait-long") {
t.Logf("SIMPLESRV: Got a wait-long req\n")
time.Sleep(time.Second * 10)
} else if strings.HasSuffix(r.URL.Path, "re-lookup") {
t.Logf("SIMPLESRV: Got a redirect req to a valid hostname\n")
if currentToken == defaultToken {
currentToken = "re-lookup"
}
http.Redirect(w, r, "http://other.valid/path", 302)
} else if strings.HasSuffix(r.URL.Path, "re-lookup-invalid") {
t.Logf("SIMPLESRV: Got a redirect req to a invalid hostname\n")
http.Redirect(w, r, "http://invalid.invalid/path", 302)
} else if strings.HasSuffix(r.URL.Path, "looper") {
t.Logf("SIMPLESRV: Got a loop req\n")
http.Redirect(w, r, r.URL.String(), 301)
} else if strings.HasSuffix(r.URL.Path, pathRedirectPort) {
t.Logf("SIMPLESRV: Got a port redirect req\n")
http.Redirect(w, r, "http://other.valid:8080/path", 302)
} else {
t.Logf("SIMPLESRV: Got a valid req\n")
fmt.Fprint(w, createValidation(currentToken, enableTLS))
currentToken = defaultToken
}
})
server := httptest.NewUnstartedServer(m)
if !enableTLS {
server.Start()
} else {
template := &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: []string{"example.com"},
}
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
cert := &tls.Certificate{
Certificate: [][]byte{certBytes},
PrivateKey: &TheKey,
}
server.TLS = &tls.Config{
Certificates: []tls.Certificate{*cert},
}
server.StartTLS()
}
return server
}
func dvsniSrv(t *testing.T, chall core.Challenge) *httptest.Server {
encodedSig := core.B64enc(chall.Validation.Signatures[0].Signature)
h := sha256.New()
h.Write([]byte(encodedSig))
Z := hex.EncodeToString(h.Sum(nil))
ZName := fmt.Sprintf("%s.%s.acme.invalid", Z[:32], Z[32:])
template := &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: []string{ZName},
}
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
cert := &tls.Certificate{
Certificate: [][]byte{certBytes},
PrivateKey: &TheKey,
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{*cert},
ClientAuth: tls.NoClientCert,
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
if clientHello.ServerName != ZName {
time.Sleep(time.Second * 10)
return nil, nil
}
return cert, nil
},
NextProtos: []string{"http/1.1"},
}
hs := httptest.NewUnstartedServer(http.DefaultServeMux)
hs.TLS = tlsConfig
hs.StartTLS()
return hs
}
func TestSimpleHttpTLS(t *testing.T) {
chall := core.Challenge{
Type: core.ChallengeTypeSimpleHTTP,
Token: expectedToken,
ValidationRecord: []core.ValidationRecord{},
AccountKey: accountKey,
}
hs := simpleSrv(t, expectedToken, true)
defer hs.Close()
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{HTTPSPort: port}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
log.Clear()
finChall, err := va.validateSimpleHTTP(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, "Error validating simpleHttp")
logs := log.GetAllMatching(`^\[AUDIT\] Attempting to validate simpleHttp for `)
test.AssertEquals(t, len(logs), 1)
test.AssertEquals(t, logs[0].Priority, syslog.LOG_NOTICE)
}
func TestSimpleHttp(t *testing.T) {
tls := false
chall := core.Challenge{
Type: core.ChallengeTypeSimpleHTTP,
Token: expectedToken,
TLS: &tls,
ValidationRecord: []core.ValidationRecord{},
AccountKey: accountKey,
}
// 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(#661): add hs.Close back, see ticket for blocker
hs := simpleSrv(t, expectedToken, tls)
goodPort, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
// Attempt to fail a challenge by telling the VA to connect to a port we are
// not listening on.
badPort := goodPort + 1
if badPort == 65536 {
badPort = goodPort - 1
}
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: badPort}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
invalidChall, err := va.validateSimpleHTTP(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, core.ConnectionProblem)
va = NewValidationAuthorityImpl(&PortConfig{HTTPPort: goodPort}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
log.Clear()
finChall, err := va.validateSimpleHTTP(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, "Error validating simpleHttp")
test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1)
log.Clear()
chall.Token = path404
invalidChall, err = va.validateSimpleHTTP(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, core.UnauthorizedProblem)
test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1)
log.Clear()
chall.Token = pathWrongToken
// The "wrong token" will actually be the expectedToken. It's wrong
// because it doesn't match pathWrongToken.
invalidChall, err = va.validateSimpleHTTP(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, core.UnauthorizedProblem)
test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1)
log.Clear()
chall.Token = pathMoved
finChall, err = va.validateSimpleHTTP(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, "Failed to follow 301 redirect")
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1)
log.Clear()
chall.Token = pathFound
finChall, err = va.validateSimpleHTTP(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, "Failed to follow 302 redirect")
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/302" to ".*/301"`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1)
ipIdentifier := core.AcmeIdentifier{Type: core.IdentifierType("ip"), Value: "127.0.0.1"}
invalidChall, err = va.validateSimpleHTTP(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, core.MalformedProblem)
invalidChall, err = va.validateSimpleHTTP(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, core.UnknownHostProblem)
chall.Token = "wait-long"
started := time.Now()
invalidChall, err = va.validateSimpleHTTP(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, core.ConnectionProblem)
}
func TestSimpleHttpRedirectLookup(t *testing.T) {
tls := false
chall := core.Challenge{
Token: expectedToken,
TLS: &tls,
ValidationRecord: []core.ValidationRecord{},
AccountKey: accountKey,
}
hs := simpleSrv(t, expectedToken, tls)
defer hs.Close()
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: port}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
log.Clear()
chall.Token = pathMoved
finChall, err := va.validateSimpleHTTP(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, chall.Token)
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 2)
log.Clear()
chall.Token = pathFound
finChall, err = va.validateSimpleHTTP(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, chall.Token)
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/302" to ".*/301"`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 3)
log.Clear()
chall.Token = pathRedirectLookupInvalid
finChall, err = va.validateSimpleHTTP(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusInvalid)
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()
chall.Token = pathRedirectLookup
finChall, err = va.validateSimpleHTTP(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, chall.Token)
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/re-lookup" to ".*other.valid/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()
chall.Token = pathRedirectPort
finChall, err = va.validateSimpleHTTP(ident, chall)
fmt.Println(finChall.ValidationRecord)
test.AssertEquals(t, finChall.Status, core.StatusInvalid)
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)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
}
func TestSimpleHttpRedirectLoop(t *testing.T) {
tls := false
chall := core.Challenge{
Token: "looper",
TLS: &tls,
ValidationRecord: []core.ValidationRecord{},
}
hs := simpleSrv(t, expectedToken, tls)
defer hs.Close()
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: port}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
log.Clear()
finChall, err := va.validateSimpleHTTP(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusInvalid)
test.AssertError(t, err, chall.Token)
fmt.Println(finChall)
}
func TestDvsni(t *testing.T) {
chall := createChallenge(core.ChallengeTypeDVSNI)
hs := dvsniSrv(t, chall)
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
log.Clear()
finChall, err := va.validateDvsni(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, "")
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.validateDvsni(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, core.MalformedProblem)
log.Clear()
invalidChall, err = va.validateDvsni(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, core.UnknownHostProblem)
// Need to re-sign to get an unknown SNI (from the signature value)
chall.Token = core.NewToken()
validationPayload, _ := json.Marshal(map[string]interface{}{
"type": chall.Type,
"token": chall.Token,
})
signer, _ := jose.NewSigner(jose.RS256, &TheKey)
chall.Validation, _ = signer.Sign(validationPayload, "")
log.Clear()
started := time.Now()
invalidChall, err = va.validateDvsni(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, core.ConnectionProblem)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
// Take down DVSNI validation server and check that validation fails.
hs.Close()
invalidChall, err = va.validateDvsni(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, core.ConnectionProblem)
}
func TestDVSNIWithTLSError(t *testing.T) {
chall := createChallenge(core.ChallengeTypeDVSNI)
hs := brokenTLSSrv()
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
invalidChall, err := va.validateDvsni(ident, chall)
test.AssertEquals(t, invalidChall.Status, core.StatusInvalid)
test.AssertError(t, err, "What cert was used?")
test.AssertEquals(t, invalidChall.Error.Type, core.TLSProblem)
}
//-----END TO DELETE-----
func httpSrv(t *testing.T, token string) *httptest.Server {
m := http.NewServeMux()
defaultToken := token
currentToken := defaultToken
m.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
if !strings.HasPrefix(r.Host, "localhost:") && r.Host != "other.valid" && r.Host != "other.valid:8080" {
t.Errorf("Bad Host header: " + r.Host)
}
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, 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 = "re-lookup"
}
http.Redirect(w, r, "http://other.valid/path", 302)
} else if strings.HasSuffix(r.URL.Path, pathReLookupInvalid) {
t.Logf("HTTPSRV: Got a redirect req to a invalid hostname\n")
http.Redirect(w, r, "http://invalid.invalid/path", 302)
} 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, pathRedirectPort) {
t.Logf("HTTPSRV: Got a port redirect req\n")
http.Redirect(w, r, "http://other.valid:8080/path", 302)
} else {
t.Logf("HTTPSRV: Got a valid req\n")
t.Logf("HTTPSRV: Path = %s\n", r.URL.Path)
authzKeysJSON, _ := json.Marshal(
core.AuthorizedKey{
Token: currentToken,
Key: accountKey,
})
fmt.Fprint(w, string(authzKeysJSON))
currentToken = defaultToken
}
})
server := httptest.NewUnstartedServer(m)
server.Start()
return server
}
func tlssniSrv(t *testing.T, chall core.Challenge) *httptest.Server {
h := sha256.New()
h.Write([]byte(chall.AuthorizedKey))
Z := hex.EncodeToString(h.Sum(nil))
ZName := fmt.Sprintf("%s.%s.acme.invalid", Z[:32], Z[32:])
template := &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: []string{ZName},
}
certBytes, _ := x509.CreateCertificate(rand.Reader, template, template, &TheKey.PublicKey, &TheKey)
cert := &tls.Certificate{
Certificate: [][]byte{certBytes},
PrivateKey: &TheKey,
}
tlsConfig := &tls.Config{
Certificates: []tls.Certificate{*cert},
ClientAuth: tls.NoClientCert,
GetCertificate: func(clientHello *tls.ClientHelloInfo) (*tls.Certificate, error) {
if clientHello.ServerName != ZName {
time.Sleep(time.Second * 10)
return nil, nil
}
return cert, nil
},
NextProtos: []string{"http/1.1"},
}
hs := httptest.NewUnstartedServer(http.DefaultServeMux)
hs.TLS = tlsConfig
hs.StartTLS()
return hs
}
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 TestHttp(t *testing.T) {
chall, err := core.HTTPChallenge01(accountKey)
test.AssertNotError(t, err, "Failed to create HTTP challenge")
err = chall.UnsafeSetToken(expectedToken)
test.AssertNotError(t, err, "Failed to complete HTTP challenge")
// 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(#661): add hs.Close back, see ticket for blocker
hs := httpSrv(t, chall.Token)
goodPort, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
// Attempt to fail a challenge by telling the VA to connect to a port we are
// not listening on.
badPort := goodPort + 1
if badPort == 65536 {
badPort = goodPort - 1
}
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: badPort}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
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, core.ConnectionProblem)
va = NewValidationAuthorityImpl(&PortConfig{HTTPPort: goodPort}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
log.Clear()
finChall, err := va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, "Error validating http")
test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1)
log.Clear()
chall.UnsafeSetToken(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, core.UnauthorizedProblem)
test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1)
log.Clear()
chall.UnsafeSetToken(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, core.UnauthorizedProblem)
test.AssertEquals(t, len(log.GetAllMatching(`^\[AUDIT\] `)), 1)
log.Clear()
chall.UnsafeSetToken(pathMoved)
finChall, err = va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, "Failed to follow 301 redirect")
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1)
log.Clear()
chall.UnsafeSetToken(pathFound)
finChall, err = va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, "Failed to follow 302 redirect")
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/302" to ".*/301"`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 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, core.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, core.UnknownHostProblem)
chall.UnsafeSetToken("wait-long")
started := time.Now()
invalidChall, err = 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, core.ConnectionProblem)
}
func TestHTTPRedirectLookup(t *testing.T) {
chall, err := core.HTTPChallenge01(accountKey)
test.AssertNotError(t, err, "Failed to create HTTP challenge")
err = chall.UnsafeSetToken(expectedToken)
test.AssertNotError(t, err, "Failed to complete HTTP challenge")
hs := httpSrv(t, expectedToken)
defer hs.Close()
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: port}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
log.Clear()
chall.UnsafeSetToken(pathMoved)
finChall, err := va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, chall.Token)
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 2)
log.Clear()
chall.UnsafeSetToken(pathFound)
finChall, err = va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, chall.Token)
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/302" to ".*/301"`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/301" to ".*/valid"`)), 1)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for localhost \[using 127.0.0.1\]: \[127.0.0.1\]`)), 3)
log.Clear()
chall.UnsafeSetToken(pathRedirectLookupInvalid)
finChall, err = va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusInvalid)
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()
chall.UnsafeSetToken(pathRedirectLookup)
finChall, err = va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, chall.Token)
test.AssertEquals(t, len(log.GetAllMatching(`redirect from ".*/re-lookup" to ".*other.valid/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()
chall.UnsafeSetToken(pathRedirectPort)
finChall, err = va.validateHTTP01(ident, chall)
fmt.Println(finChall.ValidationRecord)
test.AssertEquals(t, finChall.Status, core.StatusInvalid)
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)
test.AssertEquals(t, len(log.GetAllMatching(`Resolved addresses for other.valid \[using 127.0.0.1\]: \[127.0.0.1\]`)), 1)
}
func TestHTTPRedirectLoop(t *testing.T) {
chall, err := core.HTTPChallenge01(accountKey)
test.AssertNotError(t, err, "Failed to create HTTP challenge")
err = chall.UnsafeSetToken("looper")
test.AssertNotError(t, err, "Failed to complete HTTP challenge")
hs := httpSrv(t, expectedToken)
defer hs.Close()
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: port}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
log.Clear()
finChall, err := va.validateHTTP01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusInvalid)
test.AssertError(t, err, chall.Token)
fmt.Println(finChall)
}
func getPort(hs *httptest.Server) (int, error) {
url, err := url.Parse(hs.URL)
if err != nil {
return 0, err
}
_, portString, err := net.SplitHostPort(url.Host)
if err != nil {
return 0, err
}
port, err := strconv.ParseInt(portString, 10, 64)
if err != nil {
return 0, err
}
return int(port), nil
}
func TestTLSSNI(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01)
hs := tlssniSrv(t, chall)
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
log.Clear()
finChall, err := va.validateTLSSNI01(ident, chall)
test.AssertEquals(t, finChall.Status, core.StatusValid)
test.AssertNotError(t, err, "")
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{
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, core.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, core.UnknownHostProblem)
// Need to create a new authorized keys object to get an unknown SNI (from the signature value)
chall.Token = core.NewToken()
authorizedKey, _ := json.Marshal(core.AuthorizedKey{
Token: chall.Token,
Key: accountKey,
})
chall.AuthorizedKey = core.JSONBuffer(authorizedKey)
log.Clear()
started := time.Now()
invalidChall, err = 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, core.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, core.ConnectionProblem)
}
func TestTLSError(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01)
hs := brokenTLSSrv()
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
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, core.TLSProblem)
}
func TestValidateHTTP(t *testing.T) {
chall, err := core.HTTPChallenge01(accountKey)
test.AssertNotError(t, err, "Failed to create HTTP challenge")
err = chall.UnsafeSetToken(core.NewToken())
test.AssertNotError(t, err, "Failed to complete HTTP challenge")
hs := httpSrv(t, chall.Token)
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{HTTPPort: port}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
defer hs.Close()
var authz = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: ident,
Challenges: []core.Challenge{chall},
}
va.validate(authz, 0)
test.AssertEquals(t, core.StatusValid, mockRA.lastAuthz.Challenges[0].Status)
}
// challengeType == "tls-sni-00" or "dns-00", since they're the same
func createChallenge(challengeType string) core.Challenge {
chall := core.Challenge{
Type: challengeType,
Status: core.StatusPending,
Token: core.NewToken(),
ValidationRecord: []core.ValidationRecord{},
AccountKey: accountKey,
}
authorizedKey, _ := json.Marshal(core.AuthorizedKey{
Token: chall.Token,
Key: accountKey,
})
chall.AuthorizedKey = core.JSONBuffer(authorizedKey)
//-----BEGIN TO DELETE-----
validationPayload, _ := json.Marshal(map[string]interface{}{
"type": chall.Type,
"token": chall.Token,
})
signer, _ := jose.NewSigner(jose.RS256, &TheKey)
chall.Validation, _ = signer.Sign(validationPayload, "")
//-----END TO DELETE-----
return chall
}
func TestValidateTLSSNI01(t *testing.T) {
chall := createChallenge(core.ChallengeTypeTLSSNI01)
hs := tlssniSrv(t, chall)
defer hs.Close()
port, err := getPort(hs)
test.AssertNotError(t, err, "failed to get test server port")
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{TLSPort: port}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
var authz = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: ident,
Challenges: []core.Challenge{chall},
}
va.validate(authz, 0)
test.AssertEquals(t, core.StatusValid, mockRA.lastAuthz.Challenges[0].Status)
}
func TestValidateTLSSNINotSane(t *testing.T) {
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default()) // no calls made
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
chall := createChallenge(core.ChallengeTypeTLSSNI01)
chall.Token = "not sane"
var authz = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: ident,
Challenges: []core.Challenge{chall},
}
va.validate(authz, 0)
test.AssertEquals(t, core.StatusInvalid, mockRA.lastAuthz.Challenges[0].Status)
}
func TestUpdateValidations(t *testing.T) {
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
chall, _ := core.HTTPChallenge01(accountKey)
chall.ValidationRecord = []core.ValidationRecord{}
err := chall.UnsafeSetToken(core.NewToken())
test.AssertNotError(t, err, "Failed to complete HTTP challenge")
var authz = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: ident,
Challenges: []core.Challenge{chall},
}
started := time.Now()
va.UpdateValidations(authz, 0)
took := time.Since(started)
// Check that the call to va.UpdateValidations didn't block for 3 seconds
test.Assert(t, (took < (time.Second * 3)), "UpdateValidations blocked")
}
func TestCAAChecking(t *testing.T) {
type CAATest struct {
Domain string
Present bool
Valid bool
}
tests := []CAATest{
// Reserved
CAATest{"reserved.com", true, false},
// Critical
CAATest{"critical.com", true, false},
CAATest{"nx.critical.com", true, false},
CAATest{"cname-critical.com", true, false},
CAATest{"nx.cname-critical.com", true, false},
// Good (absent)
CAATest{"absent.com", false, true},
CAATest{"cname-absent.com", false, true},
CAATest{"nx.cname-absent.com", false, true},
CAATest{"cname-nx.com", false, true},
CAATest{"example.co.uk", false, true},
// Good (present)
CAATest{"present.com", true, true},
CAATest{"cname-present.com", true, true},
CAATest{"cname2-present.com", true, true},
CAATest{"nx.cname2-present.com", true, true},
CAATest{"dname-present.com", true, true},
CAATest{"dname2cname.com", true, true},
// CNAME to critical
}
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
va.IssuerDomain = "letsencrypt.org"
for _, caaTest := range tests {
present, valid, err := va.CheckCAARecords(core.AcmeIdentifier{Type: "dns", Value: caaTest.Domain})
test.AssertNotError(t, err, caaTest.Domain)
fmt.Println(caaTest.Domain, caaTest.Present == present, caaTest.Valid == valid)
test.AssertEquals(t, caaTest.Present, present)
test.AssertEquals(t, caaTest.Valid, valid)
}
present, valid, err := va.CheckCAARecords(core.AcmeIdentifier{Type: "dns", Value: "servfail.com"})
test.AssertError(t, err, "servfail.com")
test.Assert(t, !present, "Present should be false")
test.Assert(t, !valid, "Valid should be false")
for _, name := range []string{
"www.caa-loop.com",
"a.cname-loop.com",
"a.dname-loop.com",
"cname-servfail.com",
"cname2servfail.com",
"dname-servfail.com",
"cname-and-dname.com",
"servfail.com",
} {
_, _, err = va.CheckCAARecords(core.AcmeIdentifier{Type: "dns", Value: name})
test.AssertError(t, err, name)
}
}
func TestDNSValidationFailure(t *testing.T) {
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
chalDNS := createChallenge(core.ChallengeTypeDNS01)
var authz = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: ident,
Challenges: []core.Challenge{chalDNS},
}
va.validate(authz, 0)
t.Logf("Resulting Authz: %+v", authz)
test.AssertNotNil(t, mockRA.lastAuthz, "Should have gotten an authorization")
test.Assert(t, authz.Challenges[0].Status == core.StatusInvalid, "Should be invalid.")
test.AssertEquals(t, authz.Challenges[0].Error.Type, core.UnauthorizedProblem)
}
func TestDNSValidationInvalid(t *testing.T) {
var notDNS = core.AcmeIdentifier{
Type: core.IdentifierType("iris"),
Value: "790DB180-A274-47A4-855F-31C428CB1072",
}
chalDNS, _ := core.DNSChallenge01(accountKey)
var authz = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: notDNS,
Challenges: []core.Challenge{chalDNS},
}
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
va.validate(authz, 0)
test.AssertNotNil(t, mockRA.lastAuthz, "Should have gotten an authorization")
test.Assert(t, authz.Challenges[0].Status == core.StatusInvalid, "Should be invalid.")
test.AssertEquals(t, authz.Challenges[0].Error.Type, core.MalformedProblem)
}
func TestDNSValidationNotSane(t *testing.T) {
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
chal0, _ := core.DNSChallenge01(accountKey)
chal0.Token = ""
chal1, _ := core.DNSChallenge01(accountKey)
chal1.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_"
chal2, _ := core.DNSChallenge01(accountKey)
chal2.TLS = new(bool)
*chal2.TLS = true
var authz = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: ident,
Challenges: []core.Challenge{chal0, chal1, chal2},
}
for i := 0; i < len(authz.Challenges); i++ {
va.validate(authz, i)
test.AssertEquals(t, authz.Challenges[i].Status, core.StatusInvalid)
test.AssertEquals(t, authz.Challenges[i].Error.Type, core.MalformedProblem)
}
}
func TestDNSValidationServFail(t *testing.T) {
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
chalDNS := createChallenge(core.ChallengeTypeDNS01)
badIdent := core.AcmeIdentifier{
Type: core.IdentifierDNS,
Value: "servfail.com",
}
var authz = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: badIdent,
Challenges: []core.Challenge{chalDNS},
}
va.validate(authz, 0)
test.AssertNotNil(t, mockRA.lastAuthz, "Should have gotten an authorization")
test.Assert(t, authz.Challenges[0].Status == core.StatusInvalid, "Should be invalid.")
test.AssertEquals(t, authz.Challenges[0].Error.Type, core.ConnectionProblem)
}
func TestDNSValidationNoServer(t *testing.T) {
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default())
va.DNSResolver = core.NewTestDNSResolverImpl(time.Second*5, []string{})
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
chalDNS := createChallenge(core.ChallengeTypeDNS01)
var authz = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: ident,
Challenges: []core.Challenge{chalDNS},
}
va.validate(authz, 0)
test.AssertNotNil(t, mockRA.lastAuthz, "Should have gotten an authorization")
test.Assert(t, authz.Challenges[0].Status == core.StatusInvalid, "Should be invalid.")
test.AssertEquals(t, authz.Challenges[0].Error.Type, core.ConnectionProblem)
}
// TestDNSValidationLive is an integration test, depending on
// the existance of some Internet resources. Because of that,
// it asserts nothing; it is intended for coverage.
func TestDNSValidationLive(t *testing.T) {
stats, _ := statsd.NewNoopClient()
va := NewValidationAuthorityImpl(&PortConfig{}, stats, clock.Default())
va.DNSResolver = &mocks.MockDNS{}
mockRA := &MockRegistrationAuthority{}
va.RA = mockRA
goodChalDNS, _ := core.DNSChallenge01(accountKey)
// This token is set at _acme-challenge.good.bin.coffee
goodChalDNS.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_w"
var goodIdent = core.AcmeIdentifier{
Type: core.IdentifierDNS,
Value: "good.bin.coffee",
}
var badIdent = core.AcmeIdentifier{
Type: core.IdentifierType("dns"),
Value: "bad.bin.coffee",
}
var authzGood = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: goodIdent,
Challenges: []core.Challenge{goodChalDNS},
}
va.validate(authzGood, 0)
if authzGood.Challenges[0].Status != core.StatusValid {
t.Logf("TestDNSValidationLive on Good did not succeed.")
}
badChalDNS, _ := core.DNSChallenge01(accountKey)
// This token is NOT set at _acme-challenge.bad.bin.coffee
badChalDNS.Token = "yfCBb-bRTLz8Wd1C0lTUQK3qlKj3-t2tYGwx5Hj7r_w"
var authzBad = core.Authorization{
ID: core.NewToken(),
RegistrationID: 1,
Identifier: badIdent,
Challenges: []core.Challenge{badChalDNS},
}
va.validate(authzBad, 0)
if authzBad.Challenges[0].Status != core.StatusInvalid {
t.Logf("TestDNSValidationLive on Bad did succeed inappropriately.")
}
}
type MockRegistrationAuthority struct {
lastAuthz *core.Authorization
}
func (ra *MockRegistrationAuthority) NewRegistration(reg core.Registration) (core.Registration, error) {
return reg, nil
}
func (ra *MockRegistrationAuthority) NewAuthorization(authz core.Authorization, regID int64) (core.Authorization, error) {
return authz, nil
}
func (ra *MockRegistrationAuthority) NewCertificate(req core.CertificateRequest, regID int64) (core.Certificate, error) {
return core.Certificate{}, nil
}
func (ra *MockRegistrationAuthority) UpdateRegistration(reg core.Registration, updated core.Registration) (core.Registration, error) {
return reg, nil
}
func (ra *MockRegistrationAuthority) UpdateAuthorization(authz core.Authorization, foo int, challenge core.Challenge) (core.Authorization, error) {
return authz, nil
}
func (ra *MockRegistrationAuthority) RevokeCertificateWithReg(cert x509.Certificate, reason core.RevocationCode, reg int64) error {
return nil
}
func (ra *MockRegistrationAuthority) AdministrativelyRevokeCertificate(cert x509.Certificate, reason core.RevocationCode, user string) error {
return nil
}
func (ra *MockRegistrationAuthority) OnValidationUpdate(authz core.Authorization) error {
ra.lastAuthz = &authz
return nil
}