Hi tech@, I decided to give it a shot at specifying a custom CA for DoT validation in unwind(8). Patch at the bottom. Selfish reason for this is that I run my own DoT resolver, using a self-signed certificate. Am able to use unbound to query it, but the lack of support for a custom CA in unwind stopped me from switching to it since its inception. Now that the system is becoming more integrated (dhcpleased and resolved enabled by default, deep-ish integration between resolvd and unwind), I gave it a shot.
The patch is just the code part, manpage changes are pending but wanted to know first if the approach is correct and willing to be accepted. I successfully tried: - using a custom CA - adding a DoT forwarder not covered by it and having it rejected - removing my DoT forwarders and the custom CA to fallback to the standard CA I haven't tried yet (but will do very soon): - verifying that unwind works in early system, placing the custom CA file in the / mount (ofc won't work if the file resides in another mountpoint, thing to be documented) Worth noting, currently, CA changes come into action when forwarders are added or removed. If a config file undergoes only a CA change, it won't be reflected in the running unwind. Am unsure if this could be solved without much refactor, but haven't given up yet. The default CA file was unveiled. I removed that call, but dunno if instead I should be unveiling every new CA file or not. Blocklist aren't managed in that way, fwiw. Comments? Index: frontend.c =================================================================== RCS file: /home/cvs/src/sbin/unwind/frontend.c,v retrieving revision 1.68 diff -u -p -r1.68 frontend.c --- frontend.c 6 Feb 2021 18:01:02 -0000 1.68 +++ frontend.c 20 Jul 2021 18:21:30 -0000 @@ -365,6 +365,7 @@ frontend_dispatch_main(int fd, short eve case IMSG_RECONF_FORWARDER: case IMSG_RECONF_DOT_FORWARDER: case IMSG_RECONF_FORCE: + case IMSG_RECONF_CA_FILE: imsg_receive_config(&imsg, &nconf); break; case IMSG_RECONF_END: Index: parse.y =================================================================== RCS file: /home/cvs/src/sbin/unwind/parse.y,v retrieving revision 1.25 diff -u -p -r1.25 parse.y --- parse.y 27 Feb 2021 10:32:28 -0000 1.25 +++ parse.y 20 Jul 2021 20:40:34 -0000 @@ -103,6 +103,7 @@ typedef struct { %token FORWARDER DOT PORT ODOT_FORWARDER ODOT_DHCP %token AUTHENTICATION NAME PREFERENCE RECURSOR DHCP STUB %token BLOCK LIST LOG FORCE ACCEPT BOGUS +%token CA FILE %token <v.string> STRING %token <v.number> NUMBER @@ -120,6 +121,7 @@ grammar : /* empty */ | grammar uw_forwarder '\n' | grammar block_list '\n' | grammar force '\n' + | grammar cafile '\n' | grammar error '\n' { file->errors++; } ; @@ -343,7 +345,7 @@ acceptbogus: ACCEPT BOGUS { $$ = 1; } force_list: force_list optnl STRING { struct force_tree_entry *e; - size_t len; + size_t len; len = strlen($3); e = malloc(sizeof(*e)); @@ -374,6 +376,20 @@ force_list: force_list optnl STRING { } ; +cafile : CA FILE STRING { + if (conf->ca_file != NULL) { + yyerror("CA file already configured"); + free($3); + YYERROR; + } else { + conf->ca_file = strdup($3); + if (conf->ca_file == NULL) + err(1, "strdup"); + free($3); + } + } + ; + %% struct keywords { @@ -413,8 +429,10 @@ lookup(char *s) {"authentication", AUTHENTICATION}, {"block", BLOCK}, {"bogus", BOGUS}, + {"ca", CA}, {"dhcp", DHCP}, {"dot", DOT}, + {"file", FILE}, {"force", FORCE}, {"forwarder", FORWARDER}, {"include", INCLUDE}, Index: printconf.c =================================================================== RCS file: /home/cvs/src/sbin/unwind/printconf.c,v retrieving revision 1.16 diff -u -p -r1.16 printconf.c --- printconf.c 1 Dec 2019 14:37:34 -0000 1.16 +++ printconf.c 20 Jul 2021 20:41:25 -0000 @@ -33,6 +33,9 @@ print_config(struct uw_conf *conf) int i; enum uw_resolver_type j; + if (conf->ca_file != NULL) + printf("ca file %s\n", conf->ca_file); + if (conf->res_pref.len > 0) { printf("preference {"); for (i = 0; i < conf->res_pref.len; i++) { Index: resolver.c =================================================================== RCS file: /home/cvs/src/sbin/unwind/resolver.c,v retrieving revision 1.144 diff -u -p -r1.144 resolver.c --- resolver.c 12 Jul 2021 15:09:19 -0000 1.144 +++ resolver.c 20 Jul 2021 21:58:43 -0000 @@ -127,6 +127,7 @@ struct running_query { }; TAILQ_HEAD(, running_query) running_queries; +static const char *ca_file; typedef void (*resolve_cb_t)(struct uw_resolver *, void *, int, void *, int, int, char *); @@ -376,9 +377,6 @@ resolver(int debug, int verbose) setresuid(pw->pw_uid, pw->pw_uid, pw->pw_uid)) fatal("can't drop privileges"); - if (unveil(TLS_DEFAULT_CA_CERT_FILE, "r") == -1) - fatal("unveil %s", TLS_DEFAULT_CA_CERT_FILE); - if (pledge("stdio inet dns rpath recvfd", NULL) == -1) fatal("pledge"); @@ -664,6 +662,7 @@ resolver_dispatch_main(int fd, short eve case IMSG_RECONF_FORWARDER: case IMSG_RECONF_DOT_FORWARDER: case IMSG_RECONF_FORCE: + case IMSG_RECONF_CA_FILE: imsg_receive_config(&imsg, &nconf); break; case IMSG_RECONF_END: @@ -672,6 +671,8 @@ resolver_dispatch_main(int fd, short eve "IMSG_RECONF_CONF", __func__); restart = resolvers_to_restart(resolver_conf, nconf); merge_config(resolver_conf, nconf); + ca_file = resolver_conf->ca_file == NULL ? + TLS_DEFAULT_CA_CERT_FILE : resolver_conf->ca_file; memset(enabled_resolvers, 0, sizeof(enabled_resolvers)); for (i = 0; i < resolver_conf->res_pref.len; i++) enabled_resolvers[ @@ -1320,8 +1321,7 @@ create_resolver(enum uw_resolver_type ty break; case UW_RES_ODOT_DHCP: set_forwarders(res, &autoconf_forwarder_list, 853); - ub_ctx_set_option(res->ctx, "tls-cert-bundle:", - TLS_DEFAULT_CA_CERT_FILE); + ub_ctx_set_option(res->ctx, "tls-cert-bundle:", ca_file); ub_ctx_set_tls(res->ctx, 1); break; case UW_RES_FORWARDER: @@ -1329,14 +1329,12 @@ create_resolver(enum uw_resolver_type ty break; case UW_RES_ODOT_FORWARDER: set_forwarders(res, &resolver_conf->uw_forwarder_list, 853); - ub_ctx_set_option(res->ctx, "tls-cert-bundle:", - TLS_DEFAULT_CA_CERT_FILE); + ub_ctx_set_option(res->ctx, "tls-cert-bundle:", ca_file); ub_ctx_set_tls(res->ctx, 1); break; case UW_RES_DOT: set_forwarders(res, &resolver_conf->uw_dot_forwarder_list, 0); - ub_ctx_set_option(res->ctx, "tls-cert-bundle:", - TLS_DEFAULT_CA_CERT_FILE); + ub_ctx_set_option(res->ctx, "tls-cert-bundle:", ca_file); ub_ctx_set_tls(res->ctx, 1); break; default: Index: unwind.c =================================================================== RCS file: /home/cvs/src/sbin/unwind/unwind.c,v retrieving revision 1.61 diff -u -p -r1.61 unwind.c --- unwind.c 27 Feb 2021 10:32:28 -0000 1.61 +++ unwind.c 20 Jul 2021 20:59:20 -0000 @@ -586,7 +586,7 @@ main_reload(void) int main_imsg_send_config(struct uw_conf *xconf) { - struct uw_forwarder *uw_forwarder; + struct uw_forwarder *uw_forwarder; struct force_tree_entry *force_entry; /* Send fixed part of config to children. */ @@ -599,6 +599,11 @@ main_imsg_send_config(struct uw_conf *xc == -1) return (-1); } + if (xconf->ca_file != NULL) { + if (main_sendall(IMSG_RECONF_CA_FILE, xconf->ca_file, + strlen(xconf->ca_file) + 1) == -1) + return (-1); + } /* send static forwarders to children */ TAILQ_FOREACH(uw_forwarder, &xconf->uw_forwarder_list, entry) { @@ -679,6 +684,9 @@ merge_config(struct uw_conf *conf, struc RB_INSERT(force_tree, &conf->force, n); } + free(conf->ca_file); + conf->ca_file = xconf->ca_file; + free(xconf); } @@ -952,6 +960,13 @@ imsg_receive_config(struct imsg *imsg, s fatalx("%s: IMSG_RECONF_FORCE duplicate entry", __func__); } + break; + case IMSG_RECONF_CA_FILE: + /* make sure this is a string */ + ((char *)imsg->data)[IMSG_DATA_SIZE(*imsg) - 1] = '\0'; + if ((nconf->ca_file = strdup(imsg->data)) == + NULL) + fatal("%s: strdup", __func__); break; default: log_debug("%s: error handling imsg %d", __func__, Index: unwind.h =================================================================== RCS file: /home/cvs/src/sbin/unwind/unwind.h,v retrieving revision 1.54 diff -u -p -r1.54 unwind.h --- unwind.h 27 Feb 2021 10:32:28 -0000 1.54 +++ unwind.h 20 Jul 2021 21:57:24 -0000 @@ -94,6 +94,7 @@ enum imsg_type { IMSG_RECONF_FORWARDER, IMSG_RECONF_DOT_FORWARDER, IMSG_RECONF_FORCE, + IMSG_RECONF_CA_FILE, IMSG_RECONF_END, IMSG_UDP4SOCK, IMSG_UDP6SOCK, @@ -156,6 +157,7 @@ struct uw_conf { struct resolver_preference res_pref; char *blocklist_file; int blocklist_log; + char *ca_file; }; struct query_imsg {