From: William Lallemand Date: Fri, 1 Aug 2025 14:04:12 +0000 (+0200) Subject: MINOR: acme: emit a log for DNS-01 challenge response X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=365a69648c70ec35459266bf76de91114685260d;p=thirdparty%2Fhaproxy.git MINOR: acme: emit a log for DNS-01 challenge response This commit emits a log which output the TXT entry to create in case of DNS-01. This is useful in cases you want to update your TXT entry manually. Example: acme: foobar.pem.rsa: DNS-01 requires to set the "acme-challenge.example.com" TXT record to "7L050ytWm6ityJqolX-PzBPR0LndHV8bkZx3Zsb-FMg" --- diff --git a/include/haproxy/acme-t.h b/include/haproxy/acme-t.h index c92c67642..faef5e7e5 100644 --- a/include/haproxy/acme-t.h +++ b/include/haproxy/acme-t.h @@ -51,6 +51,7 @@ enum http_st { }; struct acme_auth { + struct ist dns; /* dns entry */ struct ist auth; /* auth URI */ struct ist chall; /* challenge URI */ struct ist token; /* token */ diff --git a/src/acme.c b/src/acme.c index 7f1eb4fe3..b93add226 100644 --- a/src/acme.c +++ b/src/acme.c @@ -755,6 +755,7 @@ static void acme_ctx_destroy(struct acme_ctx *ctx) istfree(&auth->auth); istfree(&auth->chall); istfree(&auth->token); + istfree(&auth->token); next = auth->next; free(auth); auth = next; @@ -890,6 +891,42 @@ error: } +/* + * compute a TXT record for DNS-01 challenge + * base64url(sha256(token || '.' || base64url(Thumbprint(accountKey)))) + * + * https://datatracker.ietf.org/doc/html/rfc8555/#section-8.4 + * + */ +int acme_txt_record(const struct ist thumbprint, const struct ist token, struct buffer *output) +{ + unsigned char md[EVP_MAX_MD_SIZE]; + struct buffer *tmp = NULL; + unsigned int size; + int ret = 0; + + + if ((tmp = alloc_trash_chunk()) == NULL) + goto out; + + chunk_istcat(tmp, token); + chunk_appendf(tmp, "."); + chunk_istcat(tmp, thumbprint); + + if (EVP_Digest(tmp->area, tmp->data, md, &size, EVP_sha256(), NULL) == 0) + goto out; + + ret = a2base64url((const char *)md, size, output->area, output->size); + + output->data = ret; + +out: + free_trash_chunk(tmp); + + return ret; +} + + int acme_jws_payload(struct buffer *req, struct ist nonce, struct ist url, EVP_PKEY *pkey, struct ist kid, struct buffer *output, char **errmsg) { struct buffer *b64payload = NULL; @@ -1475,6 +1512,23 @@ int acme_res_auth(struct task *task, struct acme_ctx *ctx, struct acme_auth *aut goto error; } + /* check and save the DNS entry */ + ret = mjson_get_string(hc->res.buf.area, hc->res.buf.data, "$.identifier.type", t1->area, t1->size); + if (ret == -1) { + memprintf(errmsg, "couldn't get a type \"dns\" from Authorization URL \"%s\"", auth->auth.ptr); + goto error; + } + t1->data = ret; + + ret = mjson_get_string(hc->res.buf.area, hc->res.buf.data, "$.identifier.value", t2->area, t2->size); + if (ret == -1) { + memprintf(errmsg, "couldn't get a type \"dns\" from Authorization URL \"%s\"", auth->auth.ptr); + goto error; + } + t2->data = ret; + + auth->dns = istdup(ist2(t2->area, t2->data)); + /* get the multiple challenges and select the one from the configuration */ for (i = 0; ; i++) { int ret; @@ -1524,6 +1578,14 @@ int acme_res_auth(struct task *task, struct acme_ctx *ctx, struct acme_auth *aut goto error; } + /* compute a response for the TXT entry */ + if (strcasecmp(ctx->cfg->challenge, "DNS-01") == 0) { + trash.data = acme_txt_record(ist(ctx->cfg->account.thumbprint), auth->token, &trash); + send_log(NULL, LOG_NOTICE,"acme: %s: DNS-01 requires to set the \"acme-challenge.%.*s\" TXT record to \"%.*s\"\n", + ctx->store->path, (int)auth->dns.len, auth->dns.ptr, (int)trash.data, trash.area); + } + + /* only useful for HTTP-01 */ if (acme_add_challenge_map(ctx->cfg->map, auth->token.ptr, ctx->cfg->account.thumbprint, errmsg) != 0) { memprintf(errmsg, "couldn't add the token to the '%s' map: %s", ctx->cfg->map, *errmsg); goto error;