From e263ebd77ca9c3ab9f7336a591cf46286e535bca Mon Sep 17 00:00:00 2001 From: Fernandez Ludovic Date: Wed, 25 Feb 2026 19:01:09 +0100 Subject: [PATCH] refactor: simplify record matching --- .../dnspersist01/dns_persist_challenge.go | 44 +++++++------------ challenge/dnspersist01/issue_values.go | 37 +++++++++++----- challenge/dnspersist01/issue_values_test.go | 18 ++++---- 3 files changed, 52 insertions(+), 47 deletions(-) diff --git a/challenge/dnspersist01/dns_persist_challenge.go b/challenge/dnspersist01/dns_persist_challenge.go index 4a917aa54..64997ed43 100644 --- a/challenge/dnspersist01/dns_persist_challenge.go +++ b/challenge/dnspersist01/dns_persist_challenge.go @@ -170,36 +170,24 @@ func (c *Challenge) selectIssuerDomainName(challIssuers []string, records []TXTR } func (c *Challenge) hasMatchingRecord(records []TXTRecord, issuerDomainName string, wildcard bool) bool { - for _, record := range records { - parsed, err := parseIssueValue(record.Value) - if err != nil { - continue - } - - if parsed.IssuerDomainName != issuerDomainName { - continue - } - - if parsed.AccountURI != c.accountURI { - continue - } - - if wildcard && !strings.EqualFold(parsed.Policy, policyWildcard) { - continue - } - - if c.persistUntil.IsZero() { - if !parsed.PersistUntil.IsZero() { - continue - } - } else if parsed.PersistUntil.IsZero() || !parsed.PersistUntil.Equal(c.persistUntil) { - continue - } - - return true + iv := IssueValue{ + IssuerDomainName: issuerDomainName, + AccountURI: c.accountURI, + PersistUntil: c.persistUntil, } - return false + if wildcard { + iv.Policy = policyWildcard + } + + return slices.ContainsFunc(records, func(record TXTRecord) bool { + parsed, err := parseIssueValue(record.Value) + if err != nil { + return false + } + + return parsed.match(iv) + }) } // GetAuthorizationDomainName returns the fully qualified DNS label diff --git a/challenge/dnspersist01/issue_values.go b/challenge/dnspersist01/issue_values.go index 535fb905f..1aa2869fd 100644 --- a/challenge/dnspersist01/issue_values.go +++ b/challenge/dnspersist01/issue_values.go @@ -1,6 +1,7 @@ package dnspersist01 import ( + "cmp" "errors" "fmt" "strconv" @@ -23,6 +24,22 @@ type IssueValue struct { PersistUntil time.Time } +func (v *IssueValue) match(other IssueValue) bool { + if cmp.Or( + cmp.Compare(v.IssuerDomainName, other.IssuerDomainName), + cmp.Compare(v.AccountURI, other.AccountURI), + v.PersistUntil.Compare(other.PersistUntil), + ) != 0 { + return false + } + + if strings.EqualFold(other.Policy, policyWildcard) && !strings.EqualFold(v.Policy, policyWildcard) { + return false + } + + return true +} + // buildIssueValue constructs an RFC 8659 issue-value for a dns-persist-01 TXT record. // issuerDomainName and accountURI are required. // wildcard and persistUntil are optional. @@ -54,15 +71,15 @@ func buildIssueValue(issuerDomainName, accountURI string, wildcard bool, persist // It returns an error if any portion of the value is malformed. // //nolint:gocyclo // parsing and validating tagged parameters requires branching -func parseIssueValue(value string) (IssueValue, error) { +func parseIssueValue(value string) (*IssueValue, error) { fields := strings.Split(value, ";") issuerDomainName := trimWSP(fields[0]) if issuerDomainName == "" { - return IssueValue{}, errors.New("missing issuer-domain-name") + return nil, errors.New("missing issuer-domain-name") } - parsed := IssueValue{ + parsed := &IssueValue{ IssuerDomainName: issuerDomainName, } @@ -72,24 +89,24 @@ func parseIssueValue(value string) (IssueValue, error) { for _, raw := range fields[1:] { part := trimWSP(raw) if part == "" { - return IssueValue{}, errors.New("empty parameter or trailing semicolon provided") + return nil, errors.New("empty parameter or trailing semicolon provided") } // Capture each tag=value pair. tag, val, found := strings.Cut(part, "=") if !found { - return IssueValue{}, fmt.Errorf("malformed parameter %q should be tag=value pair", part) + return nil, fmt.Errorf("malformed parameter %q should be tag=value pair", part) } tag = trimWSP(tag) if tag == "" { - return IssueValue{}, fmt.Errorf("malformed parameter %q, empty tag", part) + return nil, fmt.Errorf("malformed parameter %q, empty tag", part) } canonicalTag := strings.ToLower(tag) if seenTags[canonicalTag] { - return IssueValue{}, fmt.Errorf("duplicate parameter %q", tag) + return nil, fmt.Errorf("duplicate parameter %q", tag) } seenTags[canonicalTag] = true @@ -102,7 +119,7 @@ func parseIssueValue(value string) (IssueValue, error) { continue } - return IssueValue{}, fmt.Errorf("malformed value %q for tag %q", val, tag) + return nil, fmt.Errorf("malformed value %q for tag %q", val, tag) } // Finally, capture expected tag values. @@ -111,7 +128,7 @@ func parseIssueValue(value string) (IssueValue, error) { switch canonicalTag { case paramAccountURI: if val == "" { - return IssueValue{}, fmt.Errorf("empty value provided for mandatory %q", paramAccountURI) + return nil, fmt.Errorf("empty value provided for mandatory %q", paramAccountURI) } parsed.AccountURI = val @@ -130,7 +147,7 @@ func parseIssueValue(value string) (IssueValue, error) { case paramPersistUntil: ts, err := strconv.ParseInt(val, 10, 64) if err != nil { - return IssueValue{}, fmt.Errorf("malformed %q: %w", paramPersistUntil, err) + return nil, fmt.Errorf("malformed %q: %w", paramPersistUntil, err) } parsed.PersistUntil = time.Unix(ts, 0).UTC() diff --git a/challenge/dnspersist01/issue_values_test.go b/challenge/dnspersist01/issue_values_test.go index 99fd6387c..204c7ebf4 100644 --- a/challenge/dnspersist01/issue_values_test.go +++ b/challenge/dnspersist01/issue_values_test.go @@ -67,13 +67,13 @@ func Test_parseIssueValue(t *testing.T) { testCases := []struct { desc string value string - expected IssueValue + expected *IssueValue expectErrContains string }{ { desc: "basic", value: "authority.example; accounturi=https://authority.example/acct/123", - expected: IssueValue{ + expected: &IssueValue{ IssuerDomainName: "authority.example", AccountURI: "https://authority.example/acct/123", }, @@ -81,7 +81,7 @@ func Test_parseIssueValue(t *testing.T) { { desc: "wildcard policy is case-insensitive", value: "authority.example; accounturi=https://authority.example/acct/123; policy=wIlDcArD", - expected: IssueValue{ + expected: &IssueValue{ IssuerDomainName: "authority.example", AccountURI: "https://authority.example/acct/123", Policy: "wIlDcArD", @@ -90,7 +90,7 @@ func Test_parseIssueValue(t *testing.T) { { desc: "unknown param", value: "authority.example; accounturi=https://authority.example/acct/123; extra=value", - expected: IssueValue{ + expected: &IssueValue{ IssuerDomainName: "authority.example", AccountURI: "https://authority.example/acct/123", }, @@ -98,7 +98,7 @@ func Test_parseIssueValue(t *testing.T) { { desc: "unknown tag with empty value", value: "authority.example; accounturi=https://authority.example/acct/123; foo=", - expected: IssueValue{ + expected: &IssueValue{ IssuerDomainName: "authority.example", AccountURI: "https://authority.example/acct/123", }, @@ -106,7 +106,7 @@ func Test_parseIssueValue(t *testing.T) { { desc: "unknown tags with unusual formatting are ignored", value: "authority.example;accounturi=https://authority.example/acct/123;bad tag=value;\nweird=\\x01337", - expected: IssueValue{ + expected: &IssueValue{ IssuerDomainName: "authority.example", AccountURI: "https://authority.example/acct/123", }, @@ -114,7 +114,7 @@ func Test_parseIssueValue(t *testing.T) { { desc: "all known fields with heavy whitespace", value: " authority.example ; accounturi = https://authority.example/acct/123 ; policy = wildcard ; persistUntil = 4102444800 ", - expected: IssueValue{ + expected: &IssueValue{ IssuerDomainName: "authority.example", AccountURI: "https://authority.example/acct/123", Policy: "wildcard", @@ -124,7 +124,7 @@ func Test_parseIssueValue(t *testing.T) { { desc: "policy other than wildcard is treated as absent", value: "authority.example; accounturi=https://authority.example/acct/123; policy=notwildcard", - expected: IssueValue{ + expected: &IssueValue{ IssuerDomainName: "authority.example", AccountURI: "https://authority.example/acct/123", }, @@ -132,7 +132,7 @@ func Test_parseIssueValue(t *testing.T) { { desc: "missing accounturi", value: "authority.example", - expected: IssueValue{ + expected: &IssueValue{ IssuerDomainName: "authority.example", }, },