From ba8673801641764c1d2b94ecf0da637f23e81977 Mon Sep 17 00:00:00 2001 From: Jaime Hablutzel Date: Mon, 24 Aug 2026 11:50:08 -0300 Subject: [PATCH] va: Match CAA parameter tags case insensitively RFC 8659 Section 4.1 makes matching of CAA Property Tags case insensitive, but neither RFC 8659 nor RFC 8657 specifies a comparison rule for parameter tags such as "accounturi" and "validationmethods" (the grammar only constrains the allowed characters). Lowercase these parameter tags before matching, so a differently-cased tag still restricts issuance instead of being treated as an unrecognized parameter. For both parameters, ignoring a differently-cased tag drops a restriction the subscriber intended and produces a false-positive validation. For example, an unrecognized "AccountURI" tag would make the Property match any account, and an unrecognized "ValidationMethods" tag would permit any validation method. https://www.rfc-editor.org/rfc/rfc8659#section-4.1 https://www.rfc-editor.org/rfc/rfc8659#section-4.2 https://www.rfc-editor.org/rfc/rfc8657#section-3 https://www.rfc-editor.org/rfc/rfc8657#section-4 --- va/caa.go | 10 ++++++++-- va/caa_test.go | 30 ++++++++++++++++++++++++++++++ 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/va/caa.go b/va/caa.go index da41ad5305c..1e21915cd8e 100644 --- a/va/caa.go +++ b/va/caa.go @@ -494,7 +494,12 @@ func caaAccountURIMatches(caaParams []caaParameter, accountURIPrefixes []string, var found bool var accountURI string for _, c := range caaParams { - if c.tag == "accounturi" { + // RFC 8659 Section 4.1 makes matching of Property Tags case insensitive but + // specifies no comparison rule for parameter tags. We lowercase the parameter + // tag as a safety choice: treating a differently-cased "accounturi" as an + // unrecognized parameter would drop the restriction the subscriber intended + // and make this Property match any account, a false-positive validation. + if strings.ToLower(c.tag) == "accounturi" { if found { // A Property with multiple "accounturi" parameters is // unsatisfiable. @@ -536,7 +541,8 @@ func caaValidationMethodMatches(caaParams []caaParameter, method core.AcmeChalle var validationMethods string var found bool for _, param := range caaParams { - if param.tag == "validationmethods" { + // Match the parameter tag case insensitively, as in caaAccountURIMatches. + if strings.ToLower(param.tag) == "validationmethods" { if found { // RFC 8657 does not define what behavior to take when multiple // "validationmethods" parameters exist, but we make the diff --git a/va/caa_test.go b/va/caa_test.go index 4d5922ced04..b86ef1f2825 100644 --- a/va/caa_test.go +++ b/va/caa_test.go @@ -1421,6 +1421,24 @@ func TestAccountURIMatches(t *testing.T) { id: 654321, want: false, }, + { + name: "mixed-case tag matches the expected account", + params: []caaParameter{{tag: "AccountURI", val: "https://acme-v02.api.letsencrypt.org/acme/acct/123456"}}, + prefixes: []string{ + "https://acme-v02.api.letsencrypt.org/acme/acct/", + }, + id: 123456, + want: true, + }, + { + name: "mixed-case tag still restricts a non-matching account", + params: []caaParameter{{tag: "AccountURI", val: "https://acme-v02.api.letsencrypt.org/acme/acct/123456"}}, + prefixes: []string{ + "https://acme-v02.api.letsencrypt.org/acme/acct/", + }, + id: 654321, + want: false, + }, } for _, tc := range tests { @@ -1536,6 +1554,18 @@ func TestValidationMethodMatches(t *testing.T) { method: core.ChallengeTypeTLSALPN01, want: false, }, + { + name: "mixed-case tag matches the method in use", + params: []caaParameter{{tag: "ValidationMethods", val: "dns-01"}}, + method: core.ChallengeTypeDNS01, + want: true, + }, + { + name: "mixed-case tag still restricts a non-matching method", + params: []caaParameter{{tag: "ValidationMethods", val: "dns-01"}}, + method: core.ChallengeTypeHTTP01, + want: false, + }, } for _, tc := range tests {