/*
* Implements the ACMEv2 RFC 8555 protocol
+ * Implements the following extensions to the protocol:
+ * draft-ietf-acme-dns-persist - DNS-PERSIST-01 challenge
*/
#include "haproxy/ticks.h"
goto out;
}
} else if (strcmp(args[0], "challenge") == 0) {
- if ((!*args[1]) || (strcasecmp("http-01", args[1]) != 0 && (strcasecmp("dns-01", args[1]) != 0))) {
- ha_alert("parsing [%s:%d]: keyword '%s' in '%s' section requires a challenge type: http-01 or dns-01\n", file, linenum, args[0], cursection);
+ if ((!*args[1]) ||
+ ((strcasecmp("http-01", args[1]) != 0) &&
+ (strcasecmp("dns-01", args[1]) != 0) &&
+ (strcasecmp("dns-persist-01", args[1]) != 0))) {
+ ha_alert("parsing [%s:%d]: keyword '%s' in '%s' must be one of the following: http-01, dns-01, dns-persist-01\n", file, linenum, args[0], cursection);
err_code |= ERR_ALERT | ERR_FATAL;
goto out;
}
struct buffer *t1 = NULL, *t2 = NULL;
int ret = 1;
int i;
+ int wildcard = 0;
hc = ctx->hc;
if (!hc)
goto error;
}
- ret = mjson_get_string(tokptr, toklen, "$.token", trash.area, trash.size);
- if (ret == -1) {
- memprintf(errmsg, "couldn't get a token in challenges[%d] from Authorization URL \"%s\"", i, auth->auth.ptr);
- goto error;
- }
- trash.data = ret;
- auth->token = istdup(ist2(trash.area, trash.data));
- if (!isttest(auth->token)) {
- memprintf(errmsg, "out of memory");
- goto error;
+ if (strcasecmp(ctx->cfg->challenge, "dns-persist-01") != 0) {
+ ret = mjson_get_string(tokptr, toklen, "$.token", trash.area, trash.size);
+ if (ret == -1) {
+ memprintf(errmsg, "couldn't get a token in challenges[%d] from Authorization URL \"%s\"", i, auth->auth.ptr);
+ goto error;
+ }
+ trash.data = ret;
+ auth->token = istdup(ist2(trash.area, trash.data));
+ if (!isttest(auth->token)) {
+ memprintf(errmsg, "out of memory");
+ goto error;
+ }
}
- /* compute a response for the TXT entry */
- if (strcasecmp(ctx->cfg->challenge, "dns-01") == 0) {
+ if (strcasecmp(ctx->cfg->challenge, "dns-persist-01") == 0) {
+ /* Clients MUST consider a challenge malformed if the issuer-domain-names array is empty
+ or if it contains more than 10 entries, and MUST reject such challenges.
+ https://datatracker.ietf.org/doc/html/draft-ietf-acme-dns-persist#section-3.1-2.4.4
+ */
+
+ struct buffer *record_values = NULL;
+ int n = 0;
+
+ record_values = get_trash_chunk();
+
+ for (n = 0; ; n++) {
+ char dom_path[] = "$.issuer-domain-names[XXX]";
+
+ if (snprintf(dom_path, sizeof(dom_path), "$.issuer-domain-names[%d]", n) >= sizeof(dom_path))
+ goto error;
+
+ /* break the loop at the end of the list */
+ if (mjson_find(tokptr, toklen, dom_path, NULL, NULL) == MJSON_TOK_INVALID)
+ break;
+
+ if (n >= 10) {
+ memprintf(errmsg, "more than 10 entries in acme issuer-domain-names");
+ goto error;
+ }
+
+ ret = mjson_get_string(tokptr, toklen, dom_path, trash.area, trash.size);
+ if (ret == -1) {
+ memprintf(errmsg, "found values other than strings in acme issuer-domain-names");
+ goto error;
+ }
+ trash.data = ret;
+
+ /* collect allowed domain names for better reporting */
+ chunk_appendf(record_values, "%s\"%.*s; accounturi=%.*s%s\"", n == 0 ? "" : " OR ",
+ (int)trash.data, trash.area, (int)ctx->kid.len, ctx->kid.ptr,
+ wildcard ? "; policy=wildcard" : "");
+ }
+
+ if (n == 0) {
+ memprintf(errmsg, "0 entries in acme issuer-domain-names");
+ goto error;
+ }
+
+ /* TODO: currently this can log more records than required when wildcards are involved */
+ send_log(NULL, LOG_INFO, "acme: %s: dns-persist-01 requires to set the \"_validation-persist.%.*s\" TXT record to %.*s\n",
+ ctx->store->path, (int)auth->dns.len, auth->dns.ptr, (int)record_values->data, record_values->area);
+ }
+ else if (strcasecmp(ctx->cfg->challenge, "dns-01") == 0) {
struct sink *dpapi;
struct ist line[16];
int nmsg = 0;
dns_record = get_trash_chunk();
+ /* compute a response for the TXT entry */
if (acme_txt_record(ist(ctx->cfg->account.thumbprint), auth->token, dns_record) == 0) {
memprintf(errmsg, "couldn't compute the dns-01 challenge");
goto error;
dpapi = sink_find("dpapi");
if (dpapi)
sink_write(dpapi, LOG_HEADER_NONE, 0, line, nmsg);
- } else {
+ }
+ else if (strcasecmp(ctx->cfg->challenge, "http-01") == 0) {
/* 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;
}
}
+ else {
+ memprintf(errmsg, "impossible acme challenge: %s", ctx->cfg->challenge);
+ goto error;
+ }
/* we only need one challenge, and iteration is only used to found the right one */
break;