ACME Challenge state won't change after POST to authorization challenge url

I want to use acme protocol to certificate my website flowbreeze.cn
I use a plain http client to communicate with Let’s Encrypt test env
I successfully create an account, order and fetch my challenges.
But I cannot response my dns-01 challenge, the response code is always 200, but state is still 'pending' and won't changed
I have read rfc8555, but I didn't find out any solution.
Am i missed something?

My domain is:
flowbreeze.cn

I ran this command:

curl -X POST --location "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/1790056998/RYTs5A" --http1.1 \
    -H "Host: acme-staging-v02.api.letsencrypt.org" \
    -H "Content-Type: application/jose+json" \
    -d "{
          \"protected\": \"eyJhbGciOiJFUzI1NiIsImtpZCI6Imh0dHBzOi8vYWNtZS1zdGFnaW5nLXYwMi5hcGkubGV0c2VuY3J5cHQub3JnL2FjbWUvYWNjdC80NTMyNDc2OCIsIm5vbmNlIjoiMDAwMm0zSWVULVdJek1POXJFdW9vN2dRZTRNZzZhMWhtbjBUUTN3RjVFelIzNzQiLCJ1cmwiOiJodHRwczovL2FjbWUtc3RhZ2luZy12MDIuYXBpLmxldHNlbmNyeXB0Lm9yZy9hY21lL2NoYWxsLXYzLzE3OTAwNTY5OTgvUllUczVBIn0\",
          \"payload\": \"e30\",
          \"signature\": \"8jpUipRNb_947kJeeg0dokneq83oYocYs3en-ynX35TtblUGGlbWSDhZfsCXnA9O-06H1gHXXc-Lq3j1jQACXw\"
        }"

It produced this output:

POST https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/1790056998/RYTs5A

HTTP/1.1 200 OK
Server: nginx
Date: Mon, 28 Feb 2022 07:58:43 GMT
Content-Type: application/json
Content-Length: 192
Connection: keep-alive
Boulder-Requester: 45324768
Cache-Control: public, max-age=0, no-cache
Link: <https://acme-staging-v02.api.letsencrypt.org/directory>;rel="index"
Link: <https://acme-staging-v02.api.letsencrypt.org/acme/authz-v3/1790056998>;rel="up"
Location: https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/1790056998/RYTs5A
Replay-Nonce: 0001_P9nbrxzd6r2-lzhsTold0t8SEPUxO7SOxcU7gHD3Xc
X-Frame-Options: DENY
Strict-Transport-Security: max-age=604800

{
  "type": "dns-01",
  "status": "pending",
  "url": "https://acme-staging-v02.api.letsencrypt.org/acme/chall-v3/1790056998/RYTs5A",
  "token": "HRl9O8mhD1Flpc0LPjFTLVnM2xsWwBeOft8rgugwI9w"
}

Response code: 200 (OK); Time: 3707ms; Content length: 192 bytes

My web client is (include version):
jetbrains http client(goland 2020.3)

The operating system my web client runs on is (include version):
ubuntu 20.04

My hosting provider, if applicable, is:
Dnspod

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

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

2 Likes

I would strongly recommend to NOT use a plain HTTP client to communicate with the ACME API. Is there a reason you're not using an ACME client?

4 Likes

Welcome to the Let's Encrypt Community :slightly_smiling_face:

How many times (and how often) did you poll the authorization corresponding to the challenge that you triggered after you triggered the challenge? Did the authorization ever transition out of the pending state?

See @_az's post below.

3 Likes

It transitioned into invalid eventually: https://acme-staging-v02.api.letsencrypt.org/get/chall-v3/1790056998/RYTs5A.

I don't think Let's Encrypt implements the processing status for challenges. It does seem like needing to poll for longer might have been the problem here.

5 Likes

Looks like the TXT value includes " ? Or are those escaped \" part of the Boulder error message?

@FlowBreeze If you included any " in the TXT value of the challenge: you should not add those.

I also stand by my recommendation to use an ACME client instead of using pure HTTP. You need to do this every 3 months at minimum and preferably automated, if used for a live website.

4 Likes

@FlowBreeze
I applaud the (super paranoid) use of HTTP only.
I suppose, once fully functional, such requests can be scripted to renew every 60 days.
Please keep of informed on your progress.

4 Likes

Thanks for reply

Perhaps this problem is sloved

I post to challenge url two times,poll the authorization two time, and I wait at least half an hour before I wrote this topic,
Maybe the first time my jwt payload field is "" (not {}) that not accepted by server,but the response is still 200 and not different to the second
request
or I should wait longer time?

Next time I will wait longer time and use {}
And I'm going on to slove my dns validation error

3 Likes

Most of client tools I found is like a black box, I don't known what they actually doing.

acme.sh seems to be a good so solution, but bash code is hard to read. and I think it's a good time to learn something basic and important

And when I finished, I will use client tool like cert-manager. and It will be easier to hanlde high-level problem

1 Like

It seems like you want to build a solution from the RFC.

My suggestion: before using the staging environment, test your commands locally. LetsEncrypt offers an application that implements a version of the ACME protocol that can be used for developing clients and familiarizing yourself with the protocol:

There are a few differences between Pebble and the staging/production system (Boulder) - they are listed in the "divergences" and "implementation details" documents for each project. They are not fully compatible with each other by design - but both are fully compatible with the ACME spec. Pebble often makes a different choice from Boulder when the spec offers multiple options, to ensure you build to the spec, and not ISRG/LetsEncrypt's decisions.

You should be able to get everything running with Pebble, and then test against the staging system.

In terms of trying to understand ACME clients, the acme-tiny Python client (GitHub - diafygi/acme-tiny: A tiny script to issue and renew TLS certs from Let's Encrypt) is (probably) the smallest client and forgoes features to stay under 200 lines. That should help you understand things easily.

8 Likes

Really useful advice, I will try it.
I don't want to copy and paste Nonce, generate JOSE manual again and again.

Oh, it seems like acme-tiny is only used for http validation

        # find the http-01 challenge and write the challenge file
        challenge = [c for c in authorization['challenges'] if c['type'] == "http-01"][0]
2 Likes

Yes, and it is very limited... but the code is pretty clear. It should help you figure out how to script/automate what you want to accomplish.

3 Likes

Or expand the code with features you require.

3 Likes

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