Hi, According to https://datatracker.ietf.org/doc/draft-ietf-acme-acme/?include_text=1 , section 6.5:
An ACME server provides nonces to clients using the HTTP Replay-Nonce header field, as specified in Section 6.5.1 below. The server MUST include a Replay-Nonce header field in every successful response to a POST request and SHOULD provide it in error responses as well. But in netproc.c the acme-client is assuming that it will get the Reply-Nonce header even for GET requests. Which is the case at the moment, but since the specification says only SHOULD, I don't think it's correct to rely on that behavior. And from the same section: When a server rejects a request because its nonce value was unacceptable (or not present), it MUST provide HTTP status code 400 (Bad Request), and indicate the ACME error type "urn:ietf:params:acme:error:badNonce". An error response with the "badNonce" error type MUST include a Replay-Nonce header with a fresh nonce that the server will accept in a retry of the original query (and possibly in other requests, according to the server's nonce scoping policy). On receiving such a response, a client SHOULD retry the request using the new nonce. So I think correct approach is to send request with intentionally bad nonce and use the Reply-Nonce value from that. Patch fixing the issue in my fork (not applicable directly since I did switch to using libcurl but to illustrate the idea): diff --git a/netproc.c b/netproc.c index 47ec3be..2f362f2 100644 --- a/netproc.c +++ b/netproc.c @@ -236,11 +236,29 @@ sreq(struct conn *c, const char *addr, const char *req) char *nonce = NULL; char *reqsn = NULL; + /* + * Since server is obligated to send Reply-Nonce only on successful + * requests or if the nonce is wrong, let's provide wrong value + * `foobar' and use Reply-Nonce from what we get back. + */ + /* Send the nonce and request payload to the acctproc. */ + if (writeop(c->fd, COMM_ACCT, ACCT_SIGN) <= 0) { + return -1; + } else if (writestr(c->fd, COMM_PAY, req) <= 0) { + return -1; + } else if (writestr(c->fd, COMM_NONCE, "foobar") <= 0) { + return -1; + } + /* Now read back the signed payload. */ + if ((reqsn = readstr(c->fd, COMM_REQ)) == NULL) + return -1; + curl = prepare_curl(addr, NULL); if (!curl) { return -1; } curl_easy_setopt(curl, CURLOPT_HEADERFUNCTION, get_nonce_cb); curl_easy_setopt(curl, CURLOPT_HEADERDATA, &nonce); + curl_easy_setopt(curl, CURLOPT_POSTFIELDS, reqsn); if (curl_easy_perform(curl) != CURLE_OK) { curl_easy_cleanup(curl); Just sending the nounce as empty should probably work too based on specification, but I like `foobar' :) Have a nice day, W. -- There are only two hard things in Computer Science: cache invalidation, naming things and off-by-one errors.
signature.asc
Description: PGP signature