Generate dns-01 TXT challenge with ACME-python


I’m using acme-python library and I have problems passing the TXT validation of a domain. I construct the TXT value in two steps

  1. key_authorization = token || '.' || base64(JWK_Thumbprint(accountKey))
  2. base64(sha256(key_authorization))

are they correct?

also attaching my testing code

import logging
import os
import pkg_resources
import hashlib
import json

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
import OpenSSL

from acme import client
from acme import messages
from acme import jose

BITS = 2048  # minimum for Boulder

# generate_private_key requires cryptography>=0.5
key = jose.JWKRSA(key=rsa.generate_private_key(
acme = client.Client(DIRECTORY_URL, key)

regr = acme.register()

DOMAIN = ''  # is ignored by Boulder

authzr = acme.request_challenges(
    identifier=messages.Identifier(typ=messages.IDENTIFIER_FQDN, value=DOMAIN),

for challenge in authzr.body.challenges:
    challenge = challenge.to_json()
    if challenge['type'] == 'dns-01':
        token = challenge['token']

# token || '.' || base64(JWK_Thumbprint(accountKey))
thumbprint = key.thumbprint(hash_function=hashes.SHA256)
key_authorization = token + '.' + jose.b64encode(thumbprint).decode()
# base64(sha256(key_authorization))
validation = jose.b64encode(hashlib.sha256(key_authorization.encode()).digest()).decode()

print('_acme-challenge.' + DOMAIN + ' IN TXT ' + validation)

authzr, authzr_response = acme.poll(authzr)
for challenge in json.loads(authzr_response.content.decode())['challenges']:
    if challenge['type'] == 'dns-01':

DIG: 3599 IN TXT “8bGFl9SNhZzukcwdR7e52gFwq6HaEHB43LbimZQwnLg”

don't know whether you already figured it out or not. I managed to implement it this way.
As soon as I have the right challenge object from the offer, I do:

response, validation = challenge.response_and_validation(
# the validation is the token value to put into the txt entry, do it here
challenge = client_acme.answer_challenge(challenge, response)
finalized_order = client_acme.poll_and_finalize(order)

The finalized_order variable has the fullchain_pem attribute which includes all the certificates.
You can refer to certbot/ at 1d2540629f22d5061892edba2f3db29ab7ccc4c3 · certbot/certbot · GitHub to get an understanding how e.g. client_acme is created.

Hi there @nichil and welcome to the community,

Do you realise you're reacting to a post which is more than 6 years old? The acme library has come a long way since then and the code from OP is probably not relevant any longer.

Nowadays threads are automatically closed if there hasn't been a new post for 30 days. But back in 2016 this wasn't the case. However, as this thread would have been closed under the new "rules", I'm going to close this thread now, especially as I don't think after 6 years this is going to be a fruitfull discussion. Feel free to contact me if you think I made a mistake :slight_smile: