DNS validation is wrong, TXT record is fine

Hello, please advise:
I am creating a client that verifies the domains basakova.cz,*.basakova.cz
I will insert TXT records into DNS according to the returned token from acme.
Subsequent verification returns an error: Incorrect TXT record.
Please why is there an error when the string is the same?

I found similar problems here on the forum. Most solved by waiting for TTL.
I have TTL set to 0, so the changes are visible immediately.
Or there were too many old TXT records.
I only keep 2 TXT up to date.

2024-02-12 16:25:29;;response array (
  'identifier' =>
  array (
    'type' => 'dns',
    'value' => 'basakova.cz',
  ),
  'status' => 'invalid',
  'expires' => '2024-02-19T14:42:27Z',
  'challenges' =>
  array (
    0 =>
    array (
      'type' => 'dns-01',
      'status' => 'invalid',
      'error' =>
      array (
        'type' => 'urn:ietf:params:acme:error:unauthorized',
        'detail' => 'Incorrect TXT record "qzOBEXErWsrXLzcsEhiDemRG8qAy1T5sI0J7X66VRo4" (and 1 more) found at _acme-challenge.basakova.cz',
        'status' => 403,
      ),
      'url' => 'https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/11132273333/8UolBg',
      'token' => 'qzOBEXErWsrXLzcsEhiDemRG8qAy1T5sI0J7X66VRo4',
      'validated' => '2024-02-12T15:24:37Z',
    ),
  ),
  'wildcard' => true,

DNS:

host -t txt _acme-challenge.basakova.cz
_acme-challenge.basakova.cz descriptive text "sIH6lvU_8_8ktl5URyIFrxAifx3GvrVohk4axy7lEIo"
_acme-challenge.basakova.cz descriptive text "qzOBEXErWsrXLzcsEhiDemRG8qAy1T5sI0J7X66VRo4"

Thank you very much for your help.

The value of the TXT record isn't just the token: it's the base64url-encoded sha256 of a key authorization.

RFC 8555 documents how to construct this in sections 8.1 (for the key authorization) and 8.4, for the DNS challenge type:

rfc8555 section 8.1

rfc8555 section 8.4

Most ACME clients will compute this value for you. I assume you're writing your own, in which case you'll have to compute the value. Or if you're using a library, there should be a function to get the TXT record value.

5 Likes

Thanks for the reply, that will be the problem.

I need to incorporate the generation into the administration, that's why I'm making my client.

Sorry, I'm not very proficient in ssl.

I created a string: "token.fingerprint_account_key" which is the same content that I put in the website when I do http-01 authorization.

Then I tried the whole thing:
1/ the string itself - invalid
2/ openssl_digest(string, sha256) - invalid
3/ base64_url(openssl_digest(string, sha256)) - invalid
4/ base64_url(string) - invalid

Could you please direct me one more time?

I made a fingerprint from the account key I use to log in to acme.
I guess I misunderstood: (in RFC)
Compute the SHA-256 digest [FIPS180-4] of the stored key authorization

Thank you very much.

1 Like

From the RFC:
keyAuthorization = token || '.' || base64url(Thumbprint(accountKey))

That's how you compute the key authorization.

A client fulfills this challenge by constructing a key authorization
from the "token" value provided in the challenge and the client's
account key. The client then computes the SHA-256 digest [FIPS180-4]
of the key authorization.

That's the key authorization above, which is the token, a ., and then the base64 thumbprint.

The record provisioned to the DNS contains the base64url encoding of
this digest.

So the DNS record is the sha256 of the keyAuthorization, base64url-encoded.

That looks like what you've written as option 3 above.

If you can't get this to work, I'd suggest using an off-the-shelf library instead of writing your own from scratch, or at least testing with another client to make sure you're computing the same value

4 Likes

Another recommendation I would make if you're building your own client would be to test against other server implementations, like Pebble, to make sure that you're not just accidentally matching Let's Encrypt's behavior.

But usually you can just use an existing client or library, rather than writing your own, and that makes things much easier.

3 Likes

Thanks everyone for the advice. I tried several other combinations and every time: Invalid.
Just that I used some PHP library. But it only has HTTP-01 authentication. I tried to finish the DNS-01 verification. I guess I'm giving the wrong key.
I will try to use another library.

1 Like

Thank you very much for your help.
Finally I went through another client in PHP and found the problem. I did it right as you write. Only when using the PHP function openssl_digest I did not pass the 3rd parameter.
I put: openssl_digest(string, sha256);
It should have been: openssl_digest(string, sha256, TRUE);

Now it works!

If anyone needs it, here's a solution in PHP:
txt_rdata = base64_encode(openssl_digest(token dot fingerprint_account_key_in_base64, sha256, true))

mcpherrinm thank you very much :slight_smile:

5 Likes

I’m glad you solved it!

I had to go look what the third parameter is:

binary
Setting to true will return as raw output data, otherwise the return value is binhex encoded.

That makes sense. There shouldn’t be hex encoding in there.

6 Likes