Validation error on acme-python DNS01 challenge

I'm using the acme-python library (at certbot/acme at master · certbot/certbot · GitHub) to set up a DNS01 challenge, validate, and try to generate a new cert, but I keep getting a validation error claiming that the TXT record I set is incorrect, e.g.:

'Incorrect TXT record "2djyxs8TfJZCVHMJ7XWI-JDMtkuMkv54PzSF3gS0yZc" found at _acme-challenge.wahooreview.com'

The acme-python docs don't include examples for DNS01, just HTTP01, so I'm not sure if I'm just not using the right method calls, or if something else is wrong. Could it be a problem on the staging url? Maybe I'm not giving AWS enough time to propogate the DNS changes before I trigger the challenge from LE?

What I'm really hoping for is to see some example code for the DNS01 challenge with acme-python - there are tons of examples for HTTP01, which is how I kludged this together.

Please fill out the fields below so we can help you better. Note: you must provide your domain name to get help. Domain names for issued certificates are all made public in Certificate Transparency logs (e.g. crt.sh | example.com), so withholding your domain name here does not increase secrecy, but only makes it harder for us to provide help.

My domain is: wahooreview.com (the site isn't up yet, I'm just testing automated code to generate certs using a DNS01 challenge)

I ran this command: not using the command line, this is a slightly truncated version of the code I'm running:

DIRECTORY_URL = 'https://acme-staging-v02.api.letsencrypt.org/directory'

# generate user key
user_key = josepy.JWKRSA(key=rsa.generate_private_key(public_exponent=65537, key_size=KEY_SIZE, backend=default_backend()))

# register account and accept TOS
net = client.ClientNetwork(user_key, user_agent=USER_AGENT)
directory = client.ClientV2.get_directory(DIRECTORY_URL, net)
client_acme = client.ClientV2(directory, net=net)
email = ('...')
registration_resource = client_acme.new_account(messages.NewRegistration.from_data(email=email, terms_of_service_agreed=True))
# RegistrationResource(body=Registration(key=JWKRSA(key=<ComparableRSAKey(<cryptography.hazmat.backends.openssl.rsa._RSAPublicKey object at 0x103462b20>)>), contact=('mailto:domain.names@voxmedia.com',), agreement=None, status='valid', terms_of_service_agreed=None, only_return_existing=None, external_account_binding=None), uri='https://acme-staging-v02.api.letsencrypt.org/acme/acct/102626374', new_authzr_uri=None, terms_of_service='https://letsencrypt.org/documents/LE-SA-v1.3-September-21-2022.pdf')

# create domain private key and CSR
pkey_pem, csr_pem = new_csr(domain)

orderr = client_acme.new_order(csr_pem)
# OrderResource(body=Order(identifiers=(Identifier(typ=IdentifierType(dns), value='wahooreview.com'),), status=Status(pending), authorizations=('https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/6526382684',), certificate=None, finalize='https://acme-staging-v02.api.letsencrypt.org/acme/finalize/102626374/8798900544', expires=datetime.datetime(2023, 5, 24, 23, 37, 22, tzinfo=<UTC>), error=None), uri='https://acme-staging-v02.api.letsencrypt.org/acme/order/102626374/8798900544', csr_pem=b'-----BEGIN CERTIFICATE REQUEST-----\nMIICcjCCAVoCAQAwADCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAK8D\n7b6S1sEDceuHwtQsc2B7bHV4ZfJaK3VIr+dynMBZWsGmXswe3kZ750zmCa7ZAWtd\nCsxyp1dTq1JQDyY+SssZyzZ1v34DFEqXAXP4ix1+r7tx2aFtechWUPEWPCD1StLN\ntN+TeRNAbK9aEuUDyEmc8EEZWjEGME4dEAift2kb5Xrb6Xbf5xx/HxjOwagVvQ/p\nCECjA1ZppdC+W1S/hCb9QzKy7nDZSM4js5FSsKNS/T16P7uYIDBY7+a/caIih2sO\no3sq/ZASX80SPwqLc0ym5OwtcWp75imBEIYEY/QhRpmi1SjDhZIG0DuH9C+qr8yo\nt0QqKYsTvkOK8YfxVz0CAwEAAaAtMCsGCSqGSIb3DQEJDjEeMBwwGgYDVR0RBBMw\nEYIPd2Fob29yZXZpZXcuY29tMA0GCSqGSIb3DQEBCwUAA4IBAQBxEK1THE1iMxNN\nML8sJJnCweZ/JEg7k+A0mbGXRloUyz0DVRejYlauRzvRd7iDeT03wy8nTzR3+1Nr\nX2w9MVBAls1LrPZX0D5mM93oOx1i+qcTAvwf1hOvcf1jAGB6okbqm1Ds03PhJcAH\nDBxeQ5UqeMMdI7ZSkIoKhtB3PnkwHUZm1hkrMSvXYtJ4ViUS6GSfX1TEGWy52Bb8\n1ze3P1X91bRGeCP5827XkMyHQQd9PS4K9N0Vc+kLQ/xckfeJPkDsqIPsxc55OZ6b\nuWuIwmpdMy4G6BlE8BHnvD9MMQxNobM0bEtpymosbKOoghegRa2XyWixKKaqiIdP\nTwf6JLuH\n-----END CERTIFICATE REQUEST-----\n', authorizations=[AuthorizationResource(body=Authorization(identifier=Identifier(typ=IdentifierType(dns), value='wahooreview.com'), challenges=(ChallengeBody(chall=HTTP01(token=b'\xd9\xd8\xf2\xc6\xcf\x13|\x96BTs\t\xedu\x88\xf8\x90\xcc\xb6K\x8c\x92\xfex?4\x85\xde\x04\xb4\xc9\x97'), uri='https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/6526382684/KyaQDg', status=Status(pending), validated=None, error=None), ChallengeBody(chall=DNS01(token=b'\xd9\xd8\xf2\xc6\xcf\x13|\x96BTs\t\xedu\x88\xf8\x90\xcc\xb6K\x8c\x92\xfex?4\x85\xde\x04\xb4\xc9\x97'), uri='https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/6526382684/VV90Jw', status=Status(pending), validated=None, error=None), ChallengeBody(chall=TLSALPN01(token=b'\xd9\xd8\xf2\xc6\xcf\x13|\x96BTs\t\xedu\x88\xf8\x90\xcc\xb6K\x8c\x92\xfex?4\x85\xde\x04\xb4\xc9\x97'), uri='https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/6526382684/BEAlog', status=Status(pending), validated=None, error=None)), status=Status(pending), expires=datetime.datetime(2023, 5, 24, 23, 37, 22, tzinfo=<UTC>), wildcard=None), uri='https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/6526382684', new_cert_uri=None)], fullchain_pem=None, alternative_fullchains_pem=None)

# calls to a method that extracts the DNS challenge from orderr.authorizations and gets the token
dns_challenge_object = get_dns_challenge(orderr)  
# ChallengeBody(chall=DNS01(token=b'\xd9\xd8\xf2\xc6\xcf\x13|\x96BTs\t\xedu\x88\xf8\x90\xcc\xb6K\x8c\x92\xfex?4\x85\xde\x04\xb4\xc9\x97'), uri='https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/6526382684/VV90Jw', status=Status(pending), validated=None, error=None)
# token - '2djyxs8TfJZCVHMJ7XWI-JDMtkuMkv54PzSF3gS0yZc'

# calls to a method that uses boto3 to update the DNS record
# waits until the correct token value is returned for TXT record _acme-challenge.wahooreview.com
ready_to_validate = update_dns(dns_challenge_object, domain)
# the recordset being added to AWS: 
# {'Name': '_acme-challenge.wahooreview.com', 'Type': 'TXT', 'ResourceRecords': [{'Value': '"2djyxs8TfJZCVHMJ7XWI-JDMtkuMkv54PzSF3gS0yZc"'}], 'TTL': 60}

if ready_to_validate:
    # pull response and validation keys from the challenge object
    response, validation = dns_challenge_object.response_and_validation(client_acme.net.key)
    # DNS01Response(key_authorization='2djyxs8TfJZCVHMJ7XWI-JDMtkuMkv54PzSF3gS0yZc.oMExAPbSLYEupWINv1LVA_sElKjjfaF9Q84hHRMVZ1w')
    # PmQkGprgk728dnuHi_mGaNSyyU-XSkckW62rU-DB_a0

    # trigger authorization
    challenge_resource = client_acme.answer_challenge(dns_challenge_object, response)
    # ChallengeResource(body=ChallengeBody(chall=DNS01(token=b'\xd9\xd8\xf2\xc6\xcf\x13|\x96BTs\t\xedu\x88\xf8\x90\xcc\xb6K\x8c\x92\xfex?4\x85\xde\x04\xb4\xc9\x97'), uri='https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/6526382684/VV90Jw', status=Status(pending), validated=None, error=None), authzr_uri='https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/6526382684')

    # wait for authorization to become valid
    deadline = datetime.datetime.now() + datetime.timedelta(seconds=180)
    try:
        finalized_order = client_acme.poll_and_finalize(orderr, deadline)
    except errors.ValidationError as e:
        print("ERROR", e.failed_authzrs)

It produced this output:

ERROR [AuthorizationResource(body=Authorization(identifier=Identifier(typ=IdentifierType(dns), value='wahooreview.com'), challenges=(ChallengeBody(chall=DNS01(token=b'\xd9\xd8\xf2\xc6\xcf\x13|\x96BTs\t\xedu\x88\xf8\x90\xcc\xb6K\x8c\x92\xfex?4\x85\xde\x04\xb4\xc9\x97'), uri='https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/6526382684/VV90Jw', status=Status(invalid), validated=datetime.datetime(2023, 5, 17, 23, 40, 1, tzinfo=<UTC>), error=Error(typ='urn:ietf:params:acme:error:unauthorized', title=None, detail='Incorrect TXT record "2djyxs8TfJZCVHMJ7XWI-JDMtkuMkv54PzSF3gS0yZc" found at _acme-challenge.wahooreview.com', identifier=None, subproblems=None)),), status=Status(invalid), expires=datetime.datetime(2023, 5, 24, 23, 37, 22, tzinfo=<UTC>), wildcard=None), uri='https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/6526382684', new_cert_uri=None)]

However:

$ dig TXT _acme-challenge.wahooreview.com
...
;; ANSWER SECTION:
_acme-challenge.wahooreview.com. 300 IN	TXT	"2djyxs8TfJZCVHMJ7XWI-JDMtkuMkv54PzSF3gS0yZc"

My web server is (include version): n/a, testing locally on OS X

The operating system my web server runs on is (include version): n/a, testing locally on OS X

My hosting provider, if applicable, is: AWS

I can login to a root shell on my machine (yes or no, or I don't know): n/a, using DNS01 challenge

I'm using a control panel to manage my site (no, or provide the name and version of the control panel): Rt53 (AWS)

The version of my client is (e.g. output of certbot --version or certbot-auto --version if you're using Certbot): n/a, I'm not using the cli tool, but the acme-python library at certbot/acme at master · certbot/certbot · GitHub

validation is the value you want to use for the DNS record.

Curiously, I answered this same question yesterday.

8 Likes

Thank you so much - that's exactly what I needed.

2 Likes

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