In a quite recent event, the CA/Browser Forum decided that there’s a need for a way for CAs (Certificate Authorities) to check if they are allowed to issue certificates for a certain domain. And in a even more recent move, since September 8th, 2017, all CAs are required to preform these checks before issuing any new certificates.
Now, you might be wondering, how does these checks work? Well, by a “new” DNS record type, a CAA (Certificate Authority Authorisation) record! The CAA record builds on the concept of a “domain owner”, aka. the entity that has the power to edit the DNS records for a particular zone, whereby the owner of a domain can state from whom they normally procure certificates. By publishing a CAA record, a domain owner can limit which CAs can issue certificates for their domain, and limit anyone else from acquiring fraudulent certificates from another CA.
CAA Record Format
The process for implementing CAA is quite easy. RFC 6844 has standardised a record type, CAA, that has a priority flag, a property tag, and a value for the property.
The generic form is:
CAA <flags> <tag> <value>
- Flag Byte – an unsigned integer between 0-255
- Currently only used for the critical flag, 0, which means the CA must understand the following property tag before issuing a certificate.
- Property Tag – 3 are currently defined; “issue”, “issuewild” and “iodef”
- Tag Value – The value corresponding to the property tag
This might sound a bit complex, but it really isn’t. Basically, the flag is (as of February 2018) currently only used for the critical status, so it’s always 0. The property tags are as follows;
- issue – Defines the CA that has the right to issue certificates for the domain
- issuewild – Defines a CA that has the right to issue wildcard certificates for the domain
- iodef – Contact information for where to report newly issued certificates, or failed attempts to issue where the CA isn’t listed.
You might wonder why the critical flag needs to be there at all. It’s a clever way to allow extensions of the CAA format in the future. Basically, a record with a critical flag byte set, means that the CA must understand what the property tag means before issuing a certificate. That means, if the IETF adds a new property tag in the future that’s very important, the critical flag byte ensures that even if the CA hasn’t implemented the new property tag yet, and as such doesn’t understand it, they know that they shouldn’t issue any certificates until they understand what it means. This is basically “fail-safe”, aka. no certificate is better than a “bad” certificate.
So, in my examples we’ll be using symantec.com as our CA, mostly because of the recent attention they’ve gotten, and Let’s Encrypt because they are awesome!
Let’s say I want to keep it simple, and I want to get all my certificates from one CA. Then I would add the following record for grumpytechie.net.
grumpytechie.net CAA 0 issue "symantec.com"
This allows only Symantec’s CA to issue certificates for my domain, and effectively locks out all other CAs.
I could also want to get certificates from Let’s Encrypt. Then I need to add a new record for Let’s Encrypt as well.
grumpytechie.net CAA 0 issue "symantec.com" grumpytechie.net CAA 0 issue "letsencrypt.org"
This allows both CAs to issue certificates for the whole zone of grumpytechie.net.
Let’s say I want to enable Symantec to issue certificates for my whole zone, but I want to use Let’s Encrypt only for testing purposes on the subdomain beta. Then I would state the following in my DNS.
grumpytechie.net CAA 0 issue "symantec.com" beta.grumpytechie.net CAA 0 issue "symantec.com" beta.grumpytechie.net CAA 0 issue "letsencrypt.org"
This limits Let’s Encrypt to the beta subdomain, but allows Symantec to issue certificates for both the root and the beta. subdomain. Have a look at the last section of this post, as there’s some nuances to these types of configurations.
Wildcards – Simple (caveat)
Wildcard certificates can also be explicitly stated as follows.
grumpytechie.net CAA 0 issue "symantec.com" grumpytechie.net CAA 0 issuewild "symantec.com"
There’s a caveat here though. As long as you only use issue properties, it’s assumed that these CAs are also allowed to issue wildcard certificates. However, as soon as you publish a single record with the issuewild property you remove this assumption. The above example doesn’t really affect anything, and the issuewild is superfluous at this point, but in the next example it’ll become more clear.
Wildcards – Complicated
Let’s say I have multiple CAs that I use for whatever purpose, and I add the issuewild property as follows:
grumpytechie.net CAA 0 issue "symantec.com" grumpytechie.net CAA 0 issue "letsencrypt.org" grumpytechie.net CAA 0 issuewild "symantec.com"
This is a bit dangerous if you’re not familiar with how the issuewild property works. The configuration above would allow only Symantec to issue wildcard certificates and block Let’s Encrypt. This might be your intention, but it can be confusing as both Symantec and Let’s Encrypt would be able to issue wildcard certificates if the last record was missing.
Another caveat is that the issuewild property by itself only allows a CA to issue wildcard certificates.
grumpytechie.net CAA 0 issue "letsencrypt.org" grumpytechie.net CAA 0 issuewild "symantec.com"
So these records would only allow Let’s Encrypt to issue normal, specific certificates, and only allow Symantec to issue wildcard certificates. This might lead to problems with some CAs, as when you request a wildcard certificate they also issue a normal certificate for the root of the wildcard.
The last property tag, iodef, allows you to specify how CAs should get in touch with you when they issue a new certificate for your domain, and if they can’t issue a new certificate because it conflicts with your current CAA records. This feature isn’t fully supported by all CAs, but it’s good practice to add it none the less.
There’s two options, by email as follows, do note the mailto:;
grumpytechie.net CAA 0 iodef "mailto:email@example.com"
or through RID messages if you want to be fancy;
grumpytechie.net CAA 0 iodef "https://caa.grumpytechie.net"
Block Everything and Log
An especially neat thing you could do with CAA records is to block every CA from issuing certificates for a particular domain, and then ask them to report everything.
grumpytechie.net CAA 0 issue ";" grumpytechie.net CAA 0 iodef "mailto:firstname.lastname@example.org" grumpytechie.net CAA 0 iodef "https://caa.grumpytechie.net"
The issue “;” represents an empty issuers list.
Subdomains and Recursive Parsing
Another, a tad complicated, thing is how CAA records are interpreted. Here’s an example:
grumpytechie.net CAA 0 issue "symantec.com" foo.grumpytechie.net CAA 0 issue "letsencrypt.org" bar.grumpytechie.net CAA 0 issue "symantec.com" bar.grumpytechie.net CAA 0 issue "letsencrypt.org"
In the above example, Symantec is the “default” CA for the domain. And for the bar. subdomain, both Symantec and Let’s Encrypt can issue certificates. But, and this is where it’s not totally obvious, only Let’s Encrypt is allowed to issue certificates for the foo. subdomain, as the CA always looks for the most specific CAA record. This means that Symantec in this case would be blocked, and there exists no record for foo. with the “symantec.com” value. This can lead to some interesting issues if you’re not careful when editing complex deployments.
But what about a subdomain without a specific CAA record? The way it works is that if you ask Symantec for a certificate for beta.foobar.grumpytechie.net. for example, Symantec check first for a CAA record for beta.foobar.grumpytechie.net., then foobar.grumpytechie.net., then grumpytechie.net.
In the example above, it would eventually find the record for grumpytechie.net. and issue the certificate for the subdomain. Let’s Encrypt on the other hand wouldn’t issue the certificate, as there exists no CAA records for them for any of the domains in the chain. A request to Let’s Encrypt for beta.bar.grumpytechie.net. would go through on the other hand.
One thing I haven’t been able to confirm is what happens if the domain doesn’t match in iodef property values. For example, let’s say I have a lot of domains that I manage, and I want to get reports of CAA events to the same email or server for all of them. As I understand the configuration aspects, it shouldn’t matter what domain is inside the value for the iodef property. I hope to test this soon, and will update this post when I get around to it.