]> git.ipfire.org Git - thirdparty/haproxy.git/commitdiff
MINOR: acme: finalize by sending the CSR
authorWilliam Lallemand <wlallemand@haproxy.com>
Fri, 11 Apr 2025 18:35:56 +0000 (20:35 +0200)
committerWilliam Lallemand <wlallemand@haproxy.com>
Fri, 11 Apr 2025 23:29:27 +0000 (01:29 +0200)
This patch does the finalize step of the ACME task.
This encodes the CSR into base64 format and send it to the finalize URL.

https://www.rfc-editor.org/rfc/rfc8555#section-7.4

include/haproxy/acme-t.h
src/acme.c

index 62828bbe08ad5f8cb7fdb307bd20886765bd133d..3151d58cfdd9bd3b279bc723c3eb57e4c1ff8c63 100644 (file)
@@ -38,6 +38,7 @@ enum acme_st {
        ACME_AUTH,
        ACME_CHALLENGE,
        ACME_CHKCHALLENGE,
+       ACME_FINALIZE,
        ACME_END
 };
 
@@ -72,5 +73,6 @@ struct acme_ctx {
        struct acme_auth *auths;
        struct acme_auth *next_auth;
        X509_REQ *req;
+       struct ist finalize;
 };
 #endif
index 73c8a692db5a11cc3bfdcfc9fd9838529c134b1a..0b4174b23f84309e15f669bf782e47a336e68cf7 100644 (file)
@@ -578,6 +578,109 @@ error:
        return ret;
 }
 
+/* Send the CSR over the Finalize URL */
+int acme_req_finalize(struct task *task, struct acme_ctx *ctx, char **errmsg)
+{
+       X509_REQ *req = ctx->req;
+       struct buffer *csr = NULL;
+       struct buffer *req_in = NULL;
+       struct buffer *req_out = NULL;
+       const struct http_hdr hdrs[] = {
+               { IST("Content-Type"), IST("application/jose+json") },
+               { IST_NULL, IST_NULL }
+       };
+       int ret = 1;
+       size_t len = 0;
+       unsigned char *data = NULL;
+
+        if ((csr = alloc_trash_chunk()) == NULL)
+               goto error;
+        if ((req_in = alloc_trash_chunk()) == NULL)
+               goto error;
+        if ((req_out = alloc_trash_chunk()) == NULL)
+               goto error;
+
+       len = i2d_X509_REQ(req, &data);
+       if (len <= 0)
+               goto error;
+
+       ret = a2base64url((char *)data, len, csr->area, csr->size);
+       if (ret <= 0)
+               goto error;
+       csr->data = ret;
+
+       chunk_printf(req_in, "{ \"csr\": \"%.*s\" }", (int)csr->data, csr->area);
+       free(data);
+
+
+       if (acme_jws_payload(req_in, ctx->nonce, ctx->finalize, ctx->cfg->account.pkey, ctx->kid, req_out, errmsg) != 0)
+               goto error;
+
+       if (acme_http_req(task, ctx, ctx->finalize, HTTP_METH_POST, hdrs, ist2(req_out->area, req_out->data)))
+               goto error;
+
+
+       ret = 0;
+error:
+       memprintf(errmsg, "couldn't request the finalize URL");
+
+       free_trash_chunk(req_in);
+       free_trash_chunk(req_out);
+       free_trash_chunk(csr);
+
+       return ret;
+
+}
+
+int acme_res_finalize(struct task *task, struct acme_ctx *ctx, char **errmsg)
+{
+       struct httpclient *hc;
+       struct http_hdr *hdrs, *hdr;
+       struct buffer *t1 = NULL, *t2 = NULL;
+       int ret = 1;
+
+       hc = ctx->hc;
+       if (!hc)
+               goto error;
+
+        if ((t1 = alloc_trash_chunk()) == NULL)
+               goto error;
+        if ((t2 = alloc_trash_chunk()) == NULL)
+               goto error;
+
+       hdrs = hc->res.hdrs;
+
+       for (hdr = hdrs; isttest(hdr->v); hdr++) {
+               if (isteqi(hdr->n, ist("Replay-Nonce"))) {
+                       istfree(&ctx->nonce);
+                       ctx->nonce = istdup(hdr->v);
+               }
+       }
+
+       if (hc->res.status < 200 || hc->res.status >= 300) {
+               if ((ret = mjson_get_string(hc->res.buf.area, hc->res.buf.data, "$.detail", t1->area, t1->size)) > -1)
+                       t1->data = ret;
+               if ((ret = mjson_get_string(hc->res.buf.area, hc->res.buf.data, "$.type", t2->area, t2->size)) > -1)
+                       t2->data = ret;
+
+               if (t2->data && t1->data)
+                       memprintf(errmsg, "invalid HTTP status code %d when getting Finalize URL: \"%.*s\" (%.*s)", hc->res.status, (int)t1->data, t1->area, (int)t2->data, t2->area);
+               else
+                       memprintf(errmsg, "invalid HTTP status code %d when getting Finalize URL", hc->res.status);
+               goto error;
+       }
+out:
+       ret = 0;
+
+error:
+       free_trash_chunk(t1);
+       free_trash_chunk(t2);
+       httpclient_destroy(hc);
+       ctx->hc = NULL;
+
+       return ret;
+}
+
 /*
  * Send the READY request for the challenge
  */
@@ -932,6 +1035,18 @@ int acme_res_neworder(struct task *task, struct acme_ctx *ctx, char **errmsg)
                ctx->next_auth = auth;
        }
 
+       if ((ret = mjson_get_string(hc->res.buf.area, hc->res.buf.data, "$.finalize", trash.area, trash.size)) <= 0) {
+               memprintf(errmsg, "couldn't find the finalize URL");
+               goto error;
+       }
+       trash.data = ret;
+       istfree(&ctx->finalize);
+       ctx->finalize = istdup(ist2(trash.area, trash.data));
+       if (!isttest(ctx->finalize)) {
+               memprintf(errmsg, "out of memory");
+               goto error;
+       }
+
 out:
        ret = 0;
 
@@ -1299,12 +1414,26 @@ struct task *acme_process(struct task *task, void *context, unsigned int state)
                                }
                                http_st = ACME_HTTP_REQ;
                                if ((ctx->next_auth = ctx->next_auth->next) == NULL)
-                                       goto end;
+                                       st = ACME_FINALIZE;
 
-                               /* call with next auth or do the challenge step */
+                               /* do it with the next auth or finalize */
                                task_wakeup(task, TASK_WOKEN_MSG);
                        }
                break;
+               case ACME_FINALIZE:
+                       if (http_st == ACME_HTTP_REQ) {
+                               if (acme_req_finalize(task, ctx, &errmsg) != 0)
+                                       goto retry;
+                       }
+                       if (http_st == ACME_HTTP_RES) {
+                               if (acme_res_finalize(task, ctx, &errmsg) != 0) {
+                                       http_st = ACME_HTTP_REQ;
+                                       goto retry;
+                               }
+                               http_st = ACME_HTTP_REQ;
+                               goto end;
+                       }
+               break;
 
                default:
                break;