CAA: Don't fail on critical iodef property tags (#6921)

RFC 8659 (CAA; https://www.rfc-editor.org/rfc/rfc8659) says that "A CA
MUST NOT issue certificates for any FQDN if the Relevant RRset for that
FQDN contains a CAA critical Property for an unknown or unsupported
Property Tag."

Let's Encrypt does technically support the iodef property tag: we
recognize it, but then ignore it and never choose to send notifications
to the given contact address. Historically, we have carried around the
iodef property tags in our internal structures as though we might use
them, but all code referencing them was essentially dead code.

As part of a set of simplifications,
https://github.com/letsencrypt/boulder/pull/6886 made it so that we
completely ignore iodef property tags. However, this had the unintended
side-effect of causing iodef property tags with the Critical bit set to
be counted as "unknown critical" tags, which prevent issuance.

This change causes our property tag parsing code to recognize iodef tags
again, so that critical iodef tags don't prevent issuance.
This commit is contained in:
Aaron Gable 2023-05-30 11:33:18 -07:00 committed by GitHub
parent b9eeb6ce1c
commit 2c9925797b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 77 additions and 0 deletions

View File

@ -104,6 +104,12 @@ func filterCAA(rrs []*dns.CAA) ([]*dns.CAA, []*dns.CAA, bool) {
issue = append(issue, caaRecord)
case "issuewild":
issuewild = append(issuewild, caaRecord)
case "iodef":
// We support the iodef property tag insofar as we recognize it, but we
// never choose to send notifications to the specified addresses. So we
// do not store the contents of the property tag, but also avoid setting
// the criticalUnknown bit if there are critical iodef tags.
continue
default:
// The critical flag is the bit with significance 128. However, many CAA
// record users have misinterpreted the RFC and concluded that the bit

View File

@ -637,6 +637,77 @@ func TestCAAFailure(t *testing.T) {
test.AssertEquals(t, prob.Type, probs.CAAProblem)
}
func TestFilterCAA(t *testing.T) {
testCases := []struct {
name string
input []*dns.CAA
expectedIssueVals []string
expectedWildVals []string
expectedCU bool
}{
{
name: "recognized non-critical",
input: []*dns.CAA{
{Tag: "issue", Value: "a"},
{Tag: "issuewild", Value: "b"},
{Tag: "iodef", Value: "c"},
},
expectedIssueVals: []string{"a"},
expectedWildVals: []string{"b"},
},
{
name: "recognized critical",
input: []*dns.CAA{
{Tag: "issue", Value: "a", Flag: 128},
{Tag: "issuewild", Value: "b", Flag: 128},
{Tag: "iodef", Value: "c", Flag: 128},
},
expectedIssueVals: []string{"a"},
expectedWildVals: []string{"b"},
},
{
name: "unrecognized non-critical",
input: []*dns.CAA{
{Tag: "unknown", Flag: 2},
},
},
{
name: "unrecognized critical",
input: []*dns.CAA{
{Tag: "unknown", Flag: 128},
},
expectedCU: true,
},
{
name: "unrecognized improper critical",
input: []*dns.CAA{
{Tag: "unknown", Flag: 1},
},
expectedCU: true,
},
{
name: "unrecognized very improper critical",
input: []*dns.CAA{
{Tag: "unknown", Flag: 9},
},
expectedCU: true,
},
}
for _, tc := range testCases {
t.Run(tc.name, func(t *testing.T) {
issue, wild, cu := filterCAA(tc.input)
for _, tag := range issue {
test.AssertSliceContains(t, tc.expectedIssueVals, tag.Value)
}
for _, tag := range wild {
test.AssertSliceContains(t, tc.expectedWildVals, tag.Value)
}
test.AssertEquals(t, tc.expectedCU, cu)
})
}
}
func TestSelectCAA(t *testing.T) {
expected := dns.CAA{Tag: "issue", Value: "foo"}