diff --git a/test/integration/common_test.go b/test/integration/common_test.go index 377aa0650..fa9600a36 100644 --- a/test/integration/common_test.go +++ b/test/integration/common_test.go @@ -90,6 +90,23 @@ func delHTTP01Response(token string) error { return nil } +func addCAAIssueRecord(host string, issue string) error { + resp, err := http.Post("http://boulder.service.consul:8055/add-caa", "", + bytes.NewBufferString(fmt.Sprintf(`{ + "host": "%s", + "policies": [{"tag": "issue", "value": "%s"}] + }`, host, issue))) + if err != nil { + return fmt.Errorf("adding CAA record: %s", err) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("adding CAA record: status %d", resp.StatusCode) + } + return nil +} + func makeClientAndOrder(c *client, csrKey *ecdsa.PrivateKey, idents []acme.Identifier, cn bool, profile string, certToReplace *x509.Certificate) (*client, *acme.Order, error) { var err error if c == nil { diff --git a/test/integration/validation_test.go b/test/integration/validation_test.go new file mode 100644 index 000000000..3740a2750 --- /dev/null +++ b/test/integration/validation_test.go @@ -0,0 +1,95 @@ +//go:build integration + +package integration + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "database/sql" + "strings" + "testing" + "time" + + "github.com/eggsampler/acme/v3" + "github.com/letsencrypt/boulder/test/vars" +) + +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 = addHTTP01Response(chal.Token, chal.KeyAuthorization) + if err != nil { + t.Fatalf("setting HTTP-01 challenge token: %s", err) + } + defer delHTTP01Response(chal.Token) + + 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 = addCAAIssueRecord(domain, ";") + if err != nil { + t.Fatalf("updating CAA record: %s", 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) + } +} diff --git a/test/v2_integration.py b/test/v2_integration.py index ab76ca303..4789c8b95 100644 --- a/test/v2_integration.py +++ b/test/v2_integration.py @@ -1170,49 +1170,6 @@ def test_expiration_mailer(): if mailcount != 2: raise(Exception("\nExpiry mailer failed: expected 2 emails, got %d" % mailcount)) -caa_recheck_setup_data = {} -@register_twenty_days_ago -def caa_recheck_setup(): - client = chisel2.make_client() - # Issue a certificate with the clock set back, and save the authzs to check - # later that they are valid (200). They should however require rechecking for - # CAA purposes. - numNames = 10 - # Generate numNames subdomains of a random domain - base_domain = random_domain() - domains = [ "{0}.{1}".format(str(n),base_domain) for n in range(numNames) ] - order = chisel2.auth_and_issue(domains, client=client) - - global caa_recheck_setup_data - caa_recheck_setup_data = { - 'client': client, - 'authzs': order.authorizations, - } - -def test_recheck_caa(): - """Request issuance for a domain where we have a old cached authz from when CAA - was good. We'll set a new CAA record forbidding issuance; the CAA should - recheck CAA and reject the request. - """ - if 'authzs' not in caa_recheck_setup_data: - raise(Exception("CAA authzs not prepared for test_caa")) - domains = [] - for a in caa_recheck_setup_data['authzs']: - response = caa_recheck_setup_data['client']._post(a.uri, None) - if response.status_code != 200: - raise(Exception("Unexpected response for CAA authz: ", - response.status_code)) - domain = a.body.identifier.value - domains.append(domain) - - # Set a forbidding CAA record on just one domain - challSrv.add_caa_issue(domains[3], ";") - - # Request issuance for the previously-issued domain name, which should - # now be denied due to CAA. - chisel2.expect_problem("urn:ietf:params:acme:error:caa", - lambda: chisel2.auth_and_issue(domains, client=caa_recheck_setup_data['client'])) - def test_caa_good(): domain = random_domain() challSrv.add_caa_issue(domain, "happy-hacker-ca.invalid")