Boulder new-cert - retry after x seconds

I’m currently implementing a letsencrypt node.js client and using the dockerized boulder server for testing.

As described in document - section 5.6 the server is not providing a retry-after header in case the server is not ready to issue a certificate.
In my experience the server returns to the POST /new-cert in such cases a 403 error - see example

[2016-12-13T11:40:32.187Z]: POST /acme/new-cert
[2016-12-13T11:40:32.218Z]: Status: 403
[2016-12-13T11:40:32.218Z]: HEADERS:
[2016-12-13T11:40:32.218Z]: {"boulder-request-id":"YO3Hxij0A0pJb0_7Nh68_sshxZQG4VrxgZO_3Cpszik","boulder-requester":"15","cache-control":"public, max-age=0, no-cache","content-type":"application/problem+json","replay-nonce":"QUHUzY8HJgmzCHX0dlRpltFF_J4Md7aht5Uso3vkEys","date":"Tue, 13 Dec 2016 11:40:32 GMT","content-length":"169","connection":"close"}
[2016-12-13T11:40:32.218Z]: DATA:
[2016-12-13T11:40:32.218Z]: {"type":"urn:acme:error:unauthorized","detail":"Error creating new cert :: Authorizations for these names not found or expired:","status":403}
[2016-12-13T11:40:32.218Z]: -------- Request Finished ————————

If I send another request e.g in 10 seconds then I can get the certificate

[2016-12-13T11:40:32.218Z]: Send another new-cert request in 10 sec
[2016-12-13T11:40:42.221Z]: POST /acme/new-cert
[2016-12-13T11:40:42.297Z]: Status: 201
[2016-12-13T11:40:42.297Z]: HEADERS:
[2016-12-13T11:40:42.297Z]: {"boulder-request-id":"vUioXvi74GXWjIJxw3ipLAVqF71zPkFyyuNJKYREBO8","boulder-requester":"15","cache-control":"public, max-age=0, no-cache","content-type":"application/pkix-cert","link":"<http://localhost:4000/acme/issuer-cert>;rel=\"up\"","location":"http://localhost:4000/acme/cert/ff4346e0b78ba3536b62c8739cd87bed5149","replay-nonce":"Fv56yP5Vk11mogXXBwoq7XP4vOypRT8F_lyxUGvb7Bs","date":"Tue, 13 Dec 2016 11:40:42 GMT","content-length":"1160","connection":"close"}
[2016-12-13T11:40:42.297Z]: DATA:
[2016-12-13T11:40:42.297Z]: Data Type: Buffer
[2016-12-13T11:40:42.297Z]: -------- Request Finished ---------------- 

Is it the right way to send another POST if the first POST is not successful?

Section 5.6 is about rate-limiting. Based on the error details, it looks more like you’re sending the new-cert request before all the necessary (domain) authorizations have a valid status. Since this is node, I’d suspect you’re doing some of this asynchronously, and your code isn’t waiting until the challenge verification is actually done before doing the rest.

Side note: You might be able to use one of the existing node ACME implementations for your project, e.g. node-letsencrypt

Thanks for your fast reply.

Section 5.6 in the specification is about rate limiting, Section 5.6 in the document acme.divergences is describing the non-implementation of the retry-after header (in the specification in section 6.5.1)

You are right I’m doing all the requests asynchronously and handling them via ES6-Promises

2 further informations:

  • This “problem” only occurs in 1 of 10 cases
  • Before sending the POST new-cert I am waiting for two events:
    – Response from confirmation request of http01-challenge ( POST /acme/challenge/…)
    – Response from the http server providing the challenge that Boulder has requested the challenge

I'm not sure what you're saying here. 5.6 describes the implementation differences regarding rate limiting. In boulder's case, the server will simply return an error (with error code rateLimited), without a Retry-After header. You're not getting that error code, but rather one telling you that you do not have valid authorizations for all identifiers you're requesting a certificate for.

If I understand this correctly, you currently POST to the challenge endpoint to initiate the validation and then wait for your HTTP server to report that it has served the challenge file to boulder. This does not account for the time it takes for the response to get back to boulder, for boulder to parse the response and verify the challenge token, and for the challenge status to be updated.

Basically, you're missing is the step where you poll the authorization to see that it is actually valid. This is described in section 6.5 of the acme-01 spec:

Usually, the validation process will take some time, so the client
will need to poll the authorization resource to see when it is
finalized. For challenges where the client can tell when the server
has validated the challenge (e.g., by seeing an HTTP or DNS request
from the server), the client SHOULD NOT begin polling until it has
seen the validation request from the server.

To check on the status of an authorization, the client sends a GET
request to the authorization URI, and the server responds with the
current authorization object. In responding to poll requests while
the validation is still in progress, the server MUST return a 202
(Accepted) response with a Retry-After header field.

Thanks again for your help!

You were right I did not implement the Authorization Polling Mechanism.
I implemented this mechanism now. According to the specification the response status to this GET request is either 200(OK) or 202(accepted). I always get a status 202.

[2016-12-14T15:01:03.835Z]: Polling the authorization status, run: 1
[2016-12-14T15:01:03.835Z]:  --- GET /acme/challenge/Rq4Wkpvu9DVHJfhT2GMjoJvhyyKazDV7XYgZkyLuCT0/277 ---
[2016-12-14T15:01:03.851Z]: Status: 202
[2016-12-14T15:01:03.851Z]: HEADERS:
[2016-12-14T15:01:03.851Z]: {"boulder-request-id":"DpbL95DOjb-QnDTmVl03WykriZiZkCgZZLm58R8frkw","cache-control":"public, max-age=0, no-cache","content-type":"application/json","link":"<http://localhost:4000/acme/authz/Rq4Wkpvu9DVHJfhT2GMjoJvhyyKazDV7XYgZkyLuCT0>;rel=\"up\"","location":"http://localhost:4000/acme/challenge/Rq4Wkpvu9DVHJfhT2GMjoJvhyyKazDV7XYgZkyLuCT0/277","replay-nonce":"07snqXVDCinoV5yfB1YWHOG0GG0lu1PneGXQi0TR6z0","date":"Wed, 14 Dec 2016 15:01:03 GMT","content-length":"314","connection":"close"}
[2016-12-14T15:01:03.852Z]: DATA:
[2016-12-14T15:01:03.852Z]: {"type":"http-01","status":"pending","uri":"http://localhost:4000/acme/challenge/Rq4Wkpvu9DVHJfhT2GMjoJvhyyKazDV7XYgZkyLuCT0/277","token":"BY2iMfoje9bfdmfoZ1Ta4y9lvYggiALXPlfz1M6VBLY","keyAuthorization":"BY2iMfoje9bfdmfoZ1Ta4y9lvYggiALXPlfz1M6VBLY.5Cq3qcnlQB-cyu1RKJ7nrQzKaFyp9YOvbKpB752COEs"}
[2016-12-14T15:01:03.852Z]: --- GET /acme/challenge/Rq4Wkpvu9DVHJfhT2GMjoJvhyyKazDV7XYgZkyLuCT0/277 finished ---
[2016-12-14T15:01:04.853Z]: Polling the authorization status, run: 2
[2016-12-14T15:01:04.853Z]:  --- GET /acme/challenge/Rq4Wkpvu9DVHJfhT2GMjoJvhyyKazDV7XYgZkyLuCT0/277 ---
[2016-12-14T15:01:04.865Z]: Status: 202
[2016-12-14T15:01:04.865Z]: HEADERS:
[2016-12-14T15:01:04.865Z]: {"boulder-request-id":"66NYN1Ml6gVr5zZWnVuVT1awao6t2KgSXjt6Se6IZuk","cache-control":"public, max-age=0, no-cache","content-type":"application/json","link":"<http://localhost:4000/acme/authz/Rq4Wkpvu9DVHJfhT2GMjoJvhyyKazDV7XYgZkyLuCT0>;rel=\"up\"","location":"http://localhost:4000/acme/challenge/Rq4Wkpvu9DVHJfhT2GMjoJvhyyKazDV7XYgZkyLuCT0/277","replay-nonce":"yS9pbA64eYHnoBzxKsAiXRQZbO7-FIa_pKzI7rc2MTw","date":"Wed, 14 Dec 2016 15:01:04 GMT","content-length":"612","connection":"close"}
[2016-12-14T15:01:04.865Z]: DATA:
[2016-12-14T15:01:04.865Z]: {"type":"http-01","status":"valid","uri":"http://localhost:4000/acme/challenge/Rq4Wkpvu9DVHJfhT2GMjoJvhyyKazDV7XYgZkyLuCT0/277","token":"BY2iMfoje9bfdmfoZ1Ta4y9lvYggiALXPlfz1M6VBLY","keyAuthorization":"BY2iMfoje9bfdmfoZ1Ta4y9lvYggiALXPlfz1M6VBLY.5Cq3qcnlQB-cyu1RKJ7nrQzKaFyp9YOvbKpB752COEs","validationRecord":[{"url":"","hostname":"","port":"5002","addressesResolved":[""],"addressUsed":""}]}
[2016-12-14T15:01:04.865Z]: --- GET /acme/challenge/Rq4Wkpvu9DVHJfhT2GMjoJvhyyKazDV7XYgZkyLuCT0/277 finished ---

Via the body.status field I can nevertheless find out if the authorization request is still “pending” or “valid”. If the status = “valid” then I receive the certificate with the POST new-cert request.

Yep, the status code weirdness is a known bug (probably not documented as a divergence from the spec since it's not intentional):

I would recommend polling the /acme/authz/ resource rather than /acme/challenge. This avoids issues due to authz reuse, see the last paragraph of this post:

Many thanks for your feedback.
I implement now polling of /acme/authz

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