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
