I'm not a DNS person, in that I appreciate that it exists but am not up on the inner workings. It solves a lot of problems with dark magic I don't fully understand, and fortunately don't need to.

But Lucio noticed something that I do think is interesting, within the scope of the CAA record type.

The CAA record started with RFC6844, which was obsoleted by RFC8659. Both RFCs lay out the same core idea: you can add a CAA record to your DNS entries to say, "hey, this domain over here is allowed to issue certificates for me". That's the sort of thing that enables LetsEncrypt to hand out certs, and is an important part of why we can run HTTPS everywhere these days.

Now, RFC6844 has this in it:

Issuer Critical:  If set to '1', indicates that the corresponding
     property tag MUST be understood if the semantics of the CAA record
     are to be correctly interpreted by an issuer.
		Issuers MUST NOT issue certificates for a domain if the relevant
			CAA Resource Record set contains unknown property tags that have
			the Critical bit set.

The issuer critical flag means that the certificate issuer needs to validate your CAA record before it issues a certificate for you. There's more in the RFC about what exactly that means, but we don't care about those details for right now. The rule here is "set a flag to 1".

A little later in the RFC, the flag is described in more detail- as a bitmask. Specifically, bit 0 is the issuer critical flag. Bits 1-7 are reserved for future use.

Now, here's where we get into trouble, because programmers don't understand bits, and because the CAA record expects you to put an integer in this field. So, if you want issuer critical enabled, what value to you put in this field?

128, obviously. That's 10000000.

Except, if you don't understand bits, that's not obvious. A lot of people read this and decided that the documentation meant they needed to put 1 in the field- aka 00000001. This is wrong.

The updated RFC tries to explain it a bit more clearly:


    Bit 0, Issuer Critical Flag:
        If the value is set to "1", the Property is critical. 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. 

Note that according to the conventions set out in [RFC1035], bit 0 is the Most Significant Bit and bit 7 is the Least Significant Bit. Thus, according to those conventions, the Flags value 1 means that bit 7 is set, while a value of 128 means that bit 0 is set.

Now, pop quiz: what percentage of the people using this field have actually read the RFC? Not many. Probably a number that rounds down to zero, if we're being honest.

But now, let's say you're LetsEncrypt. You're supposed to be validating the CAA records of your customers if the bit is set, but a substantial portion of your customers are using it wrong. Do you: stand by the specification and tell them that they're wrong? Or say, "well, it's a reserved bit anyway, we'll (ab)use it and accept bad data".

Of course they'll accept bad data.

// filterCAA processes a set of CAA resource records and picks out the only bits
// we care about. It returns two slices of CAA records, representing the issue
// records and the issuewild records respectively, and a boolean indicating
// whether any unrecognized records had the critical bit set.
func filterCAA(rrs []*dns.CAA) ([]*dns.CAA, []*dns.CAA, bool) {
	var issue, issuewild []*dns.CAA
	var criticalUnknown bool

	for _, caaRecord := range rrs {
		switch strings.ToLower(caaRecord.Tag) {
		case "issue":
			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
		case "issuemail", "issuevmc":
			// We support these property tags insofar as we recognize them and
			// therefore do not bail out if someone has one marked critical. But
			// of course we do not do any further processing, as we do not issue
			// S/MIME or VMC certificates.
			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
			// with significance 1 is the critical bit. This is sufficiently
			// widespread that that bit must reasonably be considered an alias for
			// the critical bit. The remaining bits are 0/ignore as proscribed by the
			// RFC.
			if (caaRecord.Flag & (128 | 1)) != 0 {
				criticalUnknown = true
			}
		}
	}

	return issue, issuewild, criticalUnknown
}

Lucio writes:

Now I assume we all agree about the high wisdom of using bitmasks these days. Do we really need to save those bits at the price of a totally screwed up readability?

Now, I do like bitmasks, because I like the ability to trivially combine a bunch of values together with simple boolean operations, but I recognize that people can, and do screw it up. All the time. Am I going to say the DNS people were wrong for using a bitmask in their networking specification? No, I wouldn't go that far. But it certainly caused issues, and I do have to wonder: if you're treating 7 of 8 bits as reserved, maybe you should just have made it a flag?

[Advertisement] Keep all your packages and Docker containers in one place, scan for vulnerabilities, and control who can access different feeds. ProGet installs in minutes and has a powerful free version with a lot of great features that you can upgrade when ready.Learn more.