From a8f788c04630b954090e042e53561c88bc1eb09e Mon Sep 17 00:00:00 2001 From: Shiloh Heurich Date: Fri, 2 May 2025 14:43:43 -0400 Subject: [PATCH 1/5] feat: Add core definitions for dns-account-01 challenge type - Add `ChallengeTypeDNSAccount01` constant, `IsValid` update, and `RecordsSane` logic in `core/objects.go` - Add `DNSAccountChallenge01` function and handling in `core/challenges.go` - Add tests for the new challenge type in `core/core_test.go` and `core/objects_test.go` Implements core components for draft-ietf-acme-dns-account-label-00 --- core/challenges.go | 7 +++++++ core/core_test.go | 4 ++++ core/objects.go | 11 ++++++----- core/objects_test.go | 4 +++- 4 files changed, 20 insertions(+), 6 deletions(-) diff --git a/core/challenges.go b/core/challenges.go index d5e7a87295e..75a20373fb0 100644 --- a/core/challenges.go +++ b/core/challenges.go @@ -25,6 +25,11 @@ func TLSALPNChallenge01(token string) Challenge { return newChallenge(ChallengeTypeTLSALPN01, token) } +// DNSAccountChallenge01 constructs a dns-account-01 challenge. +func DNSAccountChallenge01(token string) Challenge { + return newChallenge(ChallengeTypeDNSAccount01, token) +} + // NewChallenge constructs a challenge of the given kind. It returns an // error if the challenge type is unrecognized. func NewChallenge(kind AcmeChallenge, token string) (Challenge, error) { @@ -35,6 +40,8 @@ func NewChallenge(kind AcmeChallenge, token string) (Challenge, error) { return DNSChallenge01(token), nil case ChallengeTypeTLSALPN01: return TLSALPNChallenge01(token), nil + case ChallengeTypeDNSAccount01: + return DNSAccountChallenge01(token), nil default: return Challenge{}, fmt.Errorf("unrecognized challenge type %q", kind) } diff --git a/core/core_test.go b/core/core_test.go index 889f9c9fea8..1b7ff0cb50a 100644 --- a/core/core_test.go +++ b/core/core_test.go @@ -32,12 +32,16 @@ func TestChallenges(t *testing.T) { dns01 := DNSChallenge01(token) test.AssertNotError(t, dns01.CheckPending(), "CheckConsistencyForClientOffer returned an error") + dnsAccount01 := DNSAccountChallenge01(token) + test.AssertNotError(t, dnsAccount01.CheckPending(), "CheckConsistencyForClientOffer returned an error") + tlsalpn01 := TLSALPNChallenge01(token) test.AssertNotError(t, tlsalpn01.CheckPending(), "CheckConsistencyForClientOffer returned an error") test.Assert(t, ChallengeTypeHTTP01.IsValid(), "Refused valid challenge") test.Assert(t, ChallengeTypeDNS01.IsValid(), "Refused valid challenge") test.Assert(t, ChallengeTypeTLSALPN01.IsValid(), "Refused valid challenge") + test.Assert(t, ChallengeTypeDNSAccount01.IsValid(), "Refused valid challenge") test.Assert(t, !AcmeChallenge("nonsense-71").IsValid(), "Accepted invalid challenge") } diff --git a/core/objects.go b/core/objects.go index 474d0bcba51..212f9fb8a8a 100644 --- a/core/objects.go +++ b/core/objects.go @@ -53,15 +53,16 @@ type AcmeChallenge string // These types are the available challenges const ( - ChallengeTypeHTTP01 = AcmeChallenge("http-01") - ChallengeTypeDNS01 = AcmeChallenge("dns-01") - ChallengeTypeTLSALPN01 = AcmeChallenge("tls-alpn-01") + ChallengeTypeHTTP01 = AcmeChallenge("http-01") + ChallengeTypeDNS01 = AcmeChallenge("dns-01") + ChallengeTypeTLSALPN01 = AcmeChallenge("tls-alpn-01") + ChallengeTypeDNSAccount01 = AcmeChallenge("dns-account-01") ) // IsValid tests whether the challenge is a known challenge func (c AcmeChallenge) IsValid() bool { switch c { - case ChallengeTypeHTTP01, ChallengeTypeDNS01, ChallengeTypeTLSALPN01: + case ChallengeTypeHTTP01, ChallengeTypeDNS01, ChallengeTypeTLSALPN01, ChallengeTypeDNSAccount01: return true default: return false @@ -228,7 +229,7 @@ func (ch Challenge) RecordsSane() bool { (ch.ValidationRecord[0].AddressUsed == netip.Addr{}) || len(ch.ValidationRecord[0].AddressesResolved) == 0 { return false } - case ChallengeTypeDNS01: + case ChallengeTypeDNS01, ChallengeTypeDNSAccount01: if len(ch.ValidationRecord) > 1 { return false } diff --git a/core/objects_test.go b/core/objects_test.go index 2d3194e633e..302934a669e 100644 --- a/core/objects_test.go +++ b/core/objects_test.go @@ -59,7 +59,7 @@ func TestChallengeSanityCheck(t *testing.T) { }`), &accountKey) test.AssertNotError(t, err, "Error unmarshaling JWK") - types := []AcmeChallenge{ChallengeTypeHTTP01, ChallengeTypeDNS01, ChallengeTypeTLSALPN01} + types := []AcmeChallenge{ChallengeTypeHTTP01, ChallengeTypeDNS01, ChallengeTypeTLSALPN01, ChallengeTypeDNSAccount01} for _, challengeType := range types { chall := Challenge{ Type: challengeType, @@ -152,6 +152,8 @@ func TestChallengeStringID(t *testing.T) { test.AssertEquals(t, ch.StringID(), "iFVMwA") ch.Type = ChallengeTypeHTTP01 test.AssertEquals(t, ch.StringID(), "0Gexug") + ch.Type = ChallengeTypeDNSAccount01 + test.AssertEquals(t, ch.StringID(), "8z2wSg") } func TestFindChallengeByType(t *testing.T) { From 3f0ddb929f28f8d136c48dd9f8a888930edec89d Mon Sep 17 00:00:00 2001 From: Shiloh Heurich Date: Fri, 2 May 2025 14:45:06 -0400 Subject: [PATCH 2/5] chore: Update vendor dependency `github.com/eggsampler/acme/v3` v3.6.2 --- go.mod | 2 +- go.sum | 4 ++-- vendor/github.com/eggsampler/acme/v3/Makefile | 3 ++- vendor/modules.txt | 2 +- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/go.mod b/go.mod index ec45ed35325..ec03f717288 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/aws/aws-sdk-go-v2/config v1.29.16 github.com/aws/aws-sdk-go-v2/service/s3 v1.80.2 github.com/aws/smithy-go v1.22.2 - github.com/eggsampler/acme/v3 v3.6.2-0.20250208073118-0466a0230941 + github.com/eggsampler/acme/v3 v3.6.2 github.com/go-jose/go-jose/v4 v4.1.0 github.com/go-logr/stdr v1.2.2 github.com/go-sql-driver/mysql v1.9.1 diff --git a/go.sum b/go.sum index ec2a222d1e1..209ba7d4481 100644 --- a/go.sum +++ b/go.sum @@ -70,8 +70,8 @@ github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZm github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= -github.com/eggsampler/acme/v3 v3.6.2-0.20250208073118-0466a0230941 h1:CnQwymLMJ3MSfjbZQ/bpaLfuXBZuM3LUgAHJ0gO/7d8= -github.com/eggsampler/acme/v3 v3.6.2-0.20250208073118-0466a0230941/go.mod h1:/qh0rKC/Dh7Jj+p4So7DbWmFNzC4dpcpK53r226Fhuo= +github.com/eggsampler/acme/v3 v3.6.2 h1:gvyZbQ92wNQLDASVftGpHEdFwPSfg0+17P0lLt09Tp8= +github.com/eggsampler/acme/v3 v3.6.2/go.mod h1:/qh0rKC/Dh7Jj+p4So7DbWmFNzC4dpcpK53r226Fhuo= github.com/fatih/color v1.9.0/go.mod h1:eQcE1qtQxscV5RaZvpXrrb8Drkc3/DdQ+uUYCNjL+zU= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= diff --git a/vendor/github.com/eggsampler/acme/v3/Makefile b/vendor/github.com/eggsampler/acme/v3/Makefile index c0f3c920f11..1f56d9b9b99 100644 --- a/vendor/github.com/eggsampler/acme/v3/Makefile +++ b/vendor/github.com/eggsampler/acme/v3/Makefile @@ -60,10 +60,11 @@ boulder_setup: -git clone --depth 1 https://github.com/letsencrypt/boulder.git $(BOULDER_PATH) (cd $(BOULDER_PATH); git checkout -f main && git reset --hard HEAD && git pull -q) make boulder_stop + (cd $(BOULDER_PATH); docker compose run --rm bsetup) # runs an instance of boulder boulder_start: - docker-compose -f $(BOULDER_PATH)/docker-compose.yml -f docker-compose.boulder-temp.yml up -d + docker-compose -f $(BOULDER_PATH)/docker-compose.yml -f $(BOULDER_PATH)/docker-compose.next.yml -f docker-compose.boulder-temp.yml up -d # waits until boulder responds boulder_wait: diff --git a/vendor/modules.txt b/vendor/modules.txt index dfe8b1d6172..fc8a4d04b7f 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -140,7 +140,7 @@ github.com/cespare/xxhash/v2 # github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f ## explicit github.com/dgryski/go-rendezvous -# github.com/eggsampler/acme/v3 v3.6.2-0.20250208073118-0466a0230941 +# github.com/eggsampler/acme/v3 v3.6.2 ## explicit; go 1.11 github.com/eggsampler/acme/v3 # github.com/felixge/httpsnoop v1.0.4 From 60e6787de1d9e320dc9b17aa3010eaab63345a19 Mon Sep 17 00:00:00 2001 From: Shiloh Heurich Date: Tue, 6 May 2025 12:40:48 -0400 Subject: [PATCH 3/5] feat: Add dns-account-01 challenge type to SA model - Add dns-account-01 challenge type to challTypeToUint map - Add dns-account-01 challenge type to uintToChallType map --- sa/model.go | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sa/model.go b/sa/model.go index 1fd481e9a76..bf2605d83ea 100644 --- a/sa/model.go +++ b/sa/model.go @@ -480,15 +480,17 @@ func modelToOrder(om *orderModel) (*corepb.Order, error) { } var challTypeToUint = map[string]uint8{ - "http-01": 0, - "dns-01": 1, - "tls-alpn-01": 2, + "http-01": 0, + "dns-01": 1, + "tls-alpn-01": 2, + "dns-account-01": 3, } var uintToChallType = map[uint8]string{ 0: "http-01", 1: "dns-01", 2: "tls-alpn-01", + 3: "dns-account-01", } var identifierTypeToUint = map[string]uint8{ From 6726be66e874d82a8350e01118d5e646a76d8c43 Mon Sep 17 00:00:00 2001 From: Shiloh Heurich Date: Tue, 27 May 2025 21:09:29 -0400 Subject: [PATCH 4/5] test: Add dns-account-01 challenge test case to TestGetValidAuthorizations2 Extends the existing TestGetValidAuthorizations2 function to verify that authorizations with dns-account-01 challenges can be properly stored in and retrieved from the database. --- sa/sa_test.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/sa/sa_test.go b/sa/sa_test.go index 570712d5b50..91c62416dd4 100644 --- a/sa/sa_test.go +++ b/sa/sa_test.go @@ -2639,6 +2639,35 @@ func TestGetValidAuthorizations2(t *testing.T) { aaa = am.ID } + var dac int64 + { + tokenStr := core.NewToken() + token, err := base64.RawURLEncoding.DecodeString(tokenStr) + test.AssertNotError(t, err, "computing test authorization challenge token") + + profile := "test" + attempted := challTypeToUint[string(core.ChallengeTypeDNSAccount01)] + attemptedAt := fc.Now() + vr, _ := json.Marshal([]core.ValidationRecord{}) + + am := authzModel{ + IdentifierType: identifierTypeToUint[string(identifier.TypeDNS)], + IdentifierValue: "aaa", + RegistrationID: 3, + CertificateProfileName: &profile, + Status: statusToUint[core.StatusValid], + Expires: fc.Now().Add(24 * time.Hour), + Challenges: 1 << challTypeToUint[string(core.ChallengeTypeDNSAccount01)], + Attempted: &attempted, + AttemptedAt: &attemptedAt, + Token: token, + ValidationError: nil, + ValidationRecord: vr, + } + err = sa.dbMap.Insert(context.Background(), &am) + test.AssertNotError(t, err, "failed to insert valid authz with dns-account-01") + dac = am.ID + } for _, tc := range []struct { name string regID int64 @@ -2655,6 +2684,14 @@ func TestGetValidAuthorizations2(t *testing.T) { validUntil: fc.Now().Add(time.Hour), wantIDs: []int64{aaa}, }, + { + name: "happy path, dns-account-01 challenge", + regID: 3, + identifiers: []*corepb.Identifier{identifier.NewDNS("aaa").ToProto()}, + profile: "test", + validUntil: fc.Now().Add(time.Hour), + wantIDs: []int64{dac}, + }, { name: "different identifier type", regID: 1, From ba43af28d2ce2d0737c0a1604d7e9e09b114431b Mon Sep 17 00:00:00 2001 From: Shiloh Heurich <1778483+sheurich@users.noreply.github.com> Date: Mon, 28 Jul 2025 19:45:59 -0400 Subject: [PATCH 5/5] Update sa/sa_test.go empty line between stanzas Co-authored-by: Aaron Gable --- sa/sa_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/sa/sa_test.go b/sa/sa_test.go index 97e43a2562b..b7c724638a6 100644 --- a/sa/sa_test.go +++ b/sa/sa_test.go @@ -2661,6 +2661,7 @@ func TestGetValidAuthorizations2(t *testing.T) { test.AssertNotError(t, err, "failed to insert valid authz with dns-account-01") dac = am.ID } + for _, tc := range []struct { name string regID int64