boulder/test/integration/validation_test.go

346 lines
9.1 KiB
Go

//go:build integration
package integration
import (
"crypto/ecdsa"
"crypto/elliptic"
"crypto/rand"
"database/sql"
"slices"
"strings"
"testing"
"time"
"github.com/eggsampler/acme/v3"
"github.com/miekg/dns"
challtestsrvclient "github.com/letsencrypt/boulder/test/chall-test-srv-client"
"github.com/letsencrypt/boulder/test/vars"
)
var expectedUserAgents = []string{"boulder", "remoteva-a", "remoteva-b", "remoteva-c"}
func collectUserAgentsFromDNSRequests(requests []challtestsrvclient.DNSRequest) []string {
userAgents := make([]string, len(requests))
for i, request := range requests {
userAgents[i] = request.UserAgent
}
return userAgents
}
func assertUserAgentsLength(t *testing.T, got []string, checkType string) {
t.Helper()
if len(got) != 4 {
t.Errorf("During %s, expected 4 User-Agents, got %d", checkType, len(got))
}
}
func assertExpectedUserAgents(t *testing.T, got []string, checkType string) {
t.Helper()
for _, ua := range expectedUserAgents {
if !slices.Contains(got, ua) {
t.Errorf("During %s, expected User-Agent %q in %s (got %v)", checkType, ua, expectedUserAgents, got)
}
}
}
func TestMPICTLSALPN01(t *testing.T) {
t.Parallel()
client, err := makeClient()
if err != nil {
t.Fatalf("creating acme client: %s", err)
}
domain := randomDomain(t)
order, err := client.Client.NewOrder(client.Account, []acme.Identifier{{Type: "dns", Value: domain}})
if err != nil {
t.Fatalf("creating order: %s", err)
}
authz, err := client.Client.FetchAuthorization(client.Account, order.Authorizations[0])
if err != nil {
t.Fatalf("fetching authorization: %s", err)
}
chal, ok := authz.ChallengeMap[acme.ChallengeTypeTLSALPN01]
if !ok {
t.Fatalf("no TLS-ALPN-01 challenge found in %#v", authz)
}
_, err = testSrvClient.AddARecord(domain, []string{"64.112.117.134"})
if err != nil {
t.Fatalf("adding A record: %s", err)
}
defer func() {
testSrvClient.RemoveARecord(domain)
}()
_, err = testSrvClient.AddTLSALPN01Response(domain, chal.KeyAuthorization)
if err != nil {
t.Fatal(err)
}
defer func() {
_, err = testSrvClient.RemoveTLSALPN01Response(domain)
if err != nil {
t.Fatal(err)
}
}()
chal, err = client.Client.UpdateChallenge(client.Account, chal)
if err != nil {
t.Fatalf("completing TLS-ALPN-01 validation: %s", err)
}
validationEvents, err := testSrvClient.TLSALPN01RequestHistory(domain)
if err != nil {
t.Fatal(err)
}
if len(validationEvents) != 4 {
t.Errorf("expected 4 validation events got %d", len(validationEvents))
}
dnsEvents, err := testSrvClient.DNSRequestHistory(domain)
if err != nil {
t.Fatal(err)
}
var caaEvents []challtestsrvclient.DNSRequest
for _, event := range dnsEvents {
if event.Question.Qtype == dns.TypeCAA {
caaEvents = append(caaEvents, event)
}
}
assertUserAgentsLength(t, collectUserAgentsFromDNSRequests(caaEvents), "CAA check")
assertExpectedUserAgents(t, collectUserAgentsFromDNSRequests(caaEvents), "CAA check")
}
func TestMPICDNS01(t *testing.T) {
t.Parallel()
client, err := makeClient()
if err != nil {
t.Fatalf("creating acme client: %s", err)
}
domain := randomDomain(t)
order, err := client.Client.NewOrder(client.Account, []acme.Identifier{{Type: "dns", Value: domain}})
if err != nil {
t.Fatalf("creating order: %s", err)
}
authz, err := client.Client.FetchAuthorization(client.Account, order.Authorizations[0])
if err != nil {
t.Fatalf("fetching authorization: %s", err)
}
chal, ok := authz.ChallengeMap[acme.ChallengeTypeDNS01]
if !ok {
t.Fatalf("no DNS challenge found in %#v", authz)
}
_, err = testSrvClient.AddDNS01Response(domain, chal.KeyAuthorization)
if err != nil {
t.Fatal(err)
}
defer func() {
_, err = testSrvClient.RemoveDNS01Response(domain)
if err != nil {
t.Fatal(err)
}
}()
chal, err = client.Client.UpdateChallenge(client.Account, chal)
if err != nil {
t.Fatalf("completing DNS-01 validation: %s", err)
}
challDomainDNSEvents, err := testSrvClient.DNSRequestHistory("_acme-challenge." + domain)
if err != nil {
t.Fatal(err)
}
var validationEvents []challtestsrvclient.DNSRequest
for _, event := range challDomainDNSEvents {
if event.Question.Qtype == dns.TypeTXT && event.Question.Name == "_acme-challenge."+domain+"." {
validationEvents = append(validationEvents, event)
}
}
assertUserAgentsLength(t, collectUserAgentsFromDNSRequests(validationEvents), "DNS-01 validation")
assertExpectedUserAgents(t, collectUserAgentsFromDNSRequests(validationEvents), "DNS-01 validation")
domainDNSEvents, err := testSrvClient.DNSRequestHistory(domain)
if err != nil {
t.Fatal(err)
}
var caaEvents []challtestsrvclient.DNSRequest
for _, event := range domainDNSEvents {
if event.Question.Qtype == dns.TypeCAA {
caaEvents = append(caaEvents, event)
}
}
assertUserAgentsLength(t, collectUserAgentsFromDNSRequests(caaEvents), "CAA check")
assertExpectedUserAgents(t, collectUserAgentsFromDNSRequests(caaEvents), "CAA check")
}
func TestMPICHTTP01(t *testing.T) {
t.Parallel()
client, err := makeClient()
if err != nil {
t.Fatalf("creating acme client: %s", err)
}
domain := randomDomain(t)
order, err := client.Client.NewOrder(client.Account, []acme.Identifier{{Type: "dns", Value: domain}})
if err != nil {
t.Fatalf("creating order: %s", err)
}
authz, err := client.Client.FetchAuthorization(client.Account, order.Authorizations[0])
if err != nil {
t.Fatalf("fetching authorization: %s", err)
}
chal, ok := authz.ChallengeMap[acme.ChallengeTypeHTTP01]
if !ok {
t.Fatalf("no HTTP challenge found in %#v", authz)
}
_, err = testSrvClient.AddHTTP01Response(chal.Token, chal.KeyAuthorization)
if err != nil {
t.Fatal(err)
}
defer func() {
_, err = testSrvClient.RemoveHTTP01Response(chal.Token)
if err != nil {
t.Fatal(err)
}
}()
chal, err = client.Client.UpdateChallenge(client.Account, chal)
if err != nil {
t.Fatalf("completing HTTP-01 validation: %s", err)
}
validationEvents, err := testSrvClient.HTTPRequestHistory(domain)
if err != nil {
t.Fatal(err)
}
var validationUAs []string
for _, event := range validationEvents {
if event.URL == "/.well-known/acme-challenge/"+chal.Token {
validationUAs = append(validationUAs, event.UserAgent)
}
}
assertUserAgentsLength(t, validationUAs, "HTTP-01 validation")
assertExpectedUserAgents(t, validationUAs, "HTTP-01 validation")
dnsEvents, err := testSrvClient.DNSRequestHistory(domain)
if err != nil {
t.Fatal(err)
}
var caaEvents []challtestsrvclient.DNSRequest
for _, event := range dnsEvents {
if event.Question.Qtype == dns.TypeCAA {
caaEvents = append(caaEvents, event)
}
}
assertUserAgentsLength(t, collectUserAgentsFromDNSRequests(caaEvents), "CAA check")
assertExpectedUserAgents(t, collectUserAgentsFromDNSRequests(caaEvents), "CAA check")
}
func TestCAARechecking(t *testing.T) {
t.Parallel()
domain := randomDomain(t)
idents := []acme.Identifier{{Type: "dns", Value: domain}}
// Create an order and authorization, and fulfill the associated challenge.
// This should put the authz into the "valid" state, since CAA checks passed.
client, err := makeClient()
if err != nil {
t.Fatalf("creating acme client: %s", err)
}
order, err := client.Client.NewOrder(client.Account, idents)
if err != nil {
t.Fatalf("creating order: %s", err)
}
authz, err := client.Client.FetchAuthorization(client.Account, order.Authorizations[0])
if err != nil {
t.Fatalf("fetching authorization: %s", err)
}
chal, ok := authz.ChallengeMap[acme.ChallengeTypeHTTP01]
if !ok {
t.Fatalf("no HTTP challenge found in %#v", authz)
}
_, err = testSrvClient.AddHTTP01Response(chal.Token, chal.KeyAuthorization)
if err != nil {
t.Fatal(err)
}
defer func() {
_, err = testSrvClient.RemoveHTTP01Response(chal.Token)
if err != nil {
t.Fatal(err)
}
}()
chal, err = client.Client.UpdateChallenge(client.Account, chal)
if err != nil {
t.Fatalf("completing HTTP-01 validation: %s", err)
}
// Manipulate the database so that it looks like the authz was validated
// more than 8 hours ago.
db, err := sql.Open("mysql", vars.DBConnSAIntegrationFullPerms)
if err != nil {
t.Fatalf("sql.Open: %s", err)
}
_, err = db.Exec(`UPDATE authz2 SET attemptedAt = ? WHERE identifierValue = ?`, time.Now().Add(-24*time.Hour).Format(time.DateTime), domain)
if err != nil {
t.Fatalf("updating authz attemptedAt timestamp: %s", err)
}
// Change the CAA record to now forbid issuance.
_, err = testSrvClient.AddCAAIssue(domain, ";")
if err != nil {
t.Fatal(err)
}
// Try to finalize the order created above. Due to our db manipulation, this
// should trigger a CAA recheck. And due to our challtestsrv manipulation,
// that CAA recheck should fail. Therefore the whole finalize should fail.
key, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader)
if err != nil {
t.Fatalf("generating cert key: %s", err)
}
csr, err := makeCSR(key, idents, false)
if err != nil {
t.Fatalf("generating finalize csr: %s", err)
}
_, err = client.Client.FinalizeOrder(client.Account, order, csr)
if err == nil {
t.Errorf("expected finalize to fail, but got success")
}
if !strings.Contains(err.Error(), "CAA") {
t.Errorf("expected finalize to fail due to CAA, but got: %s", err)
}
}