DNS Verification

I’ve been trying to work through the DNS challenge verification on the staging ACME server and have a few questions:

  1. If the challenge URI is responding with a 202 and invalid status, does that mean it’s polling DNS and it will eventually go to valid assuming the correct TXT record is in place? What’s the time horizon and polling frequency on this validation?

  2. I’m confused about what exactly should go in the TXT record. The key authorization, as I understand it, is a concatenation of the challenge token, a period, and the JWK thumbprint for the account key.

    Is the TXT record then meant to be a base64 encoding of the SHA256 hash of the entire key authorization? What’s the need for the extra level of obfuscation here since the token is randomly assigned to each challenge and the thumbprint is only based on the public key and therefore contains no secret information?

  3. I was able to successfully provide a key authorization but only when using the go-jose library used by boulder. When I tried to create a thumbprint using node-jose I was unable to reproduce an identical thumbprint. Is anyone aware of a Node.js library that can successfully generate an acme-compatible thumbprint?

If I can get these answered, I think I’ll have all I need to build the solution I’m looking to build. Thanks!

First off, please note the DNS challenge support currently in staging is broken. We recently merged some fixes, which should go out soon.

Nope, challenges that go to invalid never become valid. They go pending -> valid or pending -> invalid.

Yes. Specifically the base64url encoding. The hashing is not to hide information, but is intended to match the format in the tls-sni challenge, where there is a limit on the character space and length. It might be worth proposing a change in the IETF ACME WG that this should match the HTTP challenge instead, and skip the hashing step.

Take a look at test/js in the Boulder repo. Not really set up as a library, but you can compare and reuse code as needed.



Hi Jacob, thanks for clarifying! Regarding my first question, does this mean that DNS verification is a one-time deal? As in, either it works the first time you say you’re ready or you have to issue a new request (and therefore get a different token which means I have to change the record)? This seems problematic – what if I change my DNS and it’s correct but the DNS hasn’t propagated to where Let’s Encrypt picks it up? What if I made a small typo and just want to correct it?

Regarding the value of the TXT record, so that I’m clear the current way to do this is basically:

base64(sha256(token + "." + fingerprint))

Yep, that’s currently the way it works. Agreed that this may be more problematic in the DNS challenge, where propagation can take some time, than in the HTTP and TLS-SNI challenges. FWIW, Let’s Encrypt always does authoritative resolution, so it’s mainly about whether your authoritative NS has the record updated. For HTTP and TLS-SNI challenges, the client does a self-check before asking the server to verify. It would probably be worth doing the same in your client.

Also, definitely worth joining the IETF ACME WG and talking about changing / clarifying the protocol to allow some number of retries with the same token. You’re not the only person who has requested something like that.

Correct, though to be very explicit, it’s base64url, not regular base64.

1 Like

Got a prototype working! Thanks for your help. One other clarification: if I try to create an authorization and then fail my challenge response, am I able to create another new authorization for the same identifier to try again?


…extra text to meet the 20 character minimum post length… :slight_smile:

1 Like

@mbleigh could you post the Node.js code you used to successfully generate the fingerprint/thumbprint? I think I’m doing what’s in boulder/test/js, but every time I attempt to reply to the challenge I get:

  "type": "urn:acme:error:malformed",
  "detail": "Unable to update challenge :: Provided key authorization was incorrect",
  "status": 400

Here’s how I’m generating my TXT record and keyAuthorization to include in challenge response:

const jwk = rsaPemToJwk(PRIVATE_KEY, {use: "sig"}, "public");
const input = `{"e":"${jwk.e}","kty":"RSA","n":"${jwk.n}"}`;
const thumbprint = UrlSafeBase64.encode(crypto.createHash("sha256").update(input).digest());
const keyAuthorization = `${CHALLENGE_TOKEN}.${thumbprint}`;
const recordName = `_acme-challenge.${DOMAIN}.`
    txt: UrlSafeBase64.encode(crypto.createHash("sha256").update(keyAuthorization).digest()),

update: for what it’s worth, I looked at https://tools.ietf.org/html/rfc7638 and my code computes the thumbprint in the example “3.1. Example JWK Thumbprint Computation”, that is, I get NzbLsXh8uDCcd-6MNwXF4W_7noWXFZAfHkxZsRGC9Xs

update 2 [resolved]: my problem was leading zeros hiding in urlbase64 encoding, see Trouble with keyAuthorization for DNS ("Provided key authorization was incorrect") [SOLVED]