Wildcard cert plus apex domain fails DNS-01 challenge #287

Closed
opened 2025-12-29 01:21:15 +01:00 by adam · 2 comments
Owner

Originally created by @erorus on GitHub (Feb 27, 2018).

If you make a Let's Encrypt cert for *.example.com, it does not cover example.com. This is normal. Most wildcard certs from other providers include the naked/apex domain in the SAN to cover it.

The problem is that LE will validate both the wildcard and the apex domain using the same key: _acme-challenge.example.com, and dehydrated is set up to deploy all challenge tokens before validation, even with HOOK_CHAIN="no".

$ ./dehydrated -c -d '*.example.com' -d 'example.com' -t dns-01 -k ./hook.sh
Processing *.example.com with alternative names: example.com
 + Checking domain name(s) of existing cert... changed!
 + Domain name(s) are not matching!
 + Names in old certificate: *.example.com
 + Configured names: *.example.com example.com
 + Forcing renew.
 + Checking expire date of existing cert...
 + Valid till May 28 15:38:09 2018 GMT (Longer than 30 days). Ignoring because renew was forced!
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting new certificate order from CA...
 + Received 2 authorizations URLs from the CA
 + Handling authorization for example.com
 + Handling authorization for example.com
 + 2 pending challenge(s)
 + Deploying challenge tokens...
Creating challenge record for example.com in zone example.com
Created record: '_acme-challenge.example.com. 60 IN TXT "abc123"'
Waiting for sync.....................................
Completed
Creating challenge record for example.com in zone example.com
Created record: '_acme-challenge.example.com. 60 IN TXT "xyz987"'
Waiting for sync............................................
Completed
 + Responding to challenge for example.com authorization...
Failed to issue SSL cert for example.com: {
  "type": "dns-01",
  "status": "invalid",
  "error": {
    "type": "urn:ietf:params:acme:error:unauthorized",
    "detail": "Incorrect TXT record \"xyz987\" found at _acme-challenge.example.com",
    "status": 403
  },
  "url": "https://acme-staging-v02.api.letsencrypt.org/acme/challenge/...",
  "token": "...",
  "keyAuthorization": "..."
}
Originally created by @erorus on GitHub (Feb 27, 2018). If you make a Let's Encrypt cert for `*.example.com`, it does not cover `example.com`. This is normal. Most wildcard certs from other providers include the naked/apex domain in the SAN to cover it. The problem is that LE will validate both the wildcard and the apex domain using the same key: `_acme-challenge.example.com`, and dehydrated is set up to deploy all challenge tokens before validation, even with HOOK_CHAIN="no". ``` $ ./dehydrated -c -d '*.example.com' -d 'example.com' -t dns-01 -k ./hook.sh Processing *.example.com with alternative names: example.com + Checking domain name(s) of existing cert... changed! + Domain name(s) are not matching! + Names in old certificate: *.example.com + Configured names: *.example.com example.com + Forcing renew. + Checking expire date of existing cert... + Valid till May 28 15:38:09 2018 GMT (Longer than 30 days). Ignoring because renew was forced! + Signing domains... + Generating private key... + Generating signing request... + Requesting new certificate order from CA... + Received 2 authorizations URLs from the CA + Handling authorization for example.com + Handling authorization for example.com + 2 pending challenge(s) + Deploying challenge tokens... Creating challenge record for example.com in zone example.com Created record: '_acme-challenge.example.com. 60 IN TXT "abc123"' Waiting for sync..................................... Completed Creating challenge record for example.com in zone example.com Created record: '_acme-challenge.example.com. 60 IN TXT "xyz987"' Waiting for sync............................................ Completed + Responding to challenge for example.com authorization... Failed to issue SSL cert for example.com: { "type": "dns-01", "status": "invalid", "error": { "type": "urn:ietf:params:acme:error:unauthorized", "detail": "Incorrect TXT record \"xyz987\" found at _acme-challenge.example.com", "status": 403 }, "url": "https://acme-staging-v02.api.letsencrypt.org/acme/challenge/...", "token": "...", "keyAuthorization": "..." } ```
adam closed this issue 2025-12-29 01:21:15 +01:00
Author
Owner

@lukas2511 commented on GitHub (Feb 27, 2018):

Wildcard certificates are validated at the upper level of domain (e.g. *.example.com validates at example.com).

You can see that the script is actually trying to handle two authorizations for example.com, one is for the wildcard certificate, the other for the normal domain, and there is no way for dehydrated to know which one is which.
The corresponding tokens have to be in your DNS zone at the same time, or possibly one after another with very short TTLs or a 5 minute delay in the hook script, otherwise you'll have DNS caching issues.
My best guess would be that your hook-script is replacing the TXT record instead of appending new ones, this will result in a missing token.

It's confusing but there isn't really a good way around this, neither from the CA standpoint, nor inside dehydrated.

@lukas2511 commented on GitHub (Feb 27, 2018): Wildcard certificates are validated at the upper level of domain (e.g. `*.example.com` validates at `example.com`). You can see that the script is actually trying to handle two authorizations for `example.com`, one is for the wildcard certificate, the other for the normal domain, and there is no way for dehydrated to know which one is which. The corresponding tokens have to be in your DNS zone at the same time, or possibly one after another with very short TTLs or a 5 minute delay in the hook script, otherwise you'll have DNS caching issues. My best guess would be that your hook-script is replacing the TXT record instead of appending new ones, this will result in a missing token. It's confusing but there isn't really a good way around this, neither from the CA standpoint, nor inside dehydrated.
Author
Owner

@erorus commented on GitHub (Feb 27, 2018):

Ah yes, I had forgotten that you can have multiple TXT records with the same name. I just now told my hook script to append the record instead of replacing it. Both values were then in DNS, and both were accepted by LE.

 + Requesting new certificate order from CA...
 + Received 2 authorizations URLs from the CA
 + Handling authorization for example.com
 + Handling authorization for example.com
 + 2 pending challenge(s)
 + Deploying challenge tokens...
Creating challenge record for example.com in zone example.com
Created record: '_acme-challenge.example.com. 60 IN TXT "abc123"'
Waiting for sync........
Completed
Creating challenge record for example.com in zone example.com
Created record: '_acme-challenge.example.com. 60 IN TXT "xyz987"'
Waiting for sync........
Completed
 + Responding to challenge for example.com authorization...
Deleting challenge record for example.com from zone example.com
1 record sets deleted
 + Challenge is valid!
 + Responding to challenge for example.com authorization...
Deleting challenge record for example.com from zone example.com
Warning: no records matched - nothing deleted
 + Challenge is valid!
 + Requesting certificate...
 + Checking certificate...
 + Done!

Thanks for your quick reply. 👍

@erorus commented on GitHub (Feb 27, 2018): Ah yes, I had forgotten that you can have multiple TXT records with the same name. I just now told my hook script to append the record instead of [replacing it](https://github.com/whereisaaron/dehydrated-route53-hook-script/blob/570dd4c/hook.sh#L40). Both values were then in DNS, and both were accepted by LE. ``` + Requesting new certificate order from CA... + Received 2 authorizations URLs from the CA + Handling authorization for example.com + Handling authorization for example.com + 2 pending challenge(s) + Deploying challenge tokens... Creating challenge record for example.com in zone example.com Created record: '_acme-challenge.example.com. 60 IN TXT "abc123"' Waiting for sync........ Completed Creating challenge record for example.com in zone example.com Created record: '_acme-challenge.example.com. 60 IN TXT "xyz987"' Waiting for sync........ Completed + Responding to challenge for example.com authorization... Deleting challenge record for example.com from zone example.com 1 record sets deleted + Challenge is valid! + Responding to challenge for example.com authorization... Deleting challenge record for example.com from zone example.com Warning: no records matched - nothing deleted + Challenge is valid! + Requesting certificate... + Checking certificate... + Done! ``` Thanks for your quick reply. :+1:
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/dehydrated#287