From: William Lallemand Date: Fri, 11 Apr 2025 18:35:56 +0000 (+0200) Subject: MINOR: acme: finalize by sending the CSR X-Git-Tag: v3.2-dev11~87 X-Git-Url: http://git.ipfire.org/?a=commitdiff_plain;h=680222b382805de09961d88708ecec173b37c191;p=thirdparty%2Fhaproxy.git MINOR: acme: finalize by sending the CSR 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 --- diff --git a/include/haproxy/acme-t.h b/include/haproxy/acme-t.h index 62828bbe0..3151d58cf 100644 --- a/include/haproxy/acme-t.h +++ b/include/haproxy/acme-t.h @@ -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 diff --git a/src/acme.c b/src/acme.c index 73c8a692d..0b4174b23 100644 --- a/src/acme.c +++ b/src/acme.c @@ -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;