tl;dr? Jump to recommendations!
Introduction
By default, any CA can issue certificates for any domain, and anyone in control of your IP address is able to request a certificate from such a CA.
DNS Certification Authority Authorization (CAA) Resource Records allow you to define limitations on this.
The standard is defined in RFC 8659, and public CAs are required1 to follow this standard.
The accounturi and validationmethods parameters of CAA are defined in RFC 8657.
Using CAA, you can restrict which CA is allowed to issue certificates, which validation method can be used, and which account is allowed to request certificates.
Why use CAA
The list of trusted CAs includes hundreds of CAs spread across the globe. A bug in just one of them might allow attackers to obtain a valid certificate for your domain. Bugs in CAs happen all the time.
Anyone in control of your domain’s IPv4 or IPv6 address can obtain a certificate. This includes all network providers along the path, hosting providers, and depends on their security from preventing other customers to take over your IP. Attackers may also use BGP hijacking to take over control of your IP.
-
In 2022, Twitter’s IP addresses and their traffic were taken over by a Russian ISP2. They could have obtained certificates during this time.
-
In 2023, attackers positioned themselves in front of both
xmpp.ruon Linode, andjabber.ruon Hetzner. They then requested certificates from Let’s Encrypt for these domains and were able to decrypt the traffic. The attack was only noticed when the attackers’ certificate expired.3
This attack would have been prevented by a CAA record with anaccounturi.
CAA overview
CAA records have a format of CAA <flags> <tag> <value>.
Mechanism
CAs will do a CAA lookup for the (sub)domain you’re requesting a certificate for.
If they don’t find one for sub.xyz.yourdomain.tld., they will they will climb up the DNS name tree and look for CAA records at xyz.yourdomain.tld., yourdomain.tld., and even tld.
Note that the climb will stop when any CAA records are found.
The iodef CAA record below is in effect for yourdomain.tld.. It also applies to foobar.yourdomain.tld., which has no CAA records. But it does not apply to sub.yourdomin.tld., as
a query for CAA sub.yourdomain.tld. only returns the issue CAA record.
yourdomain.tld. CAA 128 iodef "mailto:tls-abue@yourdomain.tld"
yourdomain.tld. CAA 128 issue "letsencrypt.org"
sub.yourdomain.tld. CAA 128 issue ";"
<flags>
This is a bit field number (0-255). At the time of writing, only bit 0 is defined as the critical flag. All undefined bits must be set to 0. So, currently, the value should be either 0 (critical=0) or 128 (critical=1).
The critical flag means that CAs must not issue a certificate unless they understand and support the property with this bit set.
Note that the meaning of additional parameters for issue and issuewild (such as accounturi) are determined by the CA and thus not affected by this flag. CAs that do not support a parameter may chose to ignore it, and still issue the certificate. Make sure the CA you’re using supports the parameters you’re using and, ideally, test them.
<tag> <value>
The original RFC defined 3 “Property Tags”: issue, issuewild, and iodef.
There is also issuemail4 which is used for S/MIME certificates and won’t be covered here.
The CA/Browser Forum also defined contactemail and contactphone, but I won’t cover them, either.
At the time of writing, there is a new draft RFC that introduces the security property. It features some promising additions such as renewal verification over HTTPS using the existing certificate or by signing a message with a key published in DNS.
As this can’t be used yet, I won’t cover it here. But I recommend monitoring the availability of these features.
The issue property tag
The issue property restricts which CAs are allowed to issue (both non-wildcard and wildcard) certificates.
The value is the CA’s domain, and may be followed by additional key=value parameters, separated by semicolons (;).
You can define multiple CAA issue records to allow multiple CAs:
yourdomain.tld. CAA 128 issue "letsencrypt.org"
yourdomain.tld. CAA 128 issue "digicert.com"
If you don’t want any certificates for a domain, you can also deny all CAs:
yourdomain.tld. CAA 128 issue ";"
The issuewild property tag
This property works the same as issue, but is only relevant for wildcard certificates. When you don’t specify any issuewild properties, the rules for wildcard certificates are taken from the issue properties. However, all issue properties are ignored for wildcard certificates when an issuewild property exists.
The following example allows Let’s Encrypt to issue only non-wildcard certificates, while the other CA to issue only wildcard certificates:
yourdomain.tld. CAA 128 issue "letsencrypt.org"
yourdomain.tld. CAA 128 issuewild "wildcard-ca.tld"
Parameters
Both issue and issuewild can have additional parameters. The parameters accounturi and validationmethods were defined in RFC 8657, but CAs may also support anything else.
Usually, these parameters further constrain the property they’re added to.
If you want to use both accounturi and validaionmethods, you need to define both in the same record and cannot spread them across two different issue or issuewild properties!
validationmethods=
This is a comma-separated list and allows restricting the validation methods to one or more methods.
The following example prevents the use of dns-01 (or any other supported validation method), except for the two listed below:
yourdomain.tld. CAA 128 issue "letsencrypt.org; validationmethods=http-01,tls-alpn-01"
accounturi=
This is probably the most interesting one, as it allows limiting certificate issuance to the specified ACME account, and therefore, the account key. It provides a strong protection against MITM attackers, which otherwise are not really hindered by CAA as they can just request the certificate from the same CA that you’re using.
yourdomain.tld. CAA 128 issue "letsencrypt.org; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1337"
The iodef property tag
The weird name stems from the Incident Object Description Exchange Format (IODEF), as described in RFC 7970. The property allows you to define mailto, http, or https URLs where you are alerted in case certificate issuance failed and/or violated the security policy.
yourdomain.tld. CAA 128 iodef "mailto:tls-abue@yourdomain.tld"
CAA recommendations
Simple example
This will probably do for most setups. It restricts certificates to yourdomain.tld. and www.yourdomain.tld.,
while only allowing the http-01 method with a certain Let’s Encrypt account.
Wildcard certificates and any other subdomains are not allowed.
yourdomain.tld. CAA 128 iodef "mailto:tls-abue@yourdomain.tld"
yourdomain.tld. CAA 128 issue "letsencrypt.org; validationmethods=http-01; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1337"
yourdomain.tld. CAA 128 issue "letsencrypt.org; validationmethods=http-01; accounturi=https://acme-staging-v02.api.letsencrypt.org/acme/acct/4711"
yourdomain.tld. CAA 128 issuewild ";"
www.yourdomain.tld. CNAME yourdomain.tld.
*.yourdomain.tld. CAA 128 iodef "mailto:tls-abue@yourdomain.tld"
*.yourdomain.tld. CAA 128 issue ";"
Find your accounturi
If you use certbot, you can use certbot show_account.
Otherwise, you may find it in the account.json or regr.json file used by various ACME clients.
It should look something like https://acme-v02.api.letsencrypt.org/acme/acct/1337.
The staging accounturi is different!
When you use Let’s Encrypt, the accounturi is different for their staging servers! A “dry” run against the Let’s Encrypt staging servers will fail CAA unless you’ve also added the staging URI. Note that both the hostname and the account number are different.
When using certbot, you can find the staging account URI using:
certbot show_account --server https://acme-staging-v02.api.letsencrypt.org/directory
It should look something like https://acme-staging-v02.api.letsencrypt.org/acme/acct/4711.
You can add multiple CAA issue/issuewild records with different accounturi parameters to support both.
Get notified
That’s the easiest thing you can do. Just add an iodef CAA record and provide an email address.
Make sure to add an iodef record to any subdomain that has another CAA record.
yourdomain.tld. CAA 128 iodef "mailto:tls-abue@yourdomain.tld"
Use critical flag
Always set the <flags> value to 128 instead of 0 for all your CAA records. This requires CAs to understand and support the record, or reject issuance otherwise.
- yourdomain.tld. CAA 0 issue "..."
+ yourdomain.tld. CAA 128 issue "..."
Use different accounts
If you manage multiple (sub)domains and request their certificates from different environments (servers, VMs, containers, etc.), consider registering different ACME accounts for each of them. This should be the default if the account key is not shared across your environments, but if you’re cloning templates or use something like Ansible you might be using the same account for all of them.
This protects against attackers requesting certificates for your other domains once they’ve pwned one environment.
Avoid and limit wildcard certificates
Unless you make use of a wildcard (“catch-all”) subdomain that needs TLS (as in https://random8280accdd24b.yourdomain.tld), or have good reason to prevent your subdomains from showing up in CT logs, you should not be using wildcard certificates. Being lazy because you manage thousands of subdomains and they change often means you need to improve your automation, and is not a good excuse. Avoid them, if you can.
To prevent wildcard issuance on a domain, but allow them for *.sub.yourdomain.tld:
yourdomain.tld. CAA 128 issuewild ";"
sub.yourdomain.tld. CAA 128 issuewild "letsencrypt.org; ..."
Protect unused subdomains
To prevent certificate issuance for unused / unknown subdomains, add a wildcard (“catch-all”) DNS record:
*.yourdomain.tld. CAA 128 issue ";"
*.yourdomain.tld. CAA 128 iodef "mailto:tls-abue@yourdomain.tld"
Full example
The following example
- Specifies
tls-abue@yourdomain.tldas the email address to receive validation failure notifications. - Only allows Let’s Encrypt account 1337 (4711 on staging) using
http-01to request certificates foryourdomain.tld.,www.yourdomain.tld.,abc.yourdomain.tld. - Does not allow wildcard certificates for
*.yourdomain.tld.or*.abc.yourdomain.tld. - Only allows Let’s Encrypt account 1337 (4711 on staging) to request non-wildcard certificates for
sub.yourdomain.tld., and only overhttp-01, - Only allows Let’s Encrypt account 1337 (4711 on staging) to request wildcard certificates for
*.sub.yourdomain.tld., and only overdns-01. - Does not allow any certificates for random / undefined subdomains.
yourdomain.tld. CAA 128 iodef "mailto:tls-abue@yourdomain.tld"
yourdomain.tld. CAA 128 issue "letsencrypt.org; validationmethods=http-01; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1337"
yourdomain.tld. CAA 128 issue "letsencrypt.org; validationmethods=http-01; accounturi=https://acme-staging-v02.api.letsencrypt.org/acme/acct/4711"
yourdomain.tld. CAA 128 issuewild ";"
www.yourdomain.tld. CNAME yourdomain.tld.
abc.yourdomain.tld. CAA 128 iodef "mailto:tls-abue@yourdomain.tld"
abc.yourdomain.tld. CAA 128 issue "letsencrypt.org; validationmethods=http-01; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1337"
abc.yourdomain.tld. CAA 128 issue "letsencrypt.org; validationmethods=http-01; accounturi=https://acme-staging-v02.api.letsencrypt.org/acme/acct/4711"
abc.yourdomain.tld. CAA 128 issuewild ";"
sub.yourdomain.tld. CAA 128 iodef "mailto:tls-abue@yourdomain.tld"
sub.yourdomain.tld. CAA 128 issue "letsencrypt.org; validationmethods=http-01; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1337"
sub.yourdomain.tld. CAA 128 issue "letsencrypt.org; validationmethods=http-01; accounturi=https://acme-staging-v02.api.letsencrypt.org/acme/acct/4711"
sub.yourdomain.tld. CAA 128 issuewild "letsencrypt.org; validationmethods=dns-01; accounturi=https://acme-v02.api.letsencrypt.org/acme/acct/1337"
sub.yourdomain.tld. CAA 128 issuewild "letsencrypt.org; validationmethods=dns-01; accounturi=https://acme-staging-v02.api.letsencrypt.org/acme/acct/4711"
*.yourdomain.tld. CAA 128 iodef "mailto:tls-abue@yourdomain.tld"
*.yourdomain.tld. CAA 128 issue ";"
Further hardening and limitations
- Use DNSSEC to secure your CAA and any other DNS records.
- Ensure your network provider uses RPKI to secure your IP addresses.
- Monitor CT logs for your domains. A tool like certspotter or one of the subscription services may help.
- CAA records only define restrictions for CAs which the CAs themselves must follow. A malicious or buggy CA that ignores CAA records might get its loicense revoked, but is able to produce a valid certificate until then.
- Relying on public CAs was a bad overall internet design choice. DNS-based Authentication of Named Entities (DANE) may help, it’s not supported by any mainstream browser.
- You do not fundamentally own and control your DNS records. The existing solutions to this, like
.onionaddresses are not human friendly. The problem is known as Zooko’s triangle; the Tor project has an overview of research projects that attempt to address this problem, but nothing is currently viable for the average end user.
Comments
Find me at https://mstdn.io/@jomo to comment!
-
Section 3.2.2.8 of the CA/Browser Forum’s Baseline Requirements for the Issuance and Management of Publicly-Trusted TLS Server Certificates ↩
-
Some Twitter traffic briefly funneled through Russian ISP, thanks to BGP mishap ↩
-
Encrypted traffic interception on Hetzner and Linode targeting the largest Russian XMPP (Jabber) messaging service ↩