Dns-01 invalid challenge

Checked all name servers with dig and waited more then 24 hours before continuing

dig txt _acme-challenge.randonneurs.nl +short @ns0.transip.net.
"_J0q6byNEqrwuO7WO7XW9s8-QYvt0A37WV1S_HF3QXs"

./dehydrated -c

# INFO: Using main config file /Users/gert/Desktop/dehydrated/config
Processing randonneurs.nl with alternative names: form.randonneurs.nl
 + Signing domains...
 + Generating private key...
 + Generating signing request...
 + Requesting challenge for randonneurs.nl...
 + Requesting challenge for form.randonneurs.nl...

Add the following to the zone definition of randonneurs.nl:
_acme-challenge.randonneurs.nl. IN TXT "_J0q6byNEqrwuO7WO7XW9s8-QYvt0A37WV1S_HF3QXs"

Press enter to continue...

 + Responding to challenge for randonneurs.nl...

Now you can remove the following from the zone definition of randonneurs.nl:
_acme-challenge.randonneurs.nl. IN TXT "_J0q6byNEqrwuO7WO7XW9s8-QYvt0A37WV1S_HF3QXs"

Press enter to continue...

Unknown hook "invalid_challenge"

domain.txt

randonneurs.nl form.randonneurs.nl

config

# Resolve names to addresses of IP version only. (curl)
# supported values: 4, 6
# default: <unset>
#IP_VERSION=

# Path to certificate authority (default: https://acme-v01.api.letsencrypt.org/directory)
#CA="https://acme-v01.api.letsencrypt.org/directory"

# Path to certificate authority license terms redirect (default: https://acme-v01.api.letsencrypt.org/terms)
#CA_TERMS="https://acme-v01.api.letsencrypt.org/terms"

# Path to license agreement (default: <unset>)
#LICENSE=""

# Which challenge should be used? Currently http-01 and dns-01 are supported
CHALLENGETYPE="dns-01"

# Path to a directory containing additional config files, allowing to override
# the defaults found in the main configuration file. Additional config files
# in this directory needs to be named with a '.sh' ending.
# default: <unset>
#CONFIG_D=

# Base directory for account key, generated certificates and list of domains (default: $SCRIPTDIR -- uses config directory if undefined)
#BASEDIR=$SCRIPTDIR

# File containing the list of domains to request certificates for (default: $BASEDIR/domains.txt)
#DOMAINS_TXT="${BASEDIR}/domains.txt"

# Output directory for generated certificates
#CERTDIR="${BASEDIR}/certs"

# Directory for account keys and registration information
#ACCOUNTDIR="${BASEDIR}/accounts"

# Output directory for challenge-tokens to be served by webserver or deployed in HOOK (default: /var/www/dehydrated)
WELLKNOWN="${BASEDIR}/acme"

# Default keysize for private keys (default: 4096)
#KEYSIZE="4096"

# Path to openssl config file (default: <unset> - tries to figure out system default)
#OPENSSL_CNF=

# Program or function called in certain situations
#
# After generating the challenge-response, or after failed challenge (in this case altname is empty)
# Given arguments: clean_challenge|deploy_challenge altname token-filename token-content
#
# After successfully signing certificate
# Given arguments: deploy_cert domain path/to/privkey.pem path/to/cert.pem path/to/fullchain.pem
#
# BASEDIR and WELLKNOWN variables are exported and can be used in an external program
# default: <unset>
HOOK="${BASEDIR}/../dns-01-manual/hook.sh"

# Chain clean_challenge|deploy_challenge arguments together into one hook call per certificate (default: no)
#HOOK_CHAIN="no"

# Minimum days before expiration to automatically renew certificate (default: 30)
#RENEW_DAYS="30"

# Regenerate private keys instead of just signing new certificates on renewal (default: yes)
#PRIVATE_KEY_RENEW="yes"

# Create an extra private key for rollover (default: no)
#PRIVATE_KEY_ROLLOVER="no"

# Which public key algorithm should be used? Supported: rsa, prime256v1 and secp384r1
#KEY_ALGO=rsa

# E-mail to use during the registration (default: <unset>)
CONTACT_EMAIL=""

# Lockfile location, to prevent concurrent access (default: $BASEDIR/lock)
#LOCKFILE="${BASEDIR}/lock"

# Option to add CSR-flag indicating OCSP stapling to be mandatory (default: no)
#OCSP_MUST_STAPLE="no"

hook.sh

set -e
set -u
set -o pipefail

case "$1" in
	"deploy_challenge")
		echo ""
		echo "Add the following to the zone definition of ${2}:"
		echo "_acme-challenge.${2}. IN TXT \"${4}\""
		echo ""
		echo -n "Press enter to continue..."
		read tmp
		echo ""
	;;
	"clean_challenge")
		echo ""
		echo "Now you can remove the following from the zone definition of ${2}:"
		echo "_acme-challenge.${2}. IN TXT \"${4}\""
		echo ""
		echo -n "Press enter to continue..."
		read tmp
		echo ""
	;;
	"deploy_cert")
		# do nothing for now
	;;
	"unchanged_cert")
		# do nothing for now
	;;
	*)
		echo "Unknown hook \"${1}\""
		exit 1
	;;
esac

exit 0

Let’s Encrypt returns more details about the validation error it encounters. Your client doesn’t seem to log that information by default. If I’m reading the code correctly, that’s up to the hook. This sample hook shows what this could look like for invalid_challenge. I would suggest adding similar code to your hook to see the exact error and then go from there.

ok thanks i made a new hook.sh and got more details

#!/usr/bin/env bash
# https://github.com/lukas2511/dehydrated/blob/master/docs/examples/hook.sh

set -eu -o pipefail

deploy_challenge() {
		local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
		echo ""
		echo "Add the following to the zone definition of ${1}:"
		echo "_acme-challenge.${1}. IN TXT \"${3}\""
		echo ""
		echo -n "Press enter to continue..."
		read tmp
		echo ""
}

clean_challenge() {
		local DOMAIN="${1}" TOKEN_FILENAME="${2}" TOKEN_VALUE="${3}"
		echo ""
		echo "Now you can remove the following from the zone definition of ${1}:"
		echo "_acme-challenge.${1}. IN TXT \"${3}\""
		echo ""
		echo -n "Press enter to continue..."
		read tmp
		echo ""
}

deploy_cert() {
		local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}" TIMESTAMP="${6}"
		echo ""
		echo "deploy_cert()"
		echo ""
}

unchanged_cert() {
    local DOMAIN="${1}" KEYFILE="${2}" CERTFILE="${3}" FULLCHAINFILE="${4}" CHAINFILE="${5}"
		echo ""
		echo "unchanged_cert()"
		echo ""
}

invalid_challenge() {
    local DOMAIN="${1}" RESPONSE="${2}"
		echo ""
		echo "invalid_challenge()"
		echo "${1}"
		echo "${2}"
		echo ""
}

request_failure() {
    local STATUSCODE="${1}" REASON="${2}" REQTYPE="${3}"
		echo ""
		echo "request_failure()"
		echo "${1}"
		echo "${2}"
		echo "${3}"
		echo ""
}

exit_hook() {
		echo ""
		echo "done"
		echo ""
}

HANDLER="$1"; shift
if [[ "${HANDLER}" =~ ^(deploy_challenge|clean_challenge|deploy_cert|unchanged_cert|invalid_challenge|request_failure|exit_hook)$ ]]; then
  "$HANDLER" "$@"
fi
% 
invalid_challenge()
randonneurs.nl
{
  "type": "dns-01",
  "status": "invalid",
  "error": {
    "type": "urn:acme:error:connection",
    "detail": "DNS problem: SERVFAIL looking up TXT for _acme-challenge.randonneurs.nl",
    "status": 400
  },
  "uri": "https://acme-v01.api.letsencrypt.org/acme/challenge/atXDK2lS6E08OdtMhubO1FWGkzfKLYzRxGtkW7mUHsM/699408428",
  "token": "CSheYk-NB_fUQVx-cskitOUO4I2gli5VOXDBMTKnxj4",
  "keyAuthorization": "CSheYk-NB_fUQVx-cskitOUO4I2gli5VOXDBMTKnxj4.Q4Z83NlDZ1dy-hzmrOGyCHRROtkaKFS9VMU_3rDeEB8"
}

What does DNS problem: SERVFAIL mean?
Note I don’t have access to the dns server right now so I just tried with a new token that i know is not the same to test the new script, but i am beginning to think it’s not even reading the token at all but LE having issues with some Secure DNS feature or something, can you please maybe use dig to look at the other dns records that could potentially influence verification?

1 Like

Interesting - I checked the usual problems that folks have with their DNS servers when we see a SERVFAIL on TXT lookup and didn’t catch anything immediately wrong (checked TCP, UDP, mixed case, DNSSEC, wrong nameservers, etc).

The logs server-side don’t reveal an immediate cause either - as returned in the error we seem to be getting a SERVFAIL on the response.

@pfg @mnordhoff @jsha you folks have a good eye for DNS issues - do you spot anything here?

It does look like a DNSSEC issue - but only for the _acme-challenge.randonneurs.nl TXT record. Google’s HTTP interface for their DNS returns "DNSSEC validation failure. Please check http://dnsviz.net/d/_acme-challenge.randonneurs.nl/dnssec/.", see https://dns.google.com/query?name=_acme-challenge.randonneurs.nl&type=TXT&dnssec=true.

I assume the RRSIG for that record is somehow invalid, but I haven’t found any tooling that would allow me to confirm what the exact problem is. :unamused:

1 Like

Interesting - when I checked the DNSSEC settings I did it for the base randonneurs.nl domain only.

Doesn't give any validation errors here? Also, it asks for A and AAAA RR, not TXT, so could be misguided result.

Edit:
Used the "advanced" analyser and added the TXT record. You can now refresh the results and indeed now I see:

RRSIG _acme-challenge.randonneurs.nl/TXT alg 7, id 55172: The cryptographic signature of the RRSIG RR does not properly validate.

I guess this is an error in TransIP's generation of the RRSIG? @gert should probably file a bug report @ TransIP, perhaps linking to this thread :slight_smile:

6 Likes

Good find, I was about ready to give up finding a tool that returns a more useful error message for anything that’s not an A/AAAA record.

1 Like

Ok i am on it. Filing a bug report to TransIP. Thanks!

Nice sleuthing @Osiris!

2 Likes

I tried contacting TransIP several times now, no response… I guess they can’t fix it without breaking their entire dns system.

Finally got response, apparently it had to do with the usage of double quotes, I had to remove them for it to work.

So no DNSSEC problem? :fearful: Although it cĂłuld be both, the previous found error is gone too :stuck_out_tongue::http://dnsviz.net/d/_acme-challenge.randonneurs.nl/dnssec/

I believe with or without double quotes it should have generated a valid dnssec key right? So I assume it’s their dns server that can’t handle double quotes. That said how does let’s encrypt handle the double quotes exactly, I assume for let’s encrypt the double quotes don’t matter and works either way?

This topic was automatically closed 30 days after the last reply. New replies are no longer allowed.