diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 22e86f800e..99699f6a85 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -946,7 +946,7 @@ gcc:ossl3:sid:amd64: <<: *build_job system:gcc:ossl3:sid:amd64: - # Set up environment variables to run pkcs11-provider system tests + # Set up environment variables to run pkcs11-provider based system tests variables: OPENSSL_CONF: "/var/tmp/etc/openssl-provider.cnf" SOFTHSM2_CONF: "/var/tmp/softhsm2/softhsm2.conf" diff --git a/CHANGES b/CHANGES index d70f42d04c..b22184cf5e 100644 --- a/CHANGES +++ b/CHANGES @@ -1,3 +1,8 @@ +6329. [func] Add HSM support for dnssec-policy. You can now + configure keys with a key-store that allows you to + set the directory to store key files and to set a + PKCS #11 URI string. [GL #1129] + 6328. [doc] Update ZSK minimum lifetime documentation in ARM, also depends on signing delay. [GL #4510] diff --git a/bin/confgen/keygen.c b/bin/confgen/keygen.c index 4698d1a078..348c05d874 100644 --- a/bin/confgen/keygen.c +++ b/bin/confgen/keygen.c @@ -124,7 +124,7 @@ generate_key(isc_mem_t *mctx, dns_secalg_t alg, int keysize, DO("generate key", dst_key_generate(dns_rootname, alg, keysize, 0, 0, DNS_KEYPROTO_ANY, - dns_rdataclass_in, mctx, &key, NULL)); + dns_rdataclass_in, NULL, mctx, &key, NULL)); isc_buffer_init(&key_rawbuffer, &key_rawsecret, sizeof(key_rawsecret)); diff --git a/bin/dnssec/dnssec-keygen.c b/bin/dnssec/dnssec-keygen.c index 6e5dd34769..7876f3f731 100644 --- a/bin/dnssec/dnssec-keygen.c +++ b/bin/dnssec/dnssec-keygen.c @@ -92,6 +92,7 @@ struct keygen_ctx { const char *policy; const char *configfile; const char *directory; + dns_keystore_t *keystore; char *algname; char *nametype; char *type; @@ -255,14 +256,42 @@ progress(int p) { static void kasp_from_conf(cfg_obj_t *config, isc_mem_t *mctx, const char *name, - dns_kasp_t **kaspp) { + const char *keydir, const char *engine, dns_kasp_t **kaspp) { + isc_result_t result = ISC_R_NOTFOUND; const cfg_listelt_t *element; const cfg_obj_t *kasps = NULL; dns_kasp_t *kasp = NULL, *kasp_next; - isc_result_t result = ISC_R_NOTFOUND; dns_kasplist_t kasplist; + const cfg_obj_t *keystores = NULL; + dns_keystore_t *ks = NULL, *ks_next; + dns_keystorelist_t kslist; ISC_LIST_INIT(kasplist); + ISC_LIST_INIT(kslist); + + (void)cfg_map_get(config, "key-store", &keystores); + for (element = cfg_list_first(keystores); element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *kconfig = cfg_listelt_value(element); + ks = NULL; + result = cfg_keystore_fromconfig(kconfig, mctx, lctx, engine, + &kslist, NULL); + if (result != ISC_R_SUCCESS) { + fatal("failed to configure key-store '%s': %s", + cfg_obj_asstring(cfg_tuple_get(kconfig, "name")), + isc_result_totext(result)); + } + } + /* Default key-directory key store. */ + ks = NULL; + (void)cfg_keystore_fromconfig(NULL, mctx, lctx, engine, &kslist, &ks); + INSIST(ks != NULL); + if (keydir != NULL) { + /* '-K keydir' takes priority */ + dns_keystore_setdirectory(ks, keydir); + } + dns_keystore_detach(&ks); (void)cfg_map_get(config, "dnssec-policy", &kasps); for (element = cfg_list_first(kasps); element != NULL; @@ -277,7 +306,7 @@ kasp_from_conf(cfg_obj_t *config, isc_mem_t *mctx, const char *name, } result = cfg_kasp_fromconfig(kconfig, NULL, true, mctx, lctx, - &kasplist, &kasp); + &kslist, &kasplist, &kasp); if (result != ISC_R_SUCCESS) { fatal("failed to configure dnssec-policy '%s': %s", cfg_obj_asstring(cfg_tuple_get(kconfig, "name")), @@ -298,6 +327,15 @@ kasp_from_conf(cfg_obj_t *config, isc_mem_t *mctx, const char *name, ISC_LIST_UNLINK(kasplist, kasp, link); dns_kasp_detach(&kasp); } + + /* + * Cleanup keystore list. + */ + for (ks = ISC_LIST_HEAD(kslist); ks != NULL; ks = ks_next) { + ks_next = ISC_LIST_NEXT(ks, link); + ISC_LIST_UNLINK(kslist, ks, link); + dns_keystore_detach(&ks); + } } static void @@ -655,16 +693,27 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) { if (!ctx->quiet && show_progress) { fprintf(stderr, "Generating key pair."); + } + + if (ctx->keystore != NULL && ctx->policy != NULL) { + ret = dns_keystore_keygen( + ctx->keystore, name, ctx->policy, ctx->rdclass, + mctx, ctx->alg, ctx->size, flags, &key); + } else if (!ctx->quiet && show_progress) { ret = dst_key_generate(name, ctx->alg, ctx->size, param, flags, ctx->protocol, - ctx->rdclass, mctx, &key, + ctx->rdclass, NULL, mctx, &key, &progress); - putc('\n', stderr); - fflush(stderr); } else { ret = dst_key_generate(name, ctx->alg, ctx->size, param, flags, ctx->protocol, - ctx->rdclass, mctx, &key, NULL); + ctx->rdclass, NULL, mctx, &key, + NULL); + } + + if (!ctx->quiet && show_progress) { + putc('\n', stderr); + fflush(stderr); } if (ret != ISC_R_SUCCESS) { @@ -860,6 +909,18 @@ keygen(keygen_ctx_t *ctx, isc_mem_t *mctx, int argc, char **argv) { } } +static void +check_keystore_options(keygen_ctx_t *ctx) { + ctx->directory = dns_keystore_directory(ctx->keystore, NULL); + if (ctx->directory != NULL) { + isc_result_t ret = try_dir(ctx->directory); + if (ret != ISC_R_SUCCESS) { + fatal("cannot open directory %s: %s", ctx->directory, + isc_result_totext(ret)); + } + } +} + int main(int argc, char **argv) { char *algname = NULL, *freeit = NULL; @@ -1269,7 +1330,8 @@ main(int argc, char **argv) { ctx.policy, ctx.configfile); } - kasp_from_conf(config, mctx, ctx.policy, &kasp); + kasp_from_conf(config, mctx, ctx.policy, ctx.directory, + engine, &kasp); if (kasp == NULL) { fatal("failed to load dnssec-policy '%s'", ctx.policy); @@ -1295,7 +1357,10 @@ main(int argc, char **argv) { ctx.ksk = dns_kasp_key_ksk(kaspkey); ctx.zsk = dns_kasp_key_zsk(kaspkey); ctx.lifetime = dns_kasp_key_lifetime(kaspkey); - + ctx.keystore = dns_kasp_key_keystore(kaspkey); + if (ctx.keystore != NULL) { + check_keystore_options(&ctx); + } keygen(&ctx, mctx, argc, argv); kaspkey = ISC_LIST_NEXT(kaspkey, link); diff --git a/bin/dnssec/dnssec-signzone.c b/bin/dnssec/dnssec-signzone.c index 83e3b9ee6a..29ae7d578c 100644 --- a/bin/dnssec/dnssec-signzone.c +++ b/bin/dnssec/dnssec-signzone.c @@ -2639,7 +2639,7 @@ loadzonekeys(bool preserve_keys, bool load_public) { /* Load keys corresponding to the existing DNSKEY RRset. */ result = dns_dnssec_keylistfromrdataset( - gorigin, directory, mctx, &rdataset, &keysigs, &soasigs, + gorigin, NULL, directory, mctx, &rdataset, &keysigs, &soasigs, preserve_keys, load_public, &keylist); if (result != ISC_R_SUCCESS) { fatal("failed to load the zone keys: %s", @@ -2830,8 +2830,8 @@ findkeys: /* * Find keys that match this zone in the key repository. */ - result = dns_dnssec_findmatchingkeys(gorigin, directory, now, mctx, - &matchkeys); + result = dns_dnssec_findmatchingkeys(gorigin, NULL, directory, NULL, + now, mctx, &matchkeys); if (result == ISC_R_NOTFOUND) { result = ISC_R_SUCCESS; } diff --git a/bin/dnssec/dnssectool.c b/bin/dnssec/dnssectool.c index 92980a8ca9..59fc80b3f6 100644 --- a/bin/dnssec/dnssectool.c +++ b/bin/dnssec/dnssectool.c @@ -498,7 +498,8 @@ key_collision(dst_key_t *dstkey, dns_name_t *name, const char *dir, alg = dst_key_alg(dstkey); ISC_LIST_INIT(matchkeys); - result = dns_dnssec_findmatchingkeys(name, dir, now, mctx, &matchkeys); + result = dns_dnssec_findmatchingkeys(name, NULL, dir, NULL, now, mctx, + &matchkeys); if (result == ISC_R_NOTFOUND) { return (false); } diff --git a/bin/named/include/named/server.h b/bin/named/include/named/server.h index b0f531e41d..52a13d5658 100644 --- a/bin/named/include/named/server.h +++ b/bin/named/include/named/server.h @@ -65,6 +65,7 @@ struct named_server { dns_zonemgr_t *zonemgr; dns_viewlist_t viewlist; dns_kasplist_t kasplist; + dns_keystorelist_t keystorelist; ns_interfacemgr_t *interfacemgr; dns_db_t *in_roothints; diff --git a/bin/named/include/named/zoneconf.h b/bin/named/include/named/zoneconf.h index dbecd4a79e..1eb059b25a 100644 --- a/bin/named/include/named/zoneconf.h +++ b/bin/named/include/named/zoneconf.h @@ -28,8 +28,8 @@ ISC_LANG_BEGINDECLS isc_result_t named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, const cfg_obj_t *zconfig, cfg_aclconfctx_t *ac, - dns_kasplist_t *kasplist, dns_zone_t *zone, - dns_zone_t *raw); + dns_kasplist_t *kasplist, dns_keystorelist_t *keystores, + dns_zone_t *zone, dns_zone_t *raw); /*%< * Configure or reconfigure a zone according to the named.conf * data. diff --git a/bin/named/server.c b/bin/named/server.c index 062f021c57..d2da295f06 100644 --- a/bin/named/server.c +++ b/bin/named/server.c @@ -79,6 +79,7 @@ #include #include #include +#include #include #include #include @@ -441,8 +442,8 @@ static isc_result_t configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, dns_view_t *view, dns_viewlist_t *viewlist, dns_kasplist_t *kasplist, - cfg_aclconfctx_t *aclconf, bool added, bool old_rpz_ok, - bool modify); + dns_keystorelist_t *keystores, cfg_aclconfctx_t *aclconf, + bool added, bool old_rpz_ok, bool modify); static void configure_zone_setviewcommit(isc_result_t result, const cfg_obj_t *zconfig, @@ -2787,12 +2788,12 @@ catz_addmodzone_cb(void *arg) { zoneobj = cfg_listelt_value(cfg_list_first(zlist)); /* Mark view unfrozen so that zone can be added */ - isc_loopmgr_pause(named_g_loopmgr); dns_view_thaw(cz->view); result = configure_zone(cfg->config, zoneobj, cfg->vconfig, cz->view, &cz->cbd->server->viewlist, - &cz->cbd->server->kasplist, cfg->actx, true, + &cz->cbd->server->kasplist, + &cz->cbd->server->keystorelist, cfg->actx, true, false, cz->mod); dns_view_freeze(cz->view); isc_loopmgr_resume(named_g_loopmgr); @@ -3975,8 +3976,9 @@ static const char *const response_synonyms[] = { "response", NULL }; static isc_result_t configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config, cfg_obj_t *vconfig, named_cachelist_t *cachelist, - dns_kasplist_t *kasplist, const cfg_obj_t *bindkeys, - isc_mem_t *mctx, cfg_aclconfctx_t *actx, bool need_hints) { + dns_kasplist_t *kasplist, dns_keystorelist_t *keystores, + const cfg_obj_t *bindkeys, isc_mem_t *mctx, + cfg_aclconfctx_t *actx, bool need_hints) { const cfg_obj_t *maps[4]; const cfg_obj_t *cfgmaps[3]; const cfg_obj_t *optionmaps[3]; @@ -4121,7 +4123,8 @@ configure_view(dns_view_t *view, dns_viewlist_t *viewlist, cfg_obj_t *config, { const cfg_obj_t *zconfig = cfg_listelt_value(element); CHECK(configure_zone(config, zconfig, vconfig, view, viewlist, - kasplist, actx, false, old_rpz_ok, false)); + kasplist, keystores, actx, false, + old_rpz_ok, false)); zone_element_latest = element; } @@ -6429,8 +6432,8 @@ static isc_result_t configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, const cfg_obj_t *vconfig, dns_view_t *view, dns_viewlist_t *viewlist, dns_kasplist_t *kasplist, - cfg_aclconfctx_t *aclconf, bool added, bool old_rpz_ok, - bool modify) { + dns_keystorelist_t *keystores, cfg_aclconfctx_t *aclconf, + bool added, bool old_rpz_ok, bool modify) { dns_view_t *pview = NULL; /* Production view */ dns_zone_t *zone = NULL; /* New or reused zone */ dns_zone_t *raw = NULL; /* New or reused raw zone */ @@ -6624,7 +6627,7 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, dns_zone_setstats(zone, named_g_server->zonestats); } CHECK(named_zone_configure(config, vconfig, zconfig, aclconf, - kasplist, zone, NULL)); + kasplist, keystores, zone, NULL)); dns_zone_attach(zone, &view->redirect); goto cleanup; } @@ -6800,7 +6803,7 @@ configure_zone(const cfg_obj_t *config, const cfg_obj_t *zconfig, * Configure the zone. */ CHECK(named_zone_configure(config, vconfig, zconfig, aclconf, kasplist, - zone, raw)); + keystores, zone, raw)); /* * Add the zone to its view in the new view list. @@ -7434,7 +7437,7 @@ generate_session_key(const char *filename, const char *keynamestr, /* generate key */ result = dst_key_generate(keyname, alg, bits, 1, 0, DNS_KEYPROTO_ANY, - dns_rdataclass_in, mctx, &key, NULL); + dns_rdataclass_in, NULL, mctx, &key, NULL); if (result != ISC_R_SUCCESS) { return (result); } @@ -7800,7 +7803,8 @@ configure_newzones(dns_view_t *view, cfg_obj_t *config, cfg_obj_t *vconfig, const cfg_obj_t *zconfig = cfg_listelt_value(element); CHECK(configure_zone(config, zconfig, vconfig, view, &named_g_server->viewlist, - &named_g_server->kasplist, actx, true, + &named_g_server->kasplist, + &named_g_server->keystorelist, actx, true, false, false)); } @@ -7985,7 +7989,8 @@ configure_newzone(const cfg_obj_t *zconfig, cfg_obj_t *config, cfg_aclconfctx_t *actx) { return (configure_zone( config, zconfig, vconfig, view, &named_g_server->viewlist, - &named_g_server->kasplist, actx, true, false, false)); + &named_g_server->kasplist, &named_g_server->keystorelist, actx, + true, false, false)); } /*% @@ -8129,10 +8134,14 @@ load_configuration(const char *filename, named_server_t *server, const cfg_obj_t *options; const cfg_obj_t *usev4ports, *avoidv4ports, *usev6ports, *avoidv6ports; const cfg_obj_t *kasps; + const cfg_obj_t *keystores; dns_kasp_t *kasp = NULL; dns_kasp_t *kasp_next = NULL; dns_kasp_t *default_kasp = NULL; dns_kasplist_t tmpkasplist, kasplist; + dns_keystore_t *keystore = NULL; + dns_keystore_t *keystore_next = NULL; + dns_keystorelist_t tmpkeystorelist, keystorelist; const cfg_obj_t *views; dns_view_t *view_next = NULL; @@ -8171,6 +8180,7 @@ load_configuration(const char *filename, named_server_t *server, REQUIRE(isc_loop_current(named_g_loopmgr) == named_g_mainloop); ISC_LIST_INIT(kasplist); + ISC_LIST_INIT(keystorelist); ISC_LIST_INIT(viewlist); ISC_LIST_INIT(builtin_viewlist); ISC_LIST_INIT(cachelist); @@ -8882,6 +8892,33 @@ load_configuration(const char *filename, named_server_t *server, */ (void)configure_session_key(maps, server, named_g_mctx, first_time); + /* + * Create the built-in key store ("key-directory"). + */ + result = cfg_keystore_fromconfig(NULL, named_g_mctx, named_g_lctx, + named_g_engine, &keystorelist, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup_keystorelist; + } + + /* + * Create the DNSSEC key stores. + */ + keystores = NULL; + (void)cfg_map_get(config, "key-store", &keystores); + for (element = cfg_list_first(keystores); element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *kconfig = cfg_listelt_value(element); + keystore = NULL; + result = cfg_keystore_fromconfig(kconfig, named_g_mctx, + named_g_lctx, named_g_engine, + &keystorelist, NULL); + if (result != ISC_R_SUCCESS) { + goto cleanup_keystorelist; + } + } + /* * Create the built-in kasp policies ("default", "insecure"). */ @@ -8895,7 +8932,7 @@ load_configuration(const char *filename, named_server_t *server, kasp = NULL; result = cfg_kasp_fromconfig(kconfig, default_kasp, true, named_g_mctx, named_g_lctx, - &kasplist, &kasp); + &keystorelist, &kasplist, &kasp); if (result != ISC_R_SUCCESS) { goto cleanup_kasplist; } @@ -8924,7 +8961,7 @@ load_configuration(const char *filename, named_server_t *server, kasp = NULL; result = cfg_kasp_fromconfig(kconfig, default_kasp, true, named_g_mctx, named_g_lctx, - &kasplist, &kasp); + &keystorelist, &kasplist, &kasp); if (result != ISC_R_SUCCESS) { goto cleanup_kasplist; } @@ -8932,8 +8969,15 @@ load_configuration(const char *filename, named_server_t *server, dns_kasp_freeze(kasp); dns_kasp_detach(&kasp); } - dns_kasp_detach(&default_kasp); + + /* + * Save keystore list and kasp list. + */ + tmpkeystorelist = server->keystorelist; + server->keystorelist = keystorelist; + keystorelist = tmpkeystorelist; + tmpkasplist = server->kasplist; server->kasplist = kasplist; kasplist = tmpkasplist; @@ -9037,7 +9081,8 @@ load_configuration(const char *filename, named_server_t *server, } result = configure_view(view, &viewlist, config, vconfig, - &cachelist, &server->kasplist, bindkeys, + &cachelist, &server->kasplist, + &server->keystorelist, bindkeys, named_g_mctx, named_g_aclconfctx, true); if (result != ISC_R_SUCCESS) { dns_view_detach(&view); @@ -9058,7 +9103,8 @@ load_configuration(const char *filename, named_server_t *server, goto cleanup_cachelist; } result = configure_view(view, &viewlist, config, NULL, - &cachelist, &server->kasplist, bindkeys, + &cachelist, &server->kasplist, + &server->keystorelist, bindkeys, named_g_mctx, named_g_aclconfctx, true); if (result != ISC_R_SUCCESS) { dns_view_detach(&view); @@ -9085,10 +9131,10 @@ load_configuration(const char *filename, named_server_t *server, goto cleanup_cachelist; } - result = configure_view(view, &viewlist, config, vconfig, - &cachelist, &server->kasplist, bindkeys, - named_g_mctx, named_g_aclconfctx, - false); + result = configure_view( + view, &viewlist, config, vconfig, &cachelist, + &server->kasplist, &server->keystorelist, bindkeys, + named_g_mctx, named_g_aclconfctx, false); if (result != ISC_R_SUCCESS) { dns_view_detach(&view); goto cleanup_cachelist; @@ -9585,6 +9631,15 @@ cleanup_kasplist: dns_kasp_detach(&kasp); } +cleanup_keystorelist: + for (keystore = ISC_LIST_HEAD(keystorelist); keystore != NULL; + keystore = keystore_next) + { + keystore_next = ISC_LIST_NEXT(keystore, link); + ISC_LIST_UNLINK(keystorelist, keystore, link); + dns_keystore_detach(&keystore); + } + cleanup_v6portset: isc_portset_destroy(named_g_mctx, &v6portset); @@ -9849,6 +9904,7 @@ shutdown_server(void *arg) { named_server_t *server = (named_server_t *)arg; dns_view_t *view = NULL, *view_next = NULL; dns_kasp_t *kasp = NULL, *kasp_next = NULL; + dns_keystore_t *keystore = NULL, *keystore_next = NULL; bool flush = server->flushonshutdown; named_cache_t *nsc = NULL; @@ -9895,6 +9951,14 @@ shutdown_server(void *arg) { dns_kasp_detach(&kasp); } + for (keystore = ISC_LIST_HEAD(server->keystorelist); keystore != NULL; + keystore = keystore_next) + { + keystore_next = ISC_LIST_NEXT(keystore, link); + ISC_LIST_UNLINK(server->keystorelist, keystore, link); + dns_keystore_detach(&keystore); + } + for (view = ISC_LIST_HEAD(server->viewlist); view != NULL; view = view_next) { @@ -10001,6 +10065,7 @@ named_server_create(isc_mem_t *mctx, named_server_t **serverp) { /* Initialize server data structures. */ ISC_LIST_INIT(server->kasplist); + ISC_LIST_INIT(server->keystorelist); ISC_LIST_INIT(server->viewlist); /* Must be first. */ @@ -10109,6 +10174,7 @@ named_server_destroy(named_server_t **serverp) { dst_lib_destroy(); INSIST(ISC_LIST_EMPTY(server->kasplist)); + INSIST(ISC_LIST_EMPTY(server->keystorelist)); INSIST(ISC_LIST_EMPTY(server->viewlist)); INSIST(ISC_LIST_EMPTY(server->cachelist)); @@ -13351,8 +13417,9 @@ do_addzone(named_server_t *server, ns_cfgctx_t *cfg, dns_view_t *view, /* Mark view unfrozen and configure zone */ dns_view_thaw(view); result = configure_zone(cfg->config, zoneobj, cfg->vconfig, view, - &server->viewlist, &server->kasplist, cfg->actx, - true, false, false); + &server->viewlist, &server->kasplist, + &server->keystorelist, cfg->actx, true, false, + false); dns_view_freeze(view); isc_loopmgr_resume(named_g_loopmgr); @@ -13536,8 +13603,9 @@ do_modzone(named_server_t *server, ns_cfgctx_t *cfg, dns_view_t *view, /* Reconfigure the zone */ dns_view_thaw(view); result = configure_zone(cfg->config, zoneobj, cfg->vconfig, view, - &server->viewlist, &server->kasplist, cfg->actx, - true, false, true); + &server->viewlist, &server->kasplist, + &server->keystorelist, cfg->actx, true, false, + true); dns_view_freeze(view); isc_loopmgr_resume(named_g_loopmgr); @@ -14561,7 +14629,6 @@ named_server_dnssec(named_server_t *server, isc_lex_t *lex, char output[4096]; isc_stdtime_t now, when; isc_time_t timenow, timewhen; - const char *dir; dns_db_t *db = NULL; dns_dbversion_t *version = NULL; @@ -14696,7 +14763,6 @@ named_server_dnssec(named_server_t *server, isc_lex_t *lex, } /* Get DNSSEC keys. */ - dir = dns_zone_getkeydirectory(zone); CHECK(dns_zone_getdb(zone, &db)); dns_db_currentversion(db, &version); LOCK(&kasp->lock); @@ -14728,11 +14794,11 @@ named_server_dnssec(named_server_t *server, isc_lex_t *lex, LOCK(&kasp->lock); if (use_keyid) { - result = dns_keymgr_checkds_id(kasp, &keys, dir, now, - when, dspublish, keyid, + result = dns_keymgr_checkds_id(kasp, &keys, now, when, + dspublish, keyid, (unsigned int)algorithm); } else { - result = dns_keymgr_checkds(kasp, &keys, dir, now, when, + result = dns_keymgr_checkds(kasp, &keys, now, when, dspublish); } UNLOCK(&kasp->lock); @@ -14783,7 +14849,7 @@ named_server_dnssec(named_server_t *server, isc_lex_t *lex, isc_result_t ret; LOCK(&kasp->lock); - result = dns_keymgr_rollover(kasp, &keys, dir, now, when, keyid, + result = dns_keymgr_rollover(kasp, &keys, now, when, keyid, (unsigned int)algorithm); UNLOCK(&kasp->lock); diff --git a/bin/named/zoneconf.c b/bin/named/zoneconf.c index 06982f9aeb..1359507a04 100644 --- a/bin/named/zoneconf.c +++ b/bin/named/zoneconf.c @@ -866,8 +866,8 @@ process_notifytype(dns_notifytype_t ntype, dns_zonetype_t ztype, isc_result_t named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, const cfg_obj_t *zconfig, cfg_aclconfctx_t *ac, - dns_kasplist_t *kasplist, dns_zone_t *zone, - dns_zone_t *raw) { + dns_kasplist_t *kasplist, dns_keystorelist_t *keystorelist, + dns_zone_t *zone, dns_zone_t *raw) { isc_result_t result; const char *zname; dns_rdataclass_t zclass; @@ -1576,6 +1576,8 @@ named_zone_configure(const cfg_obj_t *config, const cfg_obj_t *vconfig, filename = cfg_obj_asstring(obj); CHECK(dns_zone_setkeydirectory(zone, filename)); } + /* Also save a reference to the keystore list. */ + dns_zone_setkeystores(zone, keystorelist); obj = NULL; result = named_config_get(maps, "sig-signing-signatures", &obj); diff --git a/bin/tests/system/checkconf/bad-kasp-keydir-vs-keystore1.conf b/bin/tests/system/checkconf/bad-kasp-keydir-vs-keystore1.conf new file mode 100644 index 0000000000..f01ded49f3 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp-keydir-vs-keystore1.conf @@ -0,0 +1,59 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * The same zone in different views is using different DNSSEC policies, so it + * may not use the same directory for storing keys. + */ + + +key "keyforview1" { + algorithm "hmac-sha1"; + secret "YPfMoAk6h+3iN8MDRQC004iSNHY="; +}; + +key "keyforview2" { + algorithm "hmac-sha1"; + secret "4xILSZQnuO1UKubXHkYUsvBRPu8="; +}; + +key-store "store2" { + directory "."; +}; + +dnssec-policy "policy2" { + keys { + csk key-store "store2" lifetime unlimited algorithm 13; + }; +}; + +view "example1" { + match-clients { key "keyforview1"; }; + + zone "example.net" { + type primary; + dnssec-policy "default"; + key-directory "."; + file "example1.db"; + }; +}; + +view "example2" { + match-clients { key "keyforview2"; }; + + zone "example.net" { + type primary; + dnssec-policy "policy2"; + file "example2.db"; + }; +}; diff --git a/bin/tests/system/checkconf/bad-kasp-keydir-vs-keystore2.conf b/bin/tests/system/checkconf/bad-kasp-keydir-vs-keystore2.conf new file mode 100644 index 0000000000..efe09e37fb --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp-keydir-vs-keystore2.conf @@ -0,0 +1,59 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * In view "example1" no key-directory is set, so the default is used. + * In view "example2" the key-store directory is set to "." which is the + * default. This should fail because the same zone in different views is using + * different DNSSEC policies. + */ + +key "keyforview1" { + algorithm "hmac-sha1"; + secret "YPfMoAk6h+3iN8MDRQC004iSNHY="; +}; + +key "keyforview2" { + algorithm "hmac-sha1"; + secret "4xILSZQnuO1UKubXHkYUsvBRPu8="; +}; + +key-store "store2" { + directory "."; +}; + +dnssec-policy "policy2" { + keys { + csk key-store "store2" lifetime unlimited algorithm 13; + }; +}; + +view "example1" { + match-clients { key "keyforview1"; }; + + zone "example.net" { + type primary; + dnssec-policy "default"; + file "example1.db"; + }; +}; + +view "example2" { + match-clients { key "keyforview2"; }; + + zone "example.net" { + type primary; + dnssec-policy "policy2"; + file "example2.db"; + }; +}; diff --git a/bin/tests/system/checkconf/bad-kasp-keydir-vs-keystore3.conf b/bin/tests/system/checkconf/bad-kasp-keydir-vs-keystore3.conf new file mode 100644 index 0000000000..fdc8577d44 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp-keydir-vs-keystore3.conf @@ -0,0 +1,64 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * The zone in view "example1" inherits the key directory value from "options", + * but in view "example2" sets the key-store directory to the same value. + * This should be detected as an error because the zone is using different + * DNSSEC policies and should thus use different key directories. + */ + +key "keyforview1" { + algorithm "hmac-sha1"; + secret "YPfMoAk6h+3iN8MDRQC004iSNHY="; +}; + +key "keyforview2" { + algorithm "hmac-sha1"; + secret "4xILSZQnuO1UKubXHkYUsvBRPu8="; +}; + +key-store "store2" { + directory "keys"; +}; + +dnssec-policy "policy2" { + keys { + csk key-store "store2" lifetime unlimited algorithm 13; + }; +}; + +options { + key-directory "keys"; +}; + +view "example1" { + match-clients { key "keyforview1"; }; + + zone "example.net" { + type primary; + /* key-directory inherited from options. */ + dnssec-policy "default"; + file "example1.db"; + }; +}; + +view "example2" { + match-clients { key "keyforview2"; }; + + zone "example.net" { + type primary; + dnssec-policy "policy2"; + file "example2.db"; + }; +}; diff --git a/bin/tests/system/checkconf/bad-kasp-keydir-vs-keystore4.conf b/bin/tests/system/checkconf/bad-kasp-keydir-vs-keystore4.conf new file mode 100644 index 0000000000..ddfbebc768 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp-keydir-vs-keystore4.conf @@ -0,0 +1,60 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * The zone inherits the key-directory from the "view" level. Both views use the + * same directory for storing keys, but the zone uses a different DNSSEC policy + * per view. This is a configuration error. + */ + +key "keyforview1" { + algorithm "hmac-sha1"; + secret "YPfMoAk6h+3iN8MDRQC004iSNHY="; +}; + +key "keyforview2" { + algorithm "hmac-sha1"; + secret "4xILSZQnuO1UKubXHkYUsvBRPu8="; +}; + +key-store "store2" { + directory "keys"; +}; + +dnssec-policy "policy2" { + keys { + csk key-store "store2" lifetime unlimited algorithm 13; + }; +}; + +view "example1" { + match-clients { key "keyforview1"; }; + + key-directory "keys"; + + zone "example.net" { + type primary; + dnssec-policy "default"; + file "example1.db"; + }; +}; + +view "example2" { + match-clients { key "keyforview2"; }; + + zone "example.net" { + type primary; + dnssec-policy "policy2"; + file "example2.db"; + }; +}; diff --git a/bin/tests/system/checkconf/bad-kasp-keystore.conf b/bin/tests/system/checkconf/bad-kasp-keystore.conf new file mode 100644 index 0000000000..8bbe9a38e2 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp-keystore.conf @@ -0,0 +1,19 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// Bad dnssec-policy configuration because there is no key-store with this name. +dnssec-policy "bad" { + keys { + csk key-store "ks404" lifetime unlimited algorithm 13; + }; +}; diff --git a/bin/tests/system/checkconf/bad-kasp-keystore1.conf b/bin/tests/system/checkconf/bad-kasp-keystore1.conf new file mode 100644 index 0000000000..68540fab38 --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp-keystore1.conf @@ -0,0 +1,68 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * The same zone in different views is using different DNSSEC policies, so it + * may not use the same key-store directory. + */ + + +key "keyforview1" { + algorithm "hmac-sha1"; + secret "YPfMoAk6h+3iN8MDRQC004iSNHY="; +}; + +key "keyforview2" { + algorithm "hmac-sha1"; + secret "4xILSZQnuO1UKubXHkYUsvBRPu8="; +}; + +key-store "store1" { + directory "keys"; +}; + +key-store "store2" { + directory "keys"; +}; + +dnssec-policy "policy1" { + keys { + csk key-store "store1" lifetime unlimited algorithm 13; + }; +}; + +dnssec-policy "policy2" { + keys { + csk key-store "store2" lifetime unlimited algorithm 13; + }; +}; + +view "example1" { + match-clients { key "keyforview1"; }; + + zone "example.net" { + type primary; + dnssec-policy "policy1"; + file "example1.db"; + }; +}; + +view "example2" { + match-clients { key "keyforview2"; }; + + zone "example.net" { + type primary; + dnssec-policy "policy2"; + file "example2.db"; + }; +}; diff --git a/bin/tests/system/checkconf/bad-kasp-keystore2.conf b/bin/tests/system/checkconf/bad-kasp-keystore2.conf new file mode 100644 index 0000000000..19be1bd51e --- /dev/null +++ b/bin/tests/system/checkconf/bad-kasp-keystore2.conf @@ -0,0 +1,64 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/* + * Both policies use the same key-store. Should fail because the same zone in + * different views is using different DNSSEC policies. + */ + +key "keyforview1" { + algorithm "hmac-sha1"; + secret "YPfMoAk6h+3iN8MDRQC004iSNHY="; +}; + +key "keyforview2" { + algorithm "hmac-sha1"; + secret "4xILSZQnuO1UKubXHkYUsvBRPu8="; +}; + +key-store "store" { + directory "keys"; +}; + +dnssec-policy "policy1" { + keys { + csk key-store "store" lifetime unlimited algorithm 13; + }; +}; + +dnssec-policy "policy2" { + keys { + csk key-store "store" lifetime unlimited algorithm 13; + }; +}; + + +view "example1" { + match-clients { key "keyforview1"; }; + + zone "example.net" { + type primary; + dnssec-policy "policy1"; + file "example1.db"; + }; +}; + +view "example2" { + match-clients { key "keyforview2"; }; + + zone "example.net" { + type primary; + dnssec-policy "policy2"; + file "example2.db"; + }; +}; diff --git a/bin/tests/system/checkconf/bad-keystore-key-directory.conf b/bin/tests/system/checkconf/bad-keystore-key-directory.conf new file mode 100644 index 0000000000..7007cf80f4 --- /dev/null +++ b/bin/tests/system/checkconf/bad-keystore-key-directory.conf @@ -0,0 +1,24 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +// Bad key-store configuration because the keyword 'key-directory' may not +// be used. +key-store "key-directory" { + directory "."; +}; + +dnssec-policy "bad" { + keys { + csk key-store "key-directory" lifetime unlimited algorithm 13; + }; +}; diff --git a/bin/tests/system/checkconf/good-kasp.conf b/bin/tests/system/checkconf/good-kasp.conf index d5da98fc6d..95ba817b3b 100644 --- a/bin/tests/system/checkconf/good-kasp.conf +++ b/bin/tests/system/checkconf/good-kasp.conf @@ -26,7 +26,7 @@ dnssec-policy "test" { keys { ksk key-directory lifetime P1Y algorithm ecdsa256; zsk lifetime P30D algorithm 13; - csk key-directory lifetime unlimited algorithm rsasha256 2048; + csk key-store "hsm" lifetime unlimited algorithm rsasha256 2048; }; max-zone-ttl 86400; nsec3param iterations 0 optout no salt-length 8; @@ -39,6 +39,10 @@ dnssec-policy "test" { signatures-validity-dnskey P14D; zone-propagation-delay PT5M; }; +key-store "hsm" { + directory "."; + pkcs11-uri "pkcs11:token=bind9;pin-value=1234"; +}; options { dnssec-policy "default"; }; diff --git a/bin/tests/system/checkconf/good.conf.in b/bin/tests/system/checkconf/good.conf.in index 7d1f6b8576..2fde415a40 100644 --- a/bin/tests/system/checkconf/good.conf.in +++ b/bin/tests/system/checkconf/good.conf.in @@ -23,9 +23,9 @@ dnssec-policy "test" { }; dnskey-ttl 3600; keys { - ksk key-directory lifetime P1Y algorithm 13 256; - zsk key-directory lifetime P30D algorithm 13; - csk key-directory lifetime P30D algorithm 8 2048; + ksk key-directory lifetime P1Y algorithm 13; + zsk lifetime P30D algorithm 13; + csk key-store "hsm" lifetime P30D algorithm 8 2048; }; max-zone-ttl 86400; nsec3param ; @@ -39,6 +39,10 @@ dnssec-policy "test" { signatures-validity-dnskey P14D; zone-propagation-delay PT5M; }; +key-store "hsm" { + directory "."; + pkcs11-uri "pkcs11:token=bind9;pin-value=1234"; +}; options { avoid-v4-udp-ports { 100; diff --git a/bin/tests/system/checkconf/tests.sh b/bin/tests/system/checkconf/tests.sh index d3289015d2..74361fa9cb 100644 --- a/bin/tests/system/checkconf/tests.sh +++ b/bin/tests/system/checkconf/tests.sh @@ -300,20 +300,32 @@ n=$((n + 1)) echo_i "checking for missing key directory warning ($n)" ret=0 rm -rf test.keydir +rm -rf test.keystoredir $CHECKCONF warn-keydir.conf >checkconf.out$n.1 2>&1 l=$(grep "'test.keydir' does not exist" checkconf.out$n.2 2>&1 l=$(grep "'test.keydir' is not a directory" checkconf.out$n.3 2>&1 l=$(grep "key-directory" /dev/null 2>&1 || echo_i "softhsm2-enginepkcs11 token not found for cleaning" diff --git a/bin/tests/system/enginepkcs11/ns1/named.conf.in b/bin/tests/system/enginepkcs11/ns1/named.conf.in index 985974db81..b6c2d2df9d 100644 --- a/bin/tests/system/enginepkcs11/ns1/named.conf.in +++ b/bin/tests/system/enginepkcs11/ns1/named.conf.in @@ -34,3 +34,17 @@ key rndc_key { controls { inet 10.53.0.1 port @CONTROLPORT@ allow { any; } keys { rndc_key; }; }; + +key-store "hsm" { + directory "."; + pkcs11-uri "pkcs11:token=softhsm2-enginepkcs11;pin-value=1234"; +}; + +key-store "pin" { + directory "."; + pkcs11-uri "pkcs11:token=softhsm2-enginepkcs11;pin-source=pin"; +}; + +key-store "disk" { + directory "keys"; +}; diff --git a/bin/tests/system/enginepkcs11/ns2/named.args.in b/bin/tests/system/enginepkcs11/ns2/named.args.in new file mode 100644 index 0000000000..1d6beb9a9f --- /dev/null +++ b/bin/tests/system/enginepkcs11/ns2/named.args.in @@ -0,0 +1 @@ +@ENGINE_ARGS@ -D enginepkcs11-ns2 -m record -c named.conf -d 99 -U 4 -T maxcachesize=2097152 diff --git a/bin/tests/system/enginepkcs11/ns2/named.conf.in b/bin/tests/system/enginepkcs11/ns2/named.conf.in new file mode 100644 index 0000000000..0622a949ff --- /dev/null +++ b/bin/tests/system/enginepkcs11/ns2/named.conf.in @@ -0,0 +1,57 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +controls { /* empty */ }; + +options { + query-source address 10.53.0.2; + notify-source 10.53.0.2; + transfer-source 10.53.0.2; + port @PORT@; + pid-file "named.pid"; + listen-on { 10.53.0.2; }; + listen-on-v6 { none; }; + recursion no; + dnssec-validation no; + notify no; +}; + +key "keyforview1" { + algorithm @DEFAULT_HMAC@; + secret "YPfMoAk6h+3iN8MDRQC004iSNHY="; +}; + +key "keyforview2" { + algorithm @DEFAULT_HMAC@; + secret "4xILSZQnuO1UKubXHkYUsvBRPu8="; +}; + +key-store "hsm" { + directory "."; + pkcs11-uri "pkcs11:token=softhsm2-enginepkcs11;pin-value=1234"; +}; + +key-store "hsm2" { + directory "keys"; + pkcs11-uri "pkcs11:token=softhsm2-enginepkcs11;pin-value=1234"; +}; + +key-store "pin" { + directory "."; + pkcs11-uri "pkcs11:token=softhsm2-enginepkcs11;pin-source=pin"; +}; + +key-store "disk" { + directory "keys"; +}; + diff --git a/bin/tests/system/enginepkcs11/ns2/template.db.in b/bin/tests/system/enginepkcs11/ns2/template.db.in new file mode 100644 index 0000000000..a140bff4ac --- /dev/null +++ b/bin/tests/system/enginepkcs11/ns2/template.db.in @@ -0,0 +1,23 @@ +; Copyright (C) Internet Systems Consortium, Inc. ("ISC") +; +; SPDX-License-Identifier: MPL-2.0 +; +; This Source Code Form is subject to the terms of the Mozilla Public +; License, v. 2.0. If a copy of the MPL was not distributed with this +; file, You can obtain one at http://mozilla.org/MPL/2.0/. +; +; See the COPYRIGHT file distributed with this work for additional +; information regarding copyright ownership. + +$TTL 300 ; 5 minutes +@ IN SOA ns root ( + 2000082401 ; serial + 1800 ; refresh (30 minutes) + 1800 ; retry (30 minutes) + 1814400 ; expire (3 weeks) + 3600 ; minimum (1 hour) + ) + NS ns +ns A 10.53.0.2 + +txt TXT "test" diff --git a/bin/tests/system/enginepkcs11/prereq.sh b/bin/tests/system/enginepkcs11/prereq.sh index c6caa0dc88..449d9f2058 100644 --- a/bin/tests/system/enginepkcs11/prereq.sh +++ b/bin/tests/system/enginepkcs11/prereq.sh @@ -13,6 +13,11 @@ . ../conf.sh +[ "prereq/var/tmp/etc/openssl-provider.cnf" -eq "prereq${OPENSSL_CONF}" ] || { + echo_i "skip: pkcs11-provider not enabled" + exit 255 +} + [ -n "${SOFTHSM2_CONF}" ] || { echo_i "skip: softhsm2 configuration not available" exit 255 diff --git a/bin/tests/system/enginepkcs11/setup.sh b/bin/tests/system/enginepkcs11/setup.sh index 3cb216bdbf..bf140f1895 100644 --- a/bin/tests/system/enginepkcs11/setup.sh +++ b/bin/tests/system/enginepkcs11/setup.sh @@ -16,15 +16,14 @@ set -e +$SHELL clean.sh + OPENSSL_CONF= softhsm2-util --init-token --free --pin 1234 --so-pin 1234 --label "softhsm2-enginepkcs11" | awk '/^The token has been initialized and is reassigned to slot/ { print $NF }' -printf '%s' "${HSMPIN:-1234}" >pin parse_openssl_config +printf '%s' "${HSMPIN:-1234}" >ns1/pin PWD=$(pwd) -copy_setports ns1/named.conf.in ns1/named.conf -sed -e "s/@ENGINE_ARGS@/${ENGINE_ARG}/g" ns1/named.args - keygen() { type="$1" bits="$2" @@ -33,7 +32,7 @@ keygen() { label="${id}-${zone}" p11id=$(echo "${label}" | openssl sha1 -r | awk '{print $1}') - OPENSSL_CONF= pkcs11-tool --module $SOFTHSM2_MODULE --token-label "softhsm2-enginepkcs11" -l -k --key-type $type:$bits --label "${label}" --id "${p11id}" --pin $(cat $PWD/pin) >pkcs11-tool.out.$zone.$id 2>pkcs11-tool.err.$zone.$id || return 1 + OPENSSL_CONF= pkcs11-tool --module $SOFTHSM2_MODULE --token-label "softhsm2-enginepkcs11" -l -k --key-type $type:$bits --label "${label}" --id "${p11id}" --pin $(cat $PWD/ns1/pin) >pkcs11-tool.out.$zone.$id 2>pkcs11-tool.err.$zone.$id || return 1 } keyfromlabel() { @@ -43,11 +42,16 @@ keyfromlabel() { dir="$4" shift 4 - $KEYFRLAB $ENGINE_ARG -K $dir -a $alg -l "pkcs11:token=softhsm2-enginepkcs11;object=${id}-${zone};pin-source=$PWD/pin" "$@" $zone >>keyfromlabel.out.$zone.$id 2>keyfromlabel.err.$zone.$id || return 1 + $KEYFRLAB $ENGINE_ARG -K $dir -a $alg -l "pkcs11:token=softhsm2-enginepkcs11;object=${id}-${zone};pin-source=$PWD/ns1/pin" "$@" $zone >>keyfromlabel.out.$zone.$id 2>keyfromlabel.err.$zone.$id || return 1 cat keyfromlabel.out.$zone.$id } # Setup ns1. +copy_setports ns1/named.conf.in ns1/named.conf +sed -e "s/@ENGINE_ARGS@/${ENGINE_ARG}/g" ns1/named.args + +mkdir ns1/keys + dir="ns1" infile="${dir}/template.db.in" for algtypebits in rsasha256:rsa:2048 rsasha512:rsa:2048 \ @@ -57,9 +61,10 @@ for algtypebits in rsasha256:rsa:2048 rsasha512:rsa:2048 \ type=$(echo "$algtypebits" | cut -f 2 -d :) bits=$(echo "$algtypebits" | cut -f 3 -d :) + tld="example" if $SHELL ../testcrypto.sh $alg; then - zone="$alg.example" - zonefile="zone.$alg.example.db" + zone="$alg.$tld" + zonefile="zone.$alg.$tld.db" ret=0 echo_i "Generate keys $alg $type:$bits for zone $zone" @@ -111,6 +116,15 @@ for algtypebits in rsasha256:rsa:2048 rsasha512:rsa:2048 \ cp "${ksk2}.key" "${ksk2}.ksk2" ) + echo_i "Add zone $alg.kasp to named.conf" + cp $infile ${dir}/zone.${alg}.kasp.db + + echo_i "Add zone $alg.split to named.conf" + cp $infile ${dir}/zone.${alg}.split.db + + echo_i "Add weird zone to named.conf" + cp $infile ${dir}/zone.${alg}.weird.db + echo_i "Add zone $zone to named.conf" cat >>"${dir}/named.conf" <ns2/named.args + +mkdir ns2/keys + +dir="ns2" +infile="${dir}/template.db.in" +algtypebits="ecdsap256sha256:EC:prime256v1" +alg=$(echo "$algtypebits" | cut -f 1 -d :) +type=$(echo "$algtypebits" | cut -f 2 -d :) +bits=$(echo "$algtypebits" | cut -f 3 -d :) +tld="views" + +if $SHELL ../testcrypto.sh $alg; then + zone="$alg.$tld" + zonefile1="zone.$alg.$tld.view1.db" + zonefile2="zone.$alg.$tld.view2.db" + ret=0 + + echo_i "Generate keys $alg $type:$bits for zone $zone" + keygen $type $bits $zone enginepkcs11-zsk || ret=1 + keygen $type $bits $zone enginepkcs11-ksk || ret=1 + test "$ret" -eq 0 || exit 1 + + echo_i "Get ZSK $alg $zone $type:$bits" + zsk1=$(keyfromlabel $alg $zone enginepkcs11-zsk $dir) + test -z "$zsk1" && exit 1 + + echo_i "Get KSK $alg $zone $type:$bits" + ksk1=$(keyfromlabel $alg $zone enginepkcs11-ksk $dir -f KSK) + test -z "$ksk1" && exit 1 + + ( + cd $dir + zskid1=$(keyfile_to_key_id $zsk1) + kskid1=$(keyfile_to_key_id $ksk1) + echo "$zskid1" >$zone.zskid1 + echo "$kskid1" >$zone.kskid1 + ) + + echo_i "Sign zone with $ksk1 $zsk1" + cat "$infile" "${dir}/${ksk1}.key" "${dir}/${zsk1}.key" >"${dir}/${zonefile1}" + $SIGNER $ENGINE_ARG -K $dir -S -a -g -O full -o "$zone" "${dir}/${zonefile1}" >signer.out.view1.$zone || ret=1 + test "$ret" -eq 0 || exit 1 + + cat "$infile" "${dir}/${ksk1}.key" "${dir}/${zsk1}.key" >"${dir}/${zonefile2}" + $SIGNER $ENGINE_ARG -K $dir -S -a -g -O full -o "$zone" "${dir}/${zonefile2}" >signer.out.view2.$zone || ret=1 + test "$ret" -eq 0 || exit 1 + + echo_i "Generate successor keys $alg $type:$bits for zone $zone" + keygen $type $bits $zone enginepkcs11-zsk2 || ret=1 + keygen $type $bits $zone enginepkcs11-ksk2 || ret=1 + test "$ret" -eq 0 || exit 1 + + echo_i "Get ZSK $alg $id-$zone $type:$bits" + zsk2=$(keyfromlabel $alg $zone enginepkcs11-zsk2 $dir) + test -z "$zsk2" && exit 1 + + echo_i "Get KSK $alg $id-$zone $type:$bits" + ksk2=$(keyfromlabel $alg $zone enginepkcs11-ksk2 $dir -f KSK) + test -z "$ksk2" && exit 1 + + ( + cd $dir + zskid2=$(keyfile_to_key_id $zsk2) + kskid2=$(keyfile_to_key_id $ksk2) + echo "$zskid2" >$zone.zskid2 + echo "$kskid2" >$zone.kskid2 + cp "${zsk2}.key" "${zsk2}.zsk2" + cp "${ksk2}.key" "${ksk2}.ksk2" + ) + + echo_i "Add zone $alg.same-policy.$tld to named.conf" + cp $infile ${dir}/zone.${alg}.same-policy.view1.db + cp $infile ${dir}/zone.${alg}.same-policy.view2.db + + echo_i "Add zone zone-with.different-policy.$tld to named.conf" + cp $infile ${dir}/zone.zone-with.different-policy.view1.db + cp $infile ${dir}/zone.zone-with.different-policy.view2.db + + echo_i "Add zone $zone to named.conf" + cat >>"${dir}/named.conf" <dig.out.soa.$zone.$n || ret=1 - awk '$4 == "RRSIG" { print $11 }' dig.out.soa.$zone.$n >dig.out.keyids.$zone.$n || return 1 + awk '$4 == "RRSIG" { print $11 }' dig.out.soa.$zone.$n >dig.out.keyids.$zone.$n || ret=1 numsigs=$(cat dig.out.keyids.$zone.$n | wc -l) - test $numsigs -eq 1 || return 1 - grep -w "$zskid1" dig.out.keyids.$zone.$n >/dev/null || return 1 + test $numsigs -eq 1 || ret=1 + grep -w "$zskid1" dig.out.keyids.$zone.$n >/dev/null || ret=1 test "$ret" -eq 0 || echo_i "failed (SOA RRset not signed with key $zskid1)" status=$((status + ret)) @@ -94,11 +112,11 @@ EOF n=$((n + 1)) ret=0 echo_i "Test DNSKEY response for $zone after inline signing ($n)" - _dig_dnskey() ( + _dig_dnskey() { dig_with_opts "$zone" @10.53.0.1 DNSKEY >dig.out.dnskey.$zone.$n || return 1 count=$(awk 'BEGIN { count = 0 } $4 == "DNSKEY" { count++ } END {print count}' dig.out.dnskey.$zone.$n) test $count -eq 3 - ) + } retry_quiet 10 _dig_dnskey || ret=1 test "$ret" -eq 0 || echo_i "failed (expected 3 DNSKEY records)" status=$((status + ret)) @@ -106,7 +124,7 @@ EOF n=$((n + 1)) ret=0 echo_i "Test SOA response for $zone after inline signing ($n)" - _dig_soa() ( + _dig_soa() { dig_with_opts "$zone" @10.53.0.1 SOA >dig.out.soa.$zone.$n || return 1 awk '$4 == "RRSIG" { print $11 }' dig.out.soa.$zone.$n >dig.out.keyids.$zone.$n || return 1 numsigs=$(cat dig.out.keyids.$zone.$n | wc -l) @@ -114,7 +132,7 @@ EOF grep -w "$zskid1" dig.out.keyids.$zone.$n >/dev/null || return 1 grep -w "$zskid2" dig.out.keyids.$zone.$n >/dev/null || return 1 return 0 - ) + } retry_quiet 10 _dig_soa || ret=1 test "$ret" -eq 0 || echo_i "failed (expected 2 SOA RRSIG records)" status=$((status + ret)) @@ -142,7 +160,7 @@ EOF n=$((n + 1)) ret=0 echo_i "Test DNSKEY response for $zone after inline signing (key signing) ($n)" - _dig_dnskey_ksk() ( + _dig_dnskey_ksk() { dig_with_opts "$zone" @10.53.0.1 DNSKEY >dig.out.dnskey.$zone.$n || return 1 count=$(awk 'BEGIN { count = 0 } $4 == "DNSKEY" { count++ } END {print count}' dig.out.dnskey.$zone.$n) test $count -eq 4 || return 1 @@ -152,16 +170,245 @@ EOF grep -w "$kskid1" dig.out.keyids.$zone.$n >/dev/null || return 1 grep -w "$kskid2" dig.out.keyids.$zone.$n >/dev/null || return 1 return 0 - ) + } retry_quiet 10 _dig_dnskey_ksk || ret=1 test "$ret" -eq 0 || echo_i "failed (expected 4 DNSKEY records, 2 KSK signatures)" status=$((status + ret)) + # Check dnssec-policy interaction. + + # Basic checks if setup was successful (dnssec-policy). + zone="${alg}.kasp" + n=$((n + 1)) + ret=0 + ret=0 + echo_i "Test key generation was successful for $zone ($n)" + check_keys $zone 2 || ret=1 + status=$((status + ret)) + + n=$((n + 1)) + ret=0 + echo_i "Test DNSKEY response for $zone ($n)" + _dig_policy_dnskey() { + dig_with_opts "$zone" @10.53.0.1 DNSKEY >dig.out.dnskey.$zone.$n || return 1 + count=$(awk 'BEGIN { count = 0 } $4 == "DNSKEY" { count++ } END {print count}' dig.out.dnskey.$zone.$n) + test $count -eq 2 + } + retry_quiet 2 _dig_policy_dnskey || ret=1 + test "$ret" -eq 0 || echo_i "failed (expected 2 DNSKEY records)" + status=$((status + ret)) + + n=$((n + 1)) + ret=0 + echo_i "Test SOA response for $zone ($n)" + _dig_policy_soa() { + dig_with_opts "$zone" @10.53.0.1 SOA >dig.out.soa.$zone.$n || return 1 + awk '$4 == "RRSIG" && $5 == "SOA" { print $11 }' dig.out.soa.$zone.$n >dig.out.keyids.$zone.$n || return 1 + numsigs=$(cat dig.out.keyids.$zone.$n | wc -l) + test $numsigs -eq 1 || return 1 + return 0 + } + retry_quiet 2 _dig_policy_soa || ret=1 + test "$ret" -eq 0 || echo_i "failed (expected a SOA RRSIG record)" + + zone="$alg.\"\:\;\?\&\[\]\@\!\$\*\+\,\|\=\.\(\)foo.weird" + keyfile="${alg}.%22%3A%3B%3F%26%5B%5D%40%21%24%2A%2B%2C%7C%3D%2E%28%29foo.weird" + n=$((n + 1)) + ret=0 + echo_i "Test key generation was successful for $zone ($n)" + check_keys $keyfile 2 || ret=1 + status=$((status + ret)) + + n=$((n + 1)) + ret=0 + echo_i "Test DNSKEY response for $zone ($n)" + retry_quiet 2 _dig_policy_dnskey || ret=1 + test "$ret" -eq 0 || echo_i "failed (expected 2 DNSKEY records)" + status=$((status + ret)) + + n=$((n + 1)) + ret=0 + echo_i "Test SOA response for $zone ($n)" + retry_quiet 2 _dig_policy_soa || ret=1 + test "$ret" -eq 0 || echo_i "failed (expected a SOA RRSIG record)" + status=$((status + ret)) + + # Check a dnssec-policy that uses multiple key-stores. + zone="${alg}.split" + echo_i "Test key generation was successful for $zone ($n)" + # Check KSK. + check_keys $zone 1 || ret=1 + # Check ZSK. + count=$(ls keys/K*.key | grep "K${_zone}" | wc -l) + test "$count" -eq 1 || ret=1 + test "$ret" -eq 0 || echo_i "failed (expected 1 key, got $count)" + status=$((status + ret)) + ret=0 + count=$(cat keys/K${zone}*.private | grep Engine | wc -l) + test "$count" -eq 0 || ret=1 + count=$(cat keys/K${zone}*.private | grep Label | wc -l) + test "$count" -eq 0 || ret=1 + test "$ret" -eq 0 || echo_i "failed (unexpected Engine and Label in key files)" + status=$((status + ret)) + + # Check dnssec-keygen with dnssec-policy and key-store. + zone="${alg}.keygen" + n=$((n + 1)) + ret=0 + echo_i "Test dnssec-keygen for $zone ($n)" + $KEYGEN $ENGINE_ARG -k $alg -l named.conf $zone >keygen.out.$zone.$n 2>/dev/null || ret=1 + check_keys $zone 2 || ret=1 + status=$((status + ret)) + done # Go back to main test dir. cd .. +# Perform tests inside ns2 dir +cd ns2 + +algtypebits="ecdsap256sha256:EC:prime256v1" +alg=$(echo "$algtypebits" | cut -f 1 -d :) +type=$(echo "$algtypebits" | cut -f 2 -d :) +bits=$(echo "$algtypebits" | cut -f 3 -d :) +zone="${alg}.views" +zonefile1="zone.$alg.views.view1.db.signed" +zonefile2="zone.$alg.views.view2.db.signed" + +skip=0 +if [ ! -f $zonefile1 ]; then + echo_i "skipping test for ${alg}:${type}:${bits}, no signed zone file ${zonefile1}" + skip=1 +fi + +if [ ! -f $zonefile2 ]; then + echo_i "skipping test for ${alg}:${type}:${bits}, no signed zone file ${zonefile2}" + skip=1 +fi + +if [ $skip -eq 0 ]; then + # Basic checks if setup was successful. + n=$((n + 1)) + ret=0 + echo_i "Test key generation was successful for $zone ($n)" + check_keys $zone 4 || ret=1 + status=$((status + ret)) + + n=$((n + 1)) + ret=0 + echo_i "Test zone signing was successful for $zone in view1 ($n)" + $VERIFY -z -o $zone "${zonefile1}" >verify.out.$zone.view1.$n 2>&1 || ret=1 + test "$ret" -eq 0 || echo_i "failed (dnssec-verify failed)" + status=$((status + ret)) + + n=$((n + 1)) + ret=0 + echo_i "Test zone signing was successful for $zone in view2 ($n)" + $VERIFY -z -o $zone "${zonefile2}" >verify.out.$zone.view2.$n 2>&1 || ret=1 + test "$ret" -eq 0 || echo_i "failed (dnssec-verify failed)" + status=$((status + ret)) + + # Test dnssec-policy signing with keys stored in engine. + zone="${alg}.same-policy.views" + + n=$((n + 1)) + ret=0 + echo_i "Test key generation was successful for $zone ($n)" + check_keys $zone 1 || ret=1 + status=$((status + ret)) + + _dig_inview() { + _qtype="$1" + _alg="$2" + _tsig="$DEFAULT_HMAC:$3:$4" + dig_with_opts "$zone" @10.53.0.2 $_qtype -y "$_tsig" >dig.out.$zone.$n || return 1 + awk -v cov="$_qtype" '$4 == "RRSIG" && $5 == cov { print $6 }' dig.out.$zone.$n >dig.out.alg.$zone.$n || return 1 + numsigs=$(cat dig.out.alg.$zone.$n | wc -l) + test $numsigs -eq 1 || return 1 + grep -w "$_alg" dig.out.alg.$zone.$n >/dev/null || return 1 + } + + n=$((n + 1)) + ret=0 + echo_i "Test SOA is signed for $zone in view1 ($n)" + VIEW1="YPfMoAk6h+3iN8MDRQC004iSNHY=" + retry_quiet 4 _dig_inview SOA 13 keyforview1 $VIEW1 || ret=1 + test "$ret" -eq 0 || echo_i "failed (SOA RRset not signed)" + status=$((status + ret)) + + n=$((n + 1)) + ret=0 + echo_i "Test DNSKEY is signed for $zone in view1 ($n)" + retry_quiet 4 _dig_inview DNSKEY 13 keyforview1 $VIEW1 || ret=1 + test "$ret" -eq 0 || echo_i "failed (DNSKEY RRset not signed)" + status=$((status + ret)) + + n=$((n + 1)) + ret=0 + echo_i "Test SOA is signed for $zone in view2 ($n)" + VIEW2="4xILSZQnuO1UKubXHkYUsvBRPu8=" + retry_quiet 4 _dig_inview SOA 13 keyforview2 $VIEW2 || ret=1 + test "$ret" -eq 0 || echo_i "failed (SOA RRset not signed)" + status=$((status + ret)) + + n=$((n + 1)) + ret=0 + echo_i "Test DNSKEY is signed for $zone in view2 ($n)" + retry_quiet 4 _dig_inview DNSKEY 13 keyforview2 $VIEW2 || ret=1 + test "$ret" -eq 0 || echo_i "failed (DNSKEY RRset not signed)" + status=$((status + ret)) + + # Now test zone in different views using a different dnssec-policy. + zone="zone-with.different-policy.views" + + n=$((n + 1)) + ret=0 + echo_i "Test key generation was successful for $zone in view1 ($n)" + # view1 + check_keys $zone 1 || ret=1 + status=$((status + ret)) + # view2 + echo_i "Test key generation was successful for $zone in view2 ($n)" + count=$(ls keys/K*.key | grep "K${zone}" | wc -l) + test "$count" -eq 1 || ret=1 + test "$ret" -eq 0 || echo_i "failed (expected 1 key, got $count)" + status=$((status + ret)) + + n=$((n + 1)) + ret=0 + echo_i "Test SOA is signed for $zone in view1 ($n)" + VIEW1="YPfMoAk6h+3iN8MDRQC004iSNHY=" + retry_quiet 4 _dig_inview SOA 13 keyforview1 $VIEW1 || ret=1 + test "$ret" -eq 0 || echo_i "failed (SOA RRset not signed)" + status=$((status + ret)) + + n=$((n + 1)) + ret=0 + echo_i "Test DNSKEY is signed for $zone in view1 ($n)" + retry_quiet 4 _dig_inview DNSKEY 13 keyforview1 $VIEW1 || ret=1 + test "$ret" -eq 0 || echo_i "failed (DNSKEY RRset not signed)" + status=$((status + ret)) + + n=$((n + 1)) + ret=0 + echo_i "Test SOA is signed for $zone in view2 ($n)" + VIEW2="4xILSZQnuO1UKubXHkYUsvBRPu8=" + retry_quiet 4 _dig_inview SOA 8 keyforview2 $VIEW2 || ret=1 + test "$ret" -eq 0 || echo_i "failed (SOA RRset not signed)" + status=$((status + ret)) + + n=$((n + 1)) + ret=0 + echo_i "Test DNSKEY is signed for $zone in view2 ($n)" + retry_quiet 4 _dig_inview DNSKEY 8 keyforview2 $VIEW2 || ret=1 + test "$ret" -eq 0 || echo_i "failed (DNSKEY RRset not signed)" + status=$((status + ret)) +fi + +# Go back to main test dir. +cd .. + n=$((n + 1)) ret=0 echo_i "Checking for assertion failure in pk11_numbits()" diff --git a/bin/tests/system/kasp.sh b/bin/tests/system/kasp.sh index 5f879cbe71..bc8cc9e0f5 100644 --- a/bin/tests/system/kasp.sh +++ b/bin/tests/system/kasp.sh @@ -67,6 +67,8 @@ VIEW3="C1Azf+gGPMmxrUg/WQINP6eV9Y0=" # PRIVKEY_STAT # PUBKEY_STAT # STATE_STAT +# FLAGS +# KEYDIR key_key() { echo "${1}__${2}" @@ -132,6 +134,7 @@ key_clear() { key_set "$1" "PRIVKEY_STAT" '0' key_set "$1" "PUBKEY_STAT" '0' key_set "$1" "STATE_STAT" '0' + key_set "$1" "KEYDIR" 'none' } # Start clear. @@ -176,7 +179,7 @@ get_keyids() { _zone=$2 _regex="K${_zone}.+*+*.key" - find "${_dir}" -mindepth 1 -maxdepth 1 -name "${_regex}" | sed "s,$_dir/K${_zone}.+\([0-9]\{3\}\)+\([0-9]\{5\}\).key,\2," + find "${_dir}" -mindepth 1 -maxdepth 3 -name "${_regex}" | sed "s,.*/K${_zone}.+\([0-9]\{3\}\)+\([0-9]\{5\}\).key,\2," } # By default log errors and don't quit immediately. @@ -313,6 +316,13 @@ set_keystate() { key_set "$1" "$2" "$3" } +# Set key directory. +# $1: Key to update (KEY1, KEY2, ...) +# $2: Directory. +set_keydir() { + key_set "$1" "KEYDIR" "$2" +} + # Check the key $1 with id $2. # This requires environment variables to be set. # @@ -324,7 +334,10 @@ set_keystate() { # KEY_ID=$(echo $1 | sed 's/^0\{0,4\}//') # KEY_CREATED (from the KEY_FILE) check_key() { - _dir="$DIR" + _dir=$(key_get "$1" KEYDIR) + if [ "$_dir" = "none" ]; then + _dir="$DIR" + fi _zone="$ZONE" _role=$(key_get "$1" ROLE) _key_idpad="$2" @@ -465,7 +478,10 @@ check_key() { # Check the key timing metadata for key $1. check_timingmetadata() { - _dir="$DIR" + _dir=$(key_get "$1" KEYDIR) + if [ "$_dir" = "none" ]; then + _dir="$DIR" + fi _zone="$ZONE" _key_idpad=$(key_get "$1" ID) _key_id=$(echo "$_key_idpad" | sed 's/^0\{0,4\}//') @@ -644,11 +660,11 @@ check_keytimes() { # STATE_FILE="${BASE_FILE}.state" # KEY_ID=$(echo $1 | sed 's/^0\{0,4\}//') key_unused() { - _dir=$DIR - _zone=$ZONE - _key_idpad=$1 + _dir="$DIR" + _zone="$ZONE" + _key_idpad="$1" _key_id=$(echo "$_key_idpad" | sed 's/^0\{0,4\}//') - _alg_num=$2 + _alg_num="$2" _alg_numpad=$(printf "%03d" "$_alg_num") BASE_FILE="${_dir}/K${_zone}.+${_alg_numpad}+${_key_idpad}" @@ -788,6 +804,8 @@ _check_keys() { # # It is expected that KEY1, KEY2, KEY3, and KEY4 arrays are set correctly. # Found key identifiers are stored in the right key array. +# Keys are found if they are stored inside $DIR or in a subdirectory up to +# three levels deeper. check_keys() { n=$((n + 1)) echo_i "check keys are created for zone ${ZONE} ($n)" diff --git a/bin/tests/system/kasp/clean.sh b/bin/tests/system/kasp/clean.sh index d31b53a464..8102d50597 100644 --- a/bin/tests/system/kasp/clean.sh +++ b/bin/tests/system/kasp/clean.sh @@ -35,3 +35,5 @@ rm -f rndc.dnssec.*.out.* rndc.zonestatus.out.* rm -f python.out.* rm -f *-supported.file rm -f created.key-* unused.key-* +rm -f ns3/ksk/K* ns3/zsk/K* +rm -rf ./ns3/ksk/ ./ns3/zsk/ diff --git a/bin/tests/system/kasp/ns3/named-fips.conf.in b/bin/tests/system/kasp/ns3/named-fips.conf.in index 01d7592686..3ca1551c29 100644 --- a/bin/tests/system/kasp/ns3/named-fips.conf.in +++ b/bin/tests/system/kasp/ns3/named-fips.conf.in @@ -166,6 +166,14 @@ zone "inline-signing.kasp" { dnssec-policy "default"; }; +/* A zone that uses dnssec-policy with key stores. */ +zone "keystore.kasp" { + type primary; + file "keystore.kasp.db"; + inline-signing yes; + dnssec-policy "keystore"; +}; + /* * A configured dnssec-policy but some keys already created. */ diff --git a/bin/tests/system/kasp/ns3/policies/kasp-fips.conf.in b/bin/tests/system/kasp/ns3/policies/kasp-fips.conf.in index 6778bac4d3..7b775f14b8 100644 --- a/bin/tests/system/kasp/ns3/policies/kasp-fips.conf.in +++ b/bin/tests/system/kasp/ns3/policies/kasp-fips.conf.in @@ -121,3 +121,20 @@ dnssec-policy "checkds-csk" { dnssec-policy "ttl" { max-zone-ttl 299; }; + +key-store "ksk" { + directory "ksk"; +}; + +key-store "zsk" { + directory "zsk"; +}; + +dnssec-policy "keystore" { + dnskey-ttl 303; + + keys { + ksk key-store "ksk" lifetime unlimited algorithm @DEFAULT_ALGORITHM@; + zsk key-store "zsk" lifetime unlimited algorithm @DEFAULT_ALGORITHM@; + }; +}; diff --git a/bin/tests/system/kasp/ns3/setup.sh b/bin/tests/system/kasp/ns3/setup.sh index 4d76d250c6..dd9dc83d8f 100644 --- a/bin/tests/system/kasp/ns3/setup.sh +++ b/bin/tests/system/kasp/ns3/setup.sh @@ -16,6 +16,10 @@ echo_i "ns3/setup.sh" +# Create key store directories. +mkdir ksk +mkdir zsk + setup() { zone="$1" echo_i "setting up zone: $zone" @@ -46,7 +50,7 @@ for zn in default dnssec-keygen some-keys legacy-keys pregenerated \ rumoured rsasha256 rsasha512 ecdsa256 ecdsa384 \ dynamic dynamic-inline-signing inline-signing \ checkds-ksk checkds-doubleksk checkds-csk inherit unlimited \ - manual-rollover multisigner-model2; do + manual-rollover multisigner-model2 keystore; do setup "${zn}.kasp" cp template.db.in "$zonefile" done diff --git a/bin/tests/system/kasp/tests.sh b/bin/tests/system/kasp/tests.sh index 59dd4d391a..3c2484cc4d 100644 --- a/bin/tests/system/kasp/tests.sh +++ b/bin/tests/system/kasp/tests.sh @@ -97,6 +97,7 @@ set_zonesigning "KEY4" "yes" lines=$(get_keyids "$DIR" "$ZONE" | wc -l) test "$lines" -eq $NUM_KEYS || log_error "bad number of key ids" +status=$((status + ret)) ids=$(get_keyids "$DIR" "$ZONE") for id in $ids; do @@ -127,6 +128,7 @@ set_zone "kasp" set_policy "default" "1" "3600" set_server "." "10.53.0.1" # Key properties. +key_clear "KEY1" set_keyrole "KEY1" "csk" set_keylifetime "KEY1" "0" set_keyalgorithm "KEY1" "13" "ECDSAP256SHA256" "256" @@ -508,7 +510,6 @@ n=$((n + 1)) echo_i "check if resigning the raw version of the zone is prevented for zone ${ZONE} ($n)" ret=0 grep "zone_resigninc: zone $ZONE/IN (unsigned): enter" $DIR/named.run && ret=1 -grep "error reading K$ZONE" $DIR/named.run && ret=1 test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) @@ -538,6 +539,7 @@ key_clear "KEY4" set_zone "checkds-ksk.kasp" set_policy "checkds-ksk" "2" "303" set_server "ns3" "10.53.0.3" + # Key properties. set_keyrole "KEY1" "ksk" set_keylifetime "KEY1" "0" @@ -940,6 +942,55 @@ check_apex check_subdomain dnssec_verify +# +# Zone: keystore.kasp. +# +set_zone "keystore.kasp" +set_policy "keystore" "2" "303" +set_server "ns3" "10.53.0.3" +# Key properties. +key_clear "KEY1" +set_keyrole "KEY1" "ksk" +set_keylifetime "KEY1" "0" +set_keydir "KEY1" "ns3/ksk" +set_keyalgorithm "KEY1" "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS" +set_keysigning "KEY1" "yes" +set_zonesigning "KEY1" "no" + +key_clear "KEY2" +set_keyrole "KEY2" "zsk" +set_keylifetime "KEY2" "0" +set_keydir "KEY2" "ns3/zsk" +set_keyalgorithm "KEY2" "$DEFAULT_ALGORITHM_NUMBER" "$DEFAULT_ALGORITHM" "$DEFAULT_BITS" +set_keysigning "KEY2" "no" +set_zonesigning "KEY2" "yes" + +# KSK: DNSKEY, RRSIG (ksk) published. DS needs to wait. +# ZSK: DNSKEY, RRSIG (zsk) published. +set_keystate "KEY1" "GOAL" "omnipresent" +set_keystate "KEY1" "STATE_DNSKEY" "rumoured" +set_keystate "KEY1" "STATE_KRRSIG" "rumoured" +set_keystate "KEY1" "STATE_DS" "hidden" + +set_keystate "KEY2" "GOAL" "omnipresent" +set_keystate "KEY2" "STATE_DNSKEY" "rumoured" +set_keystate "KEY2" "STATE_ZRRSIG" "rumoured" +# Two keys only. +key_clear "KEY3" +key_clear "KEY4" + +check_keys +check_dnssecstatus "$SERVER" "$POLICY" "$ZONE" +# Reuse set_keytimes_csk_policy to set the KEY1 keytimes. +set_keytimes_csk_policy +created=$(key_get KEY2 CREATED) +set_keytime "KEY2" "PUBLISHED" "${created}" +set_keytime "KEY2" "ACTIVE" "${created}" +check_keytimes +check_apex +check_subdomain +dnssec_verify + # # Zone: inherit.kasp. # diff --git a/bin/tests/system/multisigner/tests.sh b/bin/tests/system/multisigner/tests.sh index 36d6252902..abe19ff215 100644 --- a/bin/tests/system/multisigner/tests.sh +++ b/bin/tests/system/multisigner/tests.sh @@ -147,7 +147,7 @@ status=$((status + ret)) n=$((n + 1)) echo_i "make sure we did not try to sign with the keys added with nsupdate for zone ${ZONE} ($n)" ret=0 -grep "dns_dnssec_findzonekeys: error reading ./K${ZONE}.*\.private: file not found" "${DIR}/named.run" && ret=1 +grep "dns_zone_findkeys: error reading ./K${ZONE}.*\.private: file not found" "${DIR}/named.run" && ret=1 test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) # Verify again. @@ -176,7 +176,7 @@ status=$((status + ret)) n=$((n + 1)) echo_i "make sure we did not try to sign with the keys added with nsupdate for zone ${ZONE} ($n)" ret=0 -grep "dns_dnssec_findzonekeys: error reading ./K${ZONE}.*\.private: file not found" "${DIR}/named.run" && ret=1 +grep "dns_zone_findkeys: error reading ./K${ZONE}.*\.private: file not found" "${DIR}/named.run" && ret=1 test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) # Verify again. @@ -521,7 +521,7 @@ test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) dnssec_verify no_dnssec_in_journal -grep "dns_dnssec_findzonekeys: error reading ./K${ZONE}.*\.private: file not found" "${DIR}/named.run" && ret=1 +grep "dns_zone_findkeys: error reading ./K${ZONE}.*\.private: file not found" "${DIR}/named.run" && ret=1 test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) # NS4 @@ -534,7 +534,7 @@ test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) dnssec_verify no_dnssec_in_journal -grep "dns_dnssec_findzonekeys: error reading ./K${ZONE}.*\.private: file not found" "${DIR}/named.run" && ret=1 +grep "dns_zone_findkeys: error reading ./K${ZONE}.*\.private: file not found" "${DIR}/named.run" && ret=1 test "$ret" -eq 0 || echo_i "failed" status=$((status + ret)) diff --git a/doc/arm/pkcs11.inc.rst b/doc/arm/pkcs11.inc.rst index 78de07bcf3..7a586802fb 100644 --- a/doc/arm/pkcs11.inc.rst +++ b/doc/arm/pkcs11.inc.rst @@ -91,6 +91,11 @@ When using engine_pkcs11, all BIND binaries potentially need the keys require Even though OpenSSL 3 has compatibility support for Engine API it is not recommended to be used due to bugs in OpenSSL and libp11. +It is not possible to generate new keys via the engine_pkcs11 and therefore it +is not recommended to use it in a ``dnssec-policy`` setup (although it is +possible to put previously generated keys in the ``key-directory`` and let the +key manager select those keys when a key rollover is started. + Configuring engine_pkcs11 ^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -170,8 +175,8 @@ path to the PKCS#11 module which should be gatewayed to. This can be done by editing the OpenSSL configuration file, by engine specific controls, or by using the p11-kit proxy module. -It is recommended that pkcs11-provider git commit 8672b98d2558aecb49f173df97b1463c7697b540 -from August 15, 2023 or later is used. +It is required to use pkcs11-provider git commit +2e8c26b4157fd21422c66f0b4d7b26cf8c320570 from October 2, 2023 or later. BIND support for pkcs11-provider is built in and the -E command line option explained above should not be used. diff --git a/doc/arm/reference.rst b/doc/arm/reference.rst index 69d632802d..3ebcfc82a5 100644 --- a/doc/arm/reference.rst +++ b/doc/arm/reference.rst @@ -407,6 +407,9 @@ The following blocks are supported: :namedconf:ref:`key` Specifies key information for use in authentication and authorization using TSIG. + :any:``key-store`` + Describes a DNSSEC key store. See :ref:`key-store Grammar ` for details. + :any:`logging` Specifies what information the server logs and where the log messages are sent. @@ -592,6 +595,42 @@ matching this name, algorithm, and secret. The ``secret_string`` is the secret to be used by the algorithm, and is treated as a Base64-encoded string. +.. _key_store_grammar: + +:any:`key-store` Block Grammar +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +.. namedconf:statement:: key-store + :tags: dnssec + :short: Configures a DNSSEC key store. + +.. _key_store_statement: + +``key-store`` Block Definition and Usage +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +The ``key-store`` statement defines how DNSSEC keys should be stored. + +There is one built-in key store named ``key-directory``. Configuring +keys to use ``key-store "key-directory"`` is identical to using +``key-directory``. + +The following options can be specified in a :any:`key-store` statement: + +.. directory + + The ``directory`` specifies where key files for this key should be stored. + This is similar to using the zone's ``key-directory``. + +.. namedconf:statement:: pkcs11-uri + :tags: dnssec, pkcs11 + + The ``uri`` is a string that specifies a PKCS#11 URI Scheme (defined in + :rfc:`7512`). When set, ``named`` will try to create keys inside the + corresponding PKCS#11 token. This requires BIND to be built with OpenSSL 3, + and have a PKCS#11 provider configured. + +.. _logging_grammar: + :any:`logging` Block Grammar ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ .. namedconf:statement:: logging @@ -6282,7 +6321,10 @@ The following options can be specified in a :any:`dnssec-policy` statement: This behavior is enabled by default. -:any:`keys` +.. keys + :tags: dnssec + :short: Specifies the type of keys to be used for DNSSEC signing. + This is a list specifying the algorithms and roles to use when generating keys and signing the zone. Entries in this list do not represent specific DNSSEC keys, which may be changed on a regular @@ -6298,7 +6340,7 @@ The following options can be specified in a :any:`dnssec-policy` statement: keys { ksk key-directory lifetime unlimited algorithm rsasha256 2048; zsk lifetime 30d algorithm 8; - csk lifetime P6MT12H3M15S algorithm ecdsa256; + csk key-store "hsm" lifetime P6MT12H3M15S algorithm ecdsa256; }; This example specifies that three keys should be used in the zone. @@ -6311,9 +6353,15 @@ The following options can be specified in a :any:`dnssec-policy` statement: used to sign all RRsets. An optional second token determines where the key is stored. - Currently, keys can only be stored in the configured - :any:`key-directory`. This token may be used in the future to store - keys in hardware security modules or separate directories. + The two available options are ``key-store `` and + ``key-directory``. + + When using ``key-store``, the referenced :any:`key-store` describes + how the key should be be stored. This can be as a file, or it can be + inside a PKCS#11 token. + + When using ``key-directory``, the key is stored in the zone's + configured :any:`key-directory`. This is also the default. The ``lifetime`` parameter specifies how long a key may be used before rolling over. For convenience, TTL-style time-unit suffixes @@ -6346,6 +6394,13 @@ The following options can be specified in a :any:`dnssec-policy` statement: Each KSK/ZSK pair must have the same algorithm. A CSK combines the functionality of a ZSK and a KSK. +.. note:: When changing the ``key-directory`` or the ``key-store``, BIND will + be unable to find existing key files. Make sure you copy key files to the + new directory before changing the path used in the configuration file. + This is also true when changing to a built-in policy, for example to + ``insecure``. In this specific case you should move the existing key files + to the zone's ``key-directory`` from the new configuration. + .. namedconf:statement:: purge-keys :tags: dnssec :short: Specifies the amount of time after which DNSSEC keys that have been deleted from the zone can be removed from disk. diff --git a/doc/misc/options b/doc/misc/options index edf6fb04af..25c23e9e06 100644 --- a/doc/misc/options +++ b/doc/misc/options @@ -15,7 +15,7 @@ dnssec-policy { cds-digest-types { ; ... }; dnskey-ttl ; inline-signing ; - keys { ( csk | ksk | zsk ) [ ( key-directory ) ] lifetime algorithm [ ]; ... }; + keys { ( csk | ksk | zsk ) [ key-directory | key-store ] lifetime algorithm [ ]; ... }; max-zone-ttl ; nsec3param [ iterations ] [ optout ] [ salt-length ]; parent-ds-ttl ; @@ -42,6 +42,11 @@ key { secret ; }; // may occur multiple times +key-store { + directory ; + pkcs11-uri ; +}; // may occur multiple times + logging { category { ; ... }; // may occur multiple times channel { diff --git a/doc/notes/notes-current.rst b/doc/notes/notes-current.rst index 9867bbdc3a..9aa27c16ae 100644 --- a/doc/notes/notes-current.rst +++ b/doc/notes/notes-current.rst @@ -28,6 +28,11 @@ New Features - The statistics channel now includes counters that indicate the number of currently connected TCP IPv4/IPv6 clients. :gl:`#4425` +- Add HSM support to :any:`dnssec-policy`. You can now configure keys with a + ``key-store`` that allows you to set the directory to store the key files and + set a PKCS#11 URI string. The latter requires OpenSSL 3 and a valid PKCS#11 + provider to be configured for OpenSSL. :gl`#1129`. + Removed Features ~~~~~~~~~~~~~~~~ diff --git a/lib/dns/Makefile.am b/lib/dns/Makefile.am index 27a5f14ddb..e18a393609 100644 --- a/lib/dns/Makefile.am +++ b/lib/dns/Makefile.am @@ -83,6 +83,7 @@ libdns_la_HEADERS = \ include/dns/keydata.h \ include/dns/keyflags.h \ include/dns/keymgr.h \ + include/dns/keystore.h \ include/dns/keytable.h \ include/dns/keyvalues.h \ include/dns/librpz.h \ @@ -190,6 +191,7 @@ libdns_la_SOURCES = \ key.c \ keydata.c \ keymgr.c \ + keystore.c \ keytable.c \ log.c \ master.c \ diff --git a/lib/dns/dnssec.c b/lib/dns/dnssec.c index 6b45dfc117..eaf7ef6f11 100644 --- a/lib/dns/dnssec.c +++ b/lib/dns/dnssec.c @@ -759,177 +759,6 @@ syncdelete(dst_key_t *key, isc_stdtime_t now) { #define is_zone_key(key) \ ((dst_key_flags(key) & DNS_KEYFLAG_OWNERMASK) == DNS_KEYOWNER_ZONE) -isc_result_t -dns_dnssec_findzonekeys(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node, - const dns_name_t *name, const char *directory, - isc_stdtime_t now, isc_mem_t *mctx, - unsigned int maxkeys, dst_key_t **keys, - unsigned int *nkeys) { - dns_rdataset_t rdataset; - dns_rdata_t rdata = DNS_RDATA_INIT; - isc_result_t result; - dst_key_t *pubkey = NULL; - unsigned int count = 0; - - REQUIRE(nkeys != NULL); - REQUIRE(keys != NULL); - - *nkeys = 0; - memset(keys, 0, sizeof(*keys) * maxkeys); - dns_rdataset_init(&rdataset); - RETERR(dns_db_findrdataset(db, node, ver, dns_rdatatype_dnskey, 0, 0, - &rdataset, NULL)); - RETERR(dns_rdataset_first(&rdataset)); - while (result == ISC_R_SUCCESS && count < maxkeys) { - pubkey = NULL; - dns_rdataset_current(&rdataset, &rdata); - RETERR(dns_dnssec_keyfromrdata(name, &rdata, mctx, &pubkey)); - dst_key_setttl(pubkey, rdataset.ttl); - - if (!is_zone_key(pubkey) || - (dst_key_flags(pubkey) & DNS_KEYTYPE_NOAUTH) != 0) - { - goto next; - } - /* Corrupted .key file? */ - if (!dns_name_equal(name, dst_key_name(pubkey))) { - goto next; - } - keys[count] = NULL; - result = dst_key_fromfile( - dst_key_name(pubkey), dst_key_id(pubkey), - dst_key_alg(pubkey), - DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE, - directory, mctx, &keys[count]); - - /* - * If the key was revoked and the private file - * doesn't exist, maybe it was revoked internally - * by named. Try loading the unrevoked version. - */ - if (result == ISC_R_FILENOTFOUND) { - uint32_t flags; - flags = dst_key_flags(pubkey); - if ((flags & DNS_KEYFLAG_REVOKE) != 0) { - dst_key_setflags(pubkey, - flags & ~DNS_KEYFLAG_REVOKE); - result = dst_key_fromfile( - dst_key_name(pubkey), - dst_key_id(pubkey), dst_key_alg(pubkey), - DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | - DST_TYPE_STATE, - directory, mctx, &keys[count]); - if (result == ISC_R_SUCCESS && - dst_key_pubcompare(pubkey, keys[count], - false)) - { - dst_key_setflags(keys[count], flags); - } - dst_key_setflags(pubkey, flags); - } - } - - if (result != ISC_R_SUCCESS) { - char filename[DNS_NAME_FORMATSIZE + - DNS_SECALG_FORMATSIZE + - sizeof("key file for //65535")]; - isc_result_t result2; - isc_buffer_t buf; - - isc_buffer_init(&buf, filename, NAME_MAX); - result2 = dst_key_getfilename( - dst_key_name(pubkey), dst_key_id(pubkey), - dst_key_alg(pubkey), - (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | - DST_TYPE_STATE), - directory, mctx, &buf); - if (result2 != ISC_R_SUCCESS) { - char namebuf[DNS_NAME_FORMATSIZE]; - char algbuf[DNS_SECALG_FORMATSIZE]; - - dns_name_format(dst_key_name(pubkey), namebuf, - sizeof(namebuf)); - dns_secalg_format(dst_key_alg(pubkey), algbuf, - sizeof(algbuf)); - snprintf(filename, sizeof(filename) - 1, - "key file for %s/%s/%d", namebuf, - algbuf, dst_key_id(pubkey)); - } - - isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, - DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING, - "dns_dnssec_findzonekeys: error " - "reading %s: %s", - filename, isc_result_totext(result)); - } - - if (result == ISC_R_FILENOTFOUND || result == ISC_R_NOPERM) { - keys[count] = pubkey; - pubkey = NULL; - count++; - goto next; - } - - if (result != ISC_R_SUCCESS) { - goto failure; - } - - /* - * If a key is marked inactive, skip it - */ - if (!dns_dnssec_keyactive(keys[count], now)) { - dst_key_setinactive(pubkey, true); - dst_key_free(&keys[count]); - keys[count] = pubkey; - pubkey = NULL; - count++; - goto next; - } - - /* - * Whatever the key's default TTL may have - * been, the rdataset TTL takes priority. - */ - dst_key_setttl(keys[count], rdataset.ttl); - - if ((dst_key_flags(keys[count]) & DNS_KEYTYPE_NOAUTH) != 0) { - /* We should never get here. */ - dst_key_free(&keys[count]); - goto next; - } - count++; - next: - if (pubkey != NULL) { - dst_key_free(&pubkey); - } - dns_rdata_reset(&rdata); - result = dns_rdataset_next(&rdataset); - } - if (result != ISC_R_NOMORE) { - goto failure; - } - if (count == 0) { - result = ISC_R_NOTFOUND; - } else { - result = ISC_R_SUCCESS; - } - -failure: - if (dns_rdataset_isassociated(&rdataset)) { - dns_rdataset_disassociate(&rdataset); - } - if (pubkey != NULL) { - dst_key_free(&pubkey); - } - if (result != ISC_R_SUCCESS) { - while (count > 0) { - dst_key_free(&keys[--count]); - } - } - *nkeys = count; - return (result); -} - isc_result_t dns_dnssec_signmessage(dns_message_t *msg, dst_key_t *key) { dns_rdata_sig_t sig; /* SIG(0) */ @@ -1396,32 +1225,18 @@ dns_dnssec_get_hints(dns_dnsseckey_t *key, isc_stdtime_t now) { } } -/*% - * Get a list of DNSSEC keys from the key repository. - */ -isc_result_t -dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory, - isc_stdtime_t now, isc_mem_t *mctx, - dns_dnsseckeylist_t *keylist) { +static isc_result_t +findmatchingkeys(const char *directory, char *namebuf, unsigned int len, + isc_mem_t *mctx, isc_stdtime_t now, + dns_dnsseckeylist_t *list) { isc_result_t result = ISC_R_SUCCESS; - bool dir_open = false; - dns_dnsseckeylist_t list; isc_dir_t dir; + bool dir_open = false; + unsigned int i, alg; dns_dnsseckey_t *key = NULL; dst_key_t *dstkey = NULL; - char namebuf[DNS_NAME_FORMATSIZE]; - isc_buffer_t b; - unsigned int len, i, alg; - REQUIRE(keylist != NULL); - ISC_LIST_INIT(list); isc_dir_init(&dir); - - isc_buffer_init(&b, namebuf, sizeof(namebuf) - 1); - RETERR(dns_name_tofilenametext(origin, false, &b)); - len = isc_buffer_usedlength(&b); - namebuf[len] = '\0'; - if (directory == NULL) { directory = "."; } @@ -1508,11 +1323,70 @@ dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory, if (key->legacy) { dns_dnsseckey_destroy(mctx, &key); } else { - ISC_LIST_APPEND(list, key, link); + ISC_LIST_APPEND(*list, key, link); key = NULL; } } +failure: + if (dir_open) { + isc_dir_close(&dir); + } + if (dstkey != NULL) { + dst_key_free(&dstkey); + } + return (result); +} + +/*% + * Get a list of DNSSEC keys from the key repository. + */ +isc_result_t +dns_dnssec_findmatchingkeys(const dns_name_t *origin, dns_kasp_t *kasp, + const char *keydir, dns_keystorelist_t *keystores, + isc_stdtime_t now, isc_mem_t *mctx, + dns_dnsseckeylist_t *keylist) { + isc_result_t result = ISC_R_SUCCESS; + dns_dnsseckeylist_t list; + dns_dnsseckey_t *key = NULL; + char namebuf[DNS_NAME_FORMATSIZE]; + isc_buffer_t b; + unsigned int len; + + REQUIRE(keylist != NULL); + ISC_LIST_INIT(list); + + isc_buffer_init(&b, namebuf, sizeof(namebuf) - 1); + RETERR(dns_name_tofilenametext(origin, false, &b)); + len = isc_buffer_usedlength(&b); + namebuf[len] = '\0'; + + if (kasp == NULL || (strcmp(dns_kasp_getname(kasp), "none") == 0) || + (strcmp(dns_kasp_getname(kasp), "insecure") == 0)) + { + RETERR(findmatchingkeys(keydir, namebuf, len, mctx, now, + &list)); + } else if (keystores != NULL) { + for (dns_keystore_t *keystore = ISC_LIST_HEAD(*keystores); + keystore != NULL; keystore = ISC_LIST_NEXT(keystore, link)) + { + for (dns_kasp_key_t *kkey = + ISC_LIST_HEAD(dns_kasp_keys(kasp)); + kkey != NULL; kkey = ISC_LIST_NEXT(kkey, link)) + { + if (dns_kasp_key_keystore(kkey) == keystore) { + const char *directory = + dns_keystore_directory(keystore, + keydir); + RETERR(findmatchingkeys( + directory, namebuf, len, mctx, + now, &list)); + break; + } + } + } + } + if (!ISC_LIST_EMPTY(list)) { result = ISC_R_SUCCESS; ISC_LIST_APPENDLIST(*keylist, list, link); @@ -1521,19 +1395,12 @@ dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory, } failure: - if (dir_open) { - isc_dir_close(&dir); - } - INSIST(key == NULL); while ((key = ISC_LIST_HEAD(list)) != NULL) { ISC_LIST_UNLINK(list, key, link); INSIST(key->key != NULL); dst_key_free(&key->key); dns_dnsseckey_destroy(mctx, &key); } - if (dstkey != NULL) { - dst_key_free(&dstkey); - } return (result); } @@ -1641,15 +1508,46 @@ mark_active_keys(dns_dnsseckeylist_t *keylist, dns_rdataset_t *rrsigs) { return (result); } +static isc_result_t +keyfromfile(dns_kasp_t *kasp, const char *keydir, dst_key_t *key, int type, + isc_mem_t *mctx, dst_key_t **savekey) { + const char *directory = keydir; + isc_result_t result = ISC_R_NOTFOUND; + + if (kasp == NULL || (strcmp(dns_kasp_getname(kasp), "none") == 0) || + (strcmp(dns_kasp_getname(kasp), "insecure") == 0)) + { + result = dst_key_fromfile(dst_key_name(key), dst_key_id(key), + dst_key_alg(key), type, directory, + mctx, savekey); + } else { + for (dns_kasp_key_t *kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); + kkey != NULL; kkey = ISC_LIST_NEXT(kkey, link)) + { + dns_keystore_t *ks = dns_kasp_key_keystore(kkey); + directory = dns_keystore_directory(ks, keydir); + result = dst_key_fromfile(dst_key_name(key), + dst_key_id(key), + dst_key_alg(key), type, + directory, mctx, savekey); + if (result == ISC_R_SUCCESS) { + break; + } + } + } + + return (result); +} + /*% * Add the contents of a DNSKEY rdataset 'keyset' to 'keylist'. */ isc_result_t -dns_dnssec_keylistfromrdataset(const dns_name_t *origin, const char *directory, - isc_mem_t *mctx, dns_rdataset_t *keyset, - dns_rdataset_t *keysigs, dns_rdataset_t *soasigs, - bool savekeys, bool publickey, - dns_dnsseckeylist_t *keylist) { +dns_dnssec_keylistfromrdataset(const dns_name_t *origin, dns_kasp_t *kasp, + const char *directory, isc_mem_t *mctx, + dns_rdataset_t *keyset, dns_rdataset_t *keysigs, + dns_rdataset_t *soasigs, bool savekeys, + bool publickey, dns_dnsseckeylist_t *keylist) { dns_rdataset_t keys; dns_rdata_t rdata = DNS_RDATA_INIT; dst_key_t *dnskey = NULL, *pubkey = NULL, *privkey = NULL; @@ -1695,21 +1593,19 @@ dns_dnssec_keylistfromrdataset(const dns_name_t *origin, const char *directory, } /* Try to read the public key. */ - result = dst_key_fromfile( - dst_key_name(dnskey), dst_key_id(dnskey), - dst_key_alg(dnskey), (DST_TYPE_PUBLIC | DST_TYPE_STATE), - directory, mctx, &pubkey); + result = keyfromfile(kasp, directory, dnskey, + (DST_TYPE_PUBLIC | DST_TYPE_STATE), mctx, + &pubkey); if (result == ISC_R_FILENOTFOUND || result == ISC_R_NOPERM) { result = ISC_R_SUCCESS; } RETERR(result); /* Now read the private key. */ - result = dst_key_fromfile( - dst_key_name(dnskey), dst_key_id(dnskey), - dst_key_alg(dnskey), + result = keyfromfile( + kasp, directory, dnskey, (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE), - directory, mctx, &privkey); + mctx, &privkey); /* * If the key was revoked and the private file @@ -1722,12 +1618,11 @@ dns_dnssec_keylistfromrdataset(const dns_name_t *origin, const char *directory, if ((flags & DNS_KEYFLAG_REVOKE) != 0) { dst_key_setflags(dnskey, flags & ~DNS_KEYFLAG_REVOKE); - result = dst_key_fromfile( - dst_key_name(dnskey), - dst_key_id(dnskey), dst_key_alg(dnskey), - (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | - DST_TYPE_STATE), - directory, mctx, &privkey); + result = keyfromfile(kasp, directory, dnskey, + (DST_TYPE_PUBLIC | + DST_TYPE_PRIVATE | + DST_TYPE_STATE), + mctx, &privkey); if (result == ISC_R_SUCCESS && dst_key_pubcompare(dnskey, privkey, false)) { @@ -1750,7 +1645,7 @@ dns_dnssec_keylistfromrdataset(const dns_name_t *origin, const char *directory, dst_key_alg(dnskey), (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE), - directory, mctx, &buf); + NULL, mctx, &buf); if (result2 != ISC_R_SUCCESS) { char namebuf[DNS_NAME_FORMATSIZE]; char algbuf[DNS_SECALG_FORMATSIZE]; diff --git a/lib/dns/dst_api.c b/lib/dns/dst_api.c index af53947ac6..90d0f8dfe1 100644 --- a/lib/dns/dst_api.c +++ b/lib/dns/dst_api.c @@ -691,6 +691,10 @@ dst_key_fromnamedfile(const char *filename, const char *dirname, int type, } key->modified = false; + + if (dirname != NULL) { + key->directory = isc_mem_strdup(mctx, dirname); + } *keyp = key; key = NULL; @@ -1027,8 +1031,8 @@ dst_key_fromlabel(const dns_name_t *name, int alg, unsigned int flags, isc_result_t dst_key_generate(const dns_name_t *name, unsigned int alg, unsigned int bits, unsigned int param, unsigned int flags, unsigned int protocol, - dns_rdataclass_t rdclass, isc_mem_t *mctx, dst_key_t **keyp, - void (*callback)(int)) { + dns_rdataclass_t rdclass, const char *label, isc_mem_t *mctx, + dst_key_t **keyp, void (*callback)(int)) { dst_key_t *key; isc_result_t ret; @@ -1042,6 +1046,10 @@ dst_key_generate(const dns_name_t *name, unsigned int alg, unsigned int bits, key = get_key_struct(name, alg, flags, protocol, bits, rdclass, 0, mctx); + if (label != NULL) { + key->label = isc_mem_strdup(mctx, label); + } + if (bits == 0) { /*%< NULL KEY */ key->key_flags |= DNS_KEYTYPE_NOKEY; *keyp = key; @@ -1391,6 +1399,9 @@ dst_key_free(dst_key_t **keyp) { INSIST(key->func->destroy != NULL); key->func->destroy(key); } + if (key->directory != NULL) { + isc_mem_free(mctx, key->directory); + } if (key->engine != NULL) { isc_mem_free(mctx, key->engine); } diff --git a/lib/dns/dst_internal.h b/lib/dns/dst_internal.h index ad9e097fd9..a78b710738 100644 --- a/lib/dns/dst_internal.h +++ b/lib/dns/dst_internal.h @@ -91,6 +91,7 @@ struct dst_key { dns_rdataclass_t key_class; /*%< class of the key record */ dns_ttl_t key_ttl; /*%< default/initial dnskey ttl */ isc_mem_t *mctx; /*%< memory context */ + char *directory; /*%< key directory */ char *engine; /*%< engine name (HSM) */ char *label; /*%< engine label (HSM) */ union { diff --git a/lib/dns/include/dns/dnssec.h b/lib/dns/include/dns/dnssec.h index 903d40c4f7..7a6c5b5bc9 100644 --- a/lib/dns/include/dns/dnssec.h +++ b/lib/dns/include/dns/dnssec.h @@ -22,6 +22,7 @@ #include #include +#include #include #include @@ -176,20 +177,6 @@ dns_dnssec_verify(const dns_name_t *name, dns_rdataset_t *set, dst_key_t *key, *\li DST_R_* */ -/*@{*/ -isc_result_t -dns_dnssec_findzonekeys(dns_db_t *db, dns_dbversion_t *ver, dns_dbnode_t *node, - const dns_name_t *name, const char *directory, - isc_stdtime_t now, isc_mem_t *mctx, - unsigned int maxkeys, dst_key_t **keys, - unsigned int *nkeys); - -/*%< - * Finds a set of zone keys. - * XXX temporary - this should be handled in dns_zone_t. - */ -/*@}*/ - bool dns_dnssec_keyactive(dst_key_t *key, isc_stdtime_t now); /*%< @@ -295,11 +282,15 @@ dns_dnssec_get_hints(dns_dnsseckey_t *key, isc_stdtime_t now); */ isc_result_t -dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory, +dns_dnssec_findmatchingkeys(const dns_name_t *origin, dns_kasp_t *kasp, + const char *keydir, dns_keystorelist_t *keystores, isc_stdtime_t now, isc_mem_t *mctx, dns_dnsseckeylist_t *keylist); /*%< - * Search 'directory' for K* key files matching the name in 'origin'. + * Search for K* key files matching the name in 'origin'. If 'kasp' is not + * NULL, search in the directories used in 'keystores'. Otherwise search in the + * key-directory 'keydir'. + * * Append all such keys, along with use hints gleaned from their * metadata, onto 'keylist'. Skip any unsupported algorithms. * @@ -318,17 +309,18 @@ dns_dnssec_findmatchingkeys(const dns_name_t *origin, const char *directory, */ isc_result_t -dns_dnssec_keylistfromrdataset(const dns_name_t *origin, const char *directory, - isc_mem_t *mctx, dns_rdataset_t *keyset, - dns_rdataset_t *keysigs, dns_rdataset_t *soasigs, - bool savekeys, bool publickey, - dns_dnsseckeylist_t *keylist); +dns_dnssec_keylistfromrdataset(const dns_name_t *origin, dns_kasp_t *kasp, + const char *directory, isc_mem_t *mctx, + dns_rdataset_t *keyset, dns_rdataset_t *keysigs, + dns_rdataset_t *soasigs, bool savekeys, + bool publickey, dns_dnsseckeylist_t *keylist); /*%< * Append the contents of a DNSKEY rdataset 'keyset' to 'keylist'. - * Omit duplicates. If 'publickey' is false, search 'directory' for - * matching key files, and load the private keys that go with - * the public ones. If 'savekeys' is true, mark the keys so - * they will not be deleted or inactivated regardless of metadata. + * Omit duplicates. If 'publickey' is false, search the key stores referenced + * in 'kasp', or 'directory' if 'kasp' is NULL, for matching key files, and + * load the private keys that go with the public ones. If 'savekeys' is true, + * mark the keys so they will not be deleted or inactivated regardless of + * metadata. * * 'keysigs' and 'soasigs', if not NULL and associated, contain the * RRSIGS for the DNSKEY and SOA records respectively and are used to mark diff --git a/lib/dns/include/dns/kasp.h b/lib/dns/include/dns/kasp.h index 9a32f586b2..42fe126396 100644 --- a/lib/dns/include/dns/kasp.h +++ b/lib/dns/include/dns/kasp.h @@ -30,6 +30,7 @@ #include #include +#include #include ISC_LANG_BEGINDECLS @@ -51,10 +52,11 @@ struct dns_kasp_key { ISC_LINK(struct dns_kasp_key) link; /* Configuration */ - uint32_t lifetime; - uint8_t algorithm; - int length; - uint8_t role; + dns_keystore_t *keystore; + uint32_t lifetime; + uint8_t algorithm; + int length; + uint8_t role; }; struct dns_kasp_nsec3param { @@ -643,6 +645,21 @@ dns_kasp_key_lifetime(dns_kasp_key_t *key); * */ +dns_keystore_t * +dns_kasp_key_keystore(dns_kasp_key_t *key); +/*%< + * The keystore reference of this key. + * + * Requires: + * + *\li key != NULL + * + * Returns: + * + *\li Keystore of key, or NULL if zone's key-directory is used. + * + */ + bool dns_kasp_key_ksk(dns_kasp_key_t *key); /*%< diff --git a/lib/dns/include/dns/keymgr.h b/lib/dns/include/dns/keymgr.h index bf08fbb549..eadb0ea877 100644 --- a/lib/dns/include/dns/keymgr.h +++ b/lib/dns/include/dns/keymgr.h @@ -26,13 +26,13 @@ ISC_LANG_BEGINDECLS isc_result_t dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, - const char *directory, isc_mem_t *mctx, - dns_dnsseckeylist_t *keyring, dns_dnsseckeylist_t *dnskeys, + isc_mem_t *mctx, dns_dnsseckeylist_t *keyring, + dns_dnsseckeylist_t *dnskeys, const char *keydir, dns_kasp_t *kasp, isc_stdtime_t now, isc_stdtime_t *nexttime); /*%< * Manage keys in 'keyring' and update timing data according to 'kasp' policy. - * Create new keys for 'origin' if necessary in 'directory'. Append all such - * keys, along with use hints gleaned from their metadata, onto 'keyring'. + * Create new keys for 'origin' if necessary. Append all such keys, along + * with use hints gleaned from their metadata, onto 'keyring'. * * Update key states and store changes back to disk. Store when to run next * in 'nexttime'. @@ -54,13 +54,11 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, isc_result_t dns_keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, - const char *directory, isc_stdtime_t now, isc_stdtime_t when, - bool dspublish); + isc_stdtime_t now, isc_stdtime_t when, bool dspublish); isc_result_t dns_keymgr_checkds_id(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, - const char *directory, isc_stdtime_t now, - isc_stdtime_t when, bool dspublish, dns_keytag_t id, - unsigned int algorithm); + isc_stdtime_t now, isc_stdtime_t when, bool dspublish, + dns_keytag_t id, unsigned int algorithm); /*%< * Check DS for one key in 'keyring'. The key must have the KSK role. * If 'dspublish' is set to true, set the DS Publish time to 'now'. @@ -82,8 +80,7 @@ dns_keymgr_checkds_id(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, isc_result_t dns_keymgr_rollover(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, - const char *directory, isc_stdtime_t now, - isc_stdtime_t when, dns_keytag_t id, + isc_stdtime_t now, isc_stdtime_t when, dns_keytag_t id, unsigned int algorithm); /*%< * Rollover key with given 'id'. If the 'algorithm' is non-zero, it must diff --git a/lib/dns/include/dns/keystore.h b/lib/dns/include/dns/keystore.h new file mode 100644 index 0000000000..8888eb12d4 --- /dev/null +++ b/lib/dns/include/dns/keystore.h @@ -0,0 +1,220 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +#pragma once + +/***** +***** Module Info +*****/ + +/*! \file dns/keystore.h + * \brief + * DNSSEC Key Store + * + * A key store defines where to store DNSSEC keys. + */ + +/* Add -DDNS_KEYSTORE_TRACE=1 to CFLAGS for detailed reference tracing */ + +#include +#include +#include +#include + +#include + +#include + +ISC_LANG_BEGINDECLS + +/* Key store */ +struct dns_keystore { + unsigned int magic; + isc_mem_t *mctx; + const char *name; + const char *engine; + + /* Internals. */ + isc_mutex_t lock; + + /* Locked by themselves. */ + isc_refcount_t references; + + /* Under owner's locking control. */ + ISC_LINK(struct dns_keystore) link; + + /* Configuration values */ + char *directory; + char *pkcs11uri; +}; + +#define DNS_KEYSTORE_MAGIC ISC_MAGIC('K', 'E', 'Y', 'S') +#define DNS_KEYSTORE_VALID(ks) ISC_MAGIC_VALID(ks, DNS_KEYSTORE_MAGIC) + +#define DNS_KEYSTORE_KEYDIRECTORY "key-directory" + +isc_result_t +dns_keystore_create(isc_mem_t *mctx, const char *name, const char *engine, + dns_keystore_t **kspp); +/*%< + * Create a key store. + * + * Requires: + * + *\li 'mctx' is a valid memory context. + * + *\li 'name' is a valid C string. + * + *\li 'engine' is the name of the OpenSSL engine to use, may be NULL. + * + *\li kspp != NULL && *kspp == NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + +const char * +dns_keystore_name(dns_keystore_t *keystore); +/*%< + * Get keystore name. + * + * Requires: + * + *\li 'keystore' is a valid keystore. + * + * Returns: + * + *\li name of 'keystore'. + */ + +const char * +dns_keystore_engine(dns_keystore_t *keystore); +/*%< + * Get keystore engine. + * + * Requires: + * + *\li 'keystore' is a valid keystore. + * + * Returns: + * + *\li engine of 'keystore'. May be NULL. + */ + +const char * +dns_keystore_directory(dns_keystore_t *keystore, const char *keydir); +/*%< + * Get keystore directory. If 'keystore' is NULL or 'keystore->directory' is + *NULL, return 'keydir'. + * + * Returns: + * + *\li directory of 'keystore'. + */ + +void +dns_keystore_setdirectory(dns_keystore_t *keystore, const char *dir); +/*%< + * Set keystore directory. + * + * Requires: + * + *\li 'keystore' is a valid keystore. + * + */ + +const char * +dns_keystore_pkcs11uri(dns_keystore_t *keystore); +/*%< + * Get keystore PKCS#11 URI. + * + * Requires: + * + *\li 'keystore' is a valid keystore. + * + * Returns: + * + *\li PKCS#11 URI of 'keystore'. + */ + +void +dns_keystore_setpkcs11uri(dns_keystore_t *keystore, const char *uri); +/*%< + * Set keystore PKCS#11 URI. + * + * Requires: + * + *\li 'keystore' is a valid keystore. + * + */ + +isc_result_t +dns_keystore_keygen(dns_keystore_t *keystore, const dns_name_t *origin, + const char *policy, dns_rdataclass_t rdclass, + isc_mem_t *mctx, uint32_t alg, int size, int flags, + dst_key_t **dstkey); +/*%< + * Create a DNSSEC key pair. Set keystore PKCS#11 URI. + * + * Requires: + * + *\li 'keystore' is a valid keystore. + * + *\li 'origin' is a valid DNS owner name. + * + *\li 'policy' is the name of the DNSSEC policy. + * + *\li 'mctx' is a valid memory context. + * + *\li 'dstkey' is not NULL and '*dstkey' is NULL. + * + */ + +isc_result_t +dns_keystorelist_find(dns_keystorelist_t *list, const char *name, + dns_keystore_t **kspp); +/*%< + * Search for a keystore with name 'name' in 'list'. + * If found, '*kspp' is (strongly) attached to it. + * + * Requires: + * + *\li 'kspp' points to a NULL dns_keystore_t *. + * + * Returns: + * + *\li #ISC_R_SUCCESS A matching keystore was found. + *\li #ISC_R_NOTFOUND No matching keystore was found. + */ + +#ifdef DNS_KEYSTORE_TRACE +/* Compatibility macros */ +#define dns_keystore_attach(ks, ksp) \ + dns_keystore__attach(ks, ksp, __func__, __FILE__, __LINE__) +#define dns_keystore_detach(ksp) \ + dns_keystore__detach(ksp, __func__, __FILE__, __LINE__) +#define dns_keystore_ref(ptr) \ + dns_keystore__ref(ptr, __func__, __FILE__, __LINE__) +#define dns_keystore_unref(ptr) \ + dns_keystore__unref(ptr, __func__, __FILE__, __LINE__) + +ISC_REFCOUNT_TRACE_DECL(dns_keystore); +#else +ISC_REFCOUNT_DECL(dns_keystore); +#endif /* DNS_KEYSTORE_TRACE */ + +ISC_LANG_ENDDECLS diff --git a/lib/dns/include/dns/types.h b/lib/dns/include/dns/types.h index 89e6b15f78..462e36f2af 100644 --- a/lib/dns/include/dns/types.h +++ b/lib/dns/include/dns/types.h @@ -106,8 +106,10 @@ typedef struct dns_kasp_nsec3param dns_kasp_nsec3param_t; typedef uint16_t dns_keyflags_t; typedef struct dns_keynode dns_keynode_t; typedef ISC_LIST(dns_keynode_t) dns_keynodelist_t; -typedef struct dns_keytable dns_keytable_t; -typedef uint16_t dns_keytag_t; +typedef struct dns_keytable dns_keytable_t; +typedef uint16_t dns_keytag_t; +typedef struct dns_keystore dns_keystore_t; +typedef ISC_LIST(dns_keystore_t) dns_keystorelist_t; typedef struct dns_loadctx dns_loadctx_t; typedef struct dns_loadmgr dns_loadmgr_t; typedef struct dns_masterrawheader dns_masterrawheader_t; diff --git a/lib/dns/include/dns/zone.h b/lib/dns/include/dns/zone.h index de48246d1d..d1e589f445 100644 --- a/lib/dns/include/dns/zone.h +++ b/lib/dns/include/dns/zone.h @@ -1595,7 +1595,7 @@ isc_result_t dns_zone_setkeydirectory(dns_zone_t *zone, const char *directory); /*%< * Sets the name of the directory where private keys used for - * online signing of dynamic zones are found. + * online signing or dynamic zones are found. * * Require: *\li 'zone' to be a valid zone. @@ -1618,10 +1618,33 @@ dns_zone_getkeydirectory(dns_zone_t *zone); * Pointer to null-terminated file name, or NULL. */ +void +dns_zone_setkeystores(dns_zone_t *zone, dns_keystorelist_t *keystores); +/*%< + * Sets the keystore list where private keys used for + * online signing or dynamic zones are found. + * + * Require: + *\li 'zone' to be a valid zone. + */ + +dns_keystorelist_t * +dns_zone_getkeystores(dns_zone_t *zone); +/*%< + * Gets the keystore list where private keys used for + * online signing or dynamic zones are found. + * + * Require: + *\li 'zone' to be a valid zone. + * + * Returns: + * Pointer to the keystore list, or NULL. + */ + isc_result_t dns_zone_getdnsseckeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, isc_stdtime_t now, dns_dnsseckeylist_t *keys); -/*% +/*%< * Find DNSSEC keys used for signing with dnssec-policy. Load these keys * into 'keys'. * @@ -1634,6 +1657,26 @@ dns_zone_getdnsseckeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, *\li Error */ +isc_result_t +dns_zone_findkeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + isc_stdtime_t now, isc_mem_t *mctx, unsigned int maxkeys, + dst_key_t **keys, unsigned int *nkeys); +/*%< + * Finds a set of zone keys. Searches in the applicable key stores for the + * given 'zone' if there is a dnssec-policy attached, otherwise it looks up + * the keys in the zone's key-directory. The found keys are loaded into 'keys'. + * + * Requires: + *\li 'zone' to be a valid initialised zone. + *\li 'mctx' is not NULL. + *\li 'keys' is not NULL and has enough space form 'nkeys' keys. + *\li 'nkeys' is not NULL. + * + * Returns: + *\li #ISC_R_SUCCESS + *\li Error + */ + void dns_zonemgr_create(isc_mem_t *mctx, isc_loopmgr_t *loopmgr, isc_nm_t *netmgr, dns_zonemgr_t **zmgrp); diff --git a/lib/dns/include/dst/dst.h b/lib/dns/include/dst/dst.h index c0912f3e67..3da59150ad 100644 --- a/lib/dns/include/dst/dst.h +++ b/lib/dns/include/dst/dst.h @@ -629,8 +629,8 @@ dst_key_fromlabel(const dns_name_t *name, int alg, unsigned int flags, isc_result_t dst_key_generate(const dns_name_t *name, unsigned int alg, unsigned int bits, unsigned int param, unsigned int flags, unsigned int protocol, - dns_rdataclass_t rdclass, isc_mem_t *mctx, dst_key_t **keyp, - void (*callback)(int)); + dns_rdataclass_t rdclass, const char *label, isc_mem_t *mctx, + dst_key_t **keyp, void (*callback)(int)); /*%< * Generate a DST key (or keypair) with the supplied parameters. The @@ -765,6 +765,9 @@ dst_key_rid(const dst_key_t *key); dns_rdataclass_t dst_key_class(const dst_key_t *key); +const char * +dst_key_directory(const dst_key_t *key); + bool dst_key_isprivate(const dst_key_t *key); @@ -1233,6 +1236,15 @@ dst_key_copy_metadata(dst_key_t *to, dst_key_t *from); * 'to' and 'from' to be valid. */ +void +dst_key_setdirectory(dst_key_t *key, const char *dir); +/*%< + * Set the directory where to store key files for this key. + * + * Requires: + * 'key' to be valid. + */ + const char * dst_hmac_algorithm_totext(dst_algorithm_t alg); /*$< diff --git a/lib/dns/kasp.c b/lib/dns/kasp.c index 639811bf4e..8658fd629c 100644 --- a/lib/dns/kasp.c +++ b/lib/dns/kasp.c @@ -385,21 +385,20 @@ dns_kasp_addkey(dns_kasp_t *kasp, dns_kasp_key_t *key) { isc_result_t dns_kasp_key_create(dns_kasp_t *kasp, dns_kasp_key_t **keyp) { - dns_kasp_key_t *key; + dns_kasp_key_t *key = NULL; + dns_kasp_key_t k = { .length = -1 }; REQUIRE(DNS_KASP_VALID(kasp)); REQUIRE(keyp != NULL && *keyp == NULL); key = isc_mem_get(kasp->mctx, sizeof(*key)); + *key = k; + key->mctx = NULL; isc_mem_attach(kasp->mctx, &key->mctx); ISC_LINK_INIT(key, link); - key->lifetime = 0; - key->algorithm = 0; - key->length = -1; - key->role = 0; *keyp = key; return (ISC_R_SUCCESS); } @@ -408,6 +407,9 @@ void dns_kasp_key_destroy(dns_kasp_key_t *key) { REQUIRE(key != NULL); + if (key->keystore != NULL) { + dns_keystore_detach(&key->keystore); + } isc_mem_putanddetach(&key->mctx, key, sizeof(*key)); } @@ -469,6 +471,13 @@ dns_kasp_key_lifetime(dns_kasp_key_t *key) { return (key->lifetime); } +dns_keystore_t * +dns_kasp_key_keystore(dns_kasp_key_t *key) { + REQUIRE(key != NULL); + + return (key->keystore); +} + bool dns_kasp_key_ksk(dns_kasp_key_t *key) { REQUIRE(key != NULL); diff --git a/lib/dns/key.c b/lib/dns/key.c index 2449e8aac1..a0cde831a5 100644 --- a/lib/dns/key.c +++ b/lib/dns/key.c @@ -16,6 +16,7 @@ #include #include +#include #include #include @@ -123,6 +124,12 @@ dst_key_class(const dst_key_t *key) { return (key->key_class); } +const char * +dst_key_directory(const dst_key_t *key) { + REQUIRE(VALID_KEY(key)); + return (key->directory); +} + bool dst_key_iszonekey(const dst_key_t *key) { REQUIRE(VALID_KEY(key)); @@ -247,4 +254,12 @@ dst_key_getttl(const dst_key_t *key) { return (key->key_ttl); } +void +dst_key_setdirectory(dst_key_t *key, const char *dir) { + if (key->directory != NULL) { + isc_mem_free(key->mctx, key->directory); + } + key->directory = isc_mem_strdup(key->mctx, dir); +} + /*! \file */ diff --git a/lib/dns/keymgr.c b/lib/dns/keymgr.c index 56672a1198..cc59e42c0b 100644 --- a/lib/dns/keymgr.c +++ b/lib/dns/keymgr.c @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -443,24 +444,32 @@ keymgr_keyid_conflict(dst_key_t *newkey, dns_dnsseckeylist_t *keys) { */ static isc_result_t keymgr_createkey(dns_kasp_key_t *kkey, const dns_name_t *origin, - dns_rdataclass_t rdclass, isc_mem_t *mctx, - dns_dnsseckeylist_t *keylist, dns_dnsseckeylist_t *newkeys, - dst_key_t **dst_key) { - bool conflict = false; - int keyflags = DNS_KEYOWNER_ZONE; + dns_kasp_t *kasp, dns_rdataclass_t rdclass, isc_mem_t *mctx, + const char *keydir, dns_dnsseckeylist_t *keylist, + dns_dnsseckeylist_t *newkeys, dst_key_t **dst_key) { isc_result_t result = ISC_R_SUCCESS; + bool conflict = false; + int flags = DNS_KEYOWNER_ZONE; dst_key_t *newkey = NULL; + uint32_t alg = dns_kasp_key_algorithm(kkey); + dns_keystore_t *keystore = dns_kasp_key_keystore(kkey); + const char *dir = NULL; + int size = dns_kasp_key_size(kkey); + + if (dns_kasp_key_ksk(kkey)) { + flags |= DNS_KEYFLAG_KSK; + } do { - uint32_t algo = dns_kasp_key_algorithm(kkey); - int size = dns_kasp_key_size(kkey); - - if (dns_kasp_key_ksk(kkey)) { - keyflags |= DNS_KEYFLAG_KSK; + if (keystore == NULL) { + RETERR(dst_key_generate(origin, alg, size, 0, flags, + DNS_KEYPROTO_DNSSEC, rdclass, + NULL, mctx, &newkey, NULL)); + } else { + RETERR(dns_keystore_keygen( + keystore, origin, dns_kasp_getname(kasp), + rdclass, mctx, alg, size, flags, &newkey)); } - RETERR(dst_key_generate(origin, algo, size, 0, keyflags, - DNS_KEYPROTO_DNSSEC, rdclass, mctx, - &newkey, NULL)); /* Key collision? */ conflict = keymgr_keyid_conflict(newkey, keylist); @@ -481,6 +490,11 @@ keymgr_createkey(dns_kasp_key_t *kkey, const dns_name_t *origin, dst_key_setnum(newkey, DST_NUM_LIFETIME, dns_kasp_key_lifetime(kkey)); dst_key_setbool(newkey, DST_BOOL_KSK, dns_kasp_key_ksk(kkey)); dst_key_setbool(newkey, DST_BOOL_ZSK, dns_kasp_key_zsk(kkey)); + + dir = dns_keystore_directory(keystore, keydir); + if (dir != NULL) { + dst_key_setdirectory(newkey, dir); + } *dst_key = newkey; return (ISC_R_SUCCESS); @@ -1671,8 +1685,8 @@ static isc_result_t keymgr_key_rollover(dns_kasp_key_t *kaspkey, dns_dnsseckey_t *active_key, dns_dnsseckeylist_t *keyring, dns_dnsseckeylist_t *newkeys, const dns_name_t *origin, dns_rdataclass_t rdclass, - dns_kasp_t *kasp, uint32_t lifetime, bool rollover, - isc_stdtime_t now, isc_stdtime_t *nexttime, + dns_kasp_t *kasp, const char *keydir, uint32_t lifetime, + bool rollover, isc_stdtime_t now, isc_stdtime_t *nexttime, isc_mem_t *mctx) { char keystr[DST_KEY_FORMATSIZE]; isc_stdtime_t retire = 0, active = 0, prepub = 0; @@ -1790,9 +1804,9 @@ keymgr_key_rollover(dns_kasp_key_t *kaspkey, dns_dnsseckey_t *active_key, bool csk = (dns_kasp_key_ksk(kaspkey) && dns_kasp_key_zsk(kaspkey)); - isc_result_t result = keymgr_createkey(kaspkey, origin, rdclass, - mctx, keyring, newkeys, - &dst_key); + isc_result_t result = + keymgr_createkey(kaspkey, origin, kasp, rdclass, mctx, + keydir, keyring, newkeys, &dst_key); if (result != ISC_R_SUCCESS) { return (result); } @@ -1936,7 +1950,7 @@ keymgr_key_may_be_purged(dst_key_t *key, uint32_t after, isc_stdtime_t now) { } static void -keymgr_purge_keyfile(dst_key_t *key, const char *dir, int type) { +keymgr_purge_keyfile(dst_key_t *key, int type) { isc_result_t ret; isc_buffer_t fileb; char filename[NAME_MAX]; @@ -1945,7 +1959,7 @@ keymgr_purge_keyfile(dst_key_t *key, const char *dir, int type) { * Make the filename. */ isc_buffer_init(&fileb, filename, sizeof(filename)); - ret = dst_key_buildfilename(key, type, dir, &fileb); + ret = dst_key_buildfilename(key, type, dst_key_directory(key), &fileb); if (ret != ISC_R_SUCCESS) { char keystr[DST_KEY_FORMATSIZE]; dst_key_format(key, keystr, sizeof(keystr)); @@ -1975,33 +1989,25 @@ keymgr_purge_keyfile(dst_key_t *key, const char *dir, int type) { */ isc_result_t dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, - const char *directory, isc_mem_t *mctx, - dns_dnsseckeylist_t *keyring, dns_dnsseckeylist_t *dnskeys, + isc_mem_t *mctx, dns_dnsseckeylist_t *keyring, + dns_dnsseckeylist_t *dnskeys, const char *keydir, dns_kasp_t *kasp, isc_stdtime_t now, isc_stdtime_t *nexttime) { isc_result_t result = ISC_R_SUCCESS; dns_dnsseckeylist_t newkeys; dns_kasp_key_t *kkey; dns_dnsseckey_t *newkey = NULL; - isc_dir_t dir; - bool dir_open = false; bool secure_to_insecure = false; int numkeys = 0; int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE); char keystr[DST_KEY_FORMATSIZE]; - REQUIRE(DNS_KASP_VALID(kasp)); + REQUIRE(dns_name_isvalid(origin)); + REQUIRE(mctx != NULL); REQUIRE(keyring != NULL); + REQUIRE(DNS_KASP_VALID(kasp)); ISC_LIST_INIT(newkeys); - isc_dir_init(&dir); - if (directory == NULL) { - directory = "."; - } - - RETERR(isc_dir_open(&dir, directory)); - dir_open = true; - *nexttime = 0; /* Debug logging: what keys are available in the keyring? */ @@ -2076,13 +2082,9 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, keystr, keymgr_keyrole(dkey->key), dns_kasp_getname(kasp)); - keymgr_purge_keyfile(dkey->key, directory, - DST_TYPE_PUBLIC); - keymgr_purge_keyfile(dkey->key, directory, - DST_TYPE_PRIVATE); - keymgr_purge_keyfile(dkey->key, directory, - DST_TYPE_STATE); - + keymgr_purge_keyfile(dkey->key, DST_TYPE_PUBLIC); + keymgr_purge_keyfile(dkey->key, DST_TYPE_PRIVATE); + keymgr_purge_keyfile(dkey->key, DST_TYPE_STATE); dkey->purge = true; } } @@ -2191,9 +2193,10 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, } /* See if this key requires a rollover. */ - RETERR(keymgr_key_rollover( - kkey, active_key, keyring, &newkeys, origin, rdclass, - kasp, lifetime, rollover_allowed, now, nexttime, mctx)); + RETERR(keymgr_key_rollover(kkey, active_key, keyring, &newkeys, + origin, rdclass, kasp, keydir, + lifetime, rollover_allowed, now, + nexttime, mctx)); } /* Walked all kasp key configurations. Append new keys. */ @@ -2220,8 +2223,25 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, modified = true; } if (modified && !dkey->purge) { + const char *directory = dst_key_directory(dkey->key); + if (directory == NULL) { + directory = "."; + } + dns_dnssec_get_hints(dkey, now); RETERR(dst_key_tofile(dkey->key, options, directory)); + dst_key_setmodified(dkey->key, false); + + if (!isc_log_wouldlog(dns_lctx, ISC_LOG_DEBUG(3))) { + continue; + } + dst_key_format(dkey->key, keystr, sizeof(keystr)); + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_DEBUG(3), + "keymgr: DNSKEY %s (%s) " + "saved to directory %s, policy %s", + keystr, keymgr_keyrole(dkey->key), + directory, dns_kasp_getname(kasp)); } dst_key_setmodified(dkey->key, false); } @@ -2229,10 +2249,6 @@ dns_keymgr_run(const dns_name_t *origin, dns_rdataclass_t rdclass, result = ISC_R_SUCCESS; failure: - if (dir_open) { - isc_dir_close(&dir); - } - if (result != ISC_R_SUCCESS) { while ((newkey = ISC_LIST_HEAD(newkeys)) != NULL) { ISC_LIST_UNLINK(newkeys, newkey, link); @@ -2254,11 +2270,10 @@ failure: static isc_result_t keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, - const char *directory, isc_stdtime_t now, isc_stdtime_t when, - bool dspublish, dns_keytag_t id, unsigned int alg, - bool check_id) { + isc_stdtime_t now, isc_stdtime_t when, bool dspublish, + dns_keytag_t id, unsigned int alg, bool check_id) { int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE); - isc_dir_t dir; + const char *directory = NULL; isc_result_t result; dns_dnsseckey_t *ksk_key = NULL; @@ -2325,40 +2340,33 @@ keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, } /* Store key state and update hints. */ - isc_dir_init(&dir); + directory = dst_key_directory(ksk_key->key); if (directory == NULL) { directory = "."; } - result = isc_dir_open(&dir, directory); - if (result != ISC_R_SUCCESS) { - return (result); - } dns_dnssec_get_hints(ksk_key, now); result = dst_key_tofile(ksk_key->key, options, directory); if (result == ISC_R_SUCCESS) { dst_key_setmodified(ksk_key->key, false); } - isc_dir_close(&dir); return (result); } isc_result_t dns_keymgr_checkds(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, - const char *directory, isc_stdtime_t now, isc_stdtime_t when, - bool dspublish) { - return (keymgr_checkds(kasp, keyring, directory, now, when, dspublish, - 0, 0, false)); + isc_stdtime_t now, isc_stdtime_t when, bool dspublish) { + return (keymgr_checkds(kasp, keyring, now, when, dspublish, 0, 0, + false)); } isc_result_t dns_keymgr_checkds_id(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, - const char *directory, isc_stdtime_t now, - isc_stdtime_t when, bool dspublish, dns_keytag_t id, - unsigned int alg) { - return (keymgr_checkds(kasp, keyring, directory, now, when, dspublish, - id, alg, true)); + isc_stdtime_t now, isc_stdtime_t when, bool dspublish, + dns_keytag_t id, unsigned int alg) { + return (keymgr_checkds(kasp, keyring, now, when, dspublish, id, alg, + true)); } static void @@ -2565,11 +2573,10 @@ dns_keymgr_status(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, isc_result_t dns_keymgr_rollover(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, - const char *directory, isc_stdtime_t now, - isc_stdtime_t when, dns_keytag_t id, + isc_stdtime_t now, isc_stdtime_t when, dns_keytag_t id, unsigned int algorithm) { int options = (DST_TYPE_PRIVATE | DST_TYPE_PUBLIC | DST_TYPE_STATE); - isc_dir_t dir; + const char *directory = NULL; isc_result_t result; dns_dnsseckey_t *key = NULL; isc_stdtime_t active, retire, prepub; @@ -2628,21 +2635,16 @@ dns_keymgr_rollover(dns_kasp_t *kasp, dns_dnsseckeylist_t *keyring, dst_key_setnum(key->key, DST_NUM_LIFETIME, (retire - active)); /* Store key state and update hints. */ - isc_dir_init(&dir); + directory = dst_key_directory(key->key); if (directory == NULL) { directory = "."; } - result = isc_dir_open(&dir, directory); - if (result != ISC_R_SUCCESS) { - return (result); - } dns_dnssec_get_hints(key, now); result = dst_key_tofile(key->key, options, directory); if (result == ISC_R_SUCCESS) { dst_key_setmodified(key->key, false); } - isc_dir_close(&dir); return (result); } diff --git a/lib/dns/keystore.c b/lib/dns/keystore.c new file mode 100644 index 0000000000..90b766d62e --- /dev/null +++ b/lib/dns/keystore.c @@ -0,0 +1,291 @@ +/* + * Copyright (C) Internet Systems Consortium, Inc. ("ISC") + * + * SPDX-License-Identifier: MPL-2.0 + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, you can obtain one at https://mozilla.org/MPL/2.0/. + * + * See the COPYRIGHT file distributed with this work for additional + * information regarding copyright ownership. + */ + +/*! \file */ + +#include + +#include +#include +#include +#include +#include + +#include +#include +#include + +isc_result_t +dns_keystore_create(isc_mem_t *mctx, const char *name, const char *engine, + dns_keystore_t **kspp) { + dns_keystore_t *keystore; + + REQUIRE(name != NULL); + REQUIRE(kspp != NULL && *kspp == NULL); + + keystore = isc_mem_get(mctx, sizeof(*keystore)); + keystore->engine = engine; + keystore->mctx = NULL; + isc_mem_attach(mctx, &keystore->mctx); + + keystore->name = isc_mem_strdup(mctx, name); + isc_mutex_init(&keystore->lock); + + isc_refcount_init(&keystore->references, 1); + + ISC_LINK_INIT(keystore, link); + + keystore->directory = NULL; + keystore->pkcs11uri = NULL; + + keystore->magic = DNS_KEYSTORE_MAGIC; + *kspp = keystore; + + return (ISC_R_SUCCESS); +} + +static inline void +dns__keystore_destroy(dns_keystore_t *keystore) { + char *name; + + REQUIRE(!ISC_LINK_LINKED(keystore, link)); + + isc_mutex_destroy(&keystore->lock); + name = UNCONST(keystore->name); + isc_mem_free(keystore->mctx, name); + if (keystore->directory != NULL) { + isc_mem_free(keystore->mctx, keystore->directory); + } + if (keystore->pkcs11uri != NULL) { + isc_mem_free(keystore->mctx, keystore->pkcs11uri); + } + isc_mem_putanddetach(&keystore->mctx, keystore, sizeof(*keystore)); +} + +#ifdef DNS_KEYSTORE_TRACE +ISC_REFCOUNT_TRACE_IMPL(dns_keystore, dns__keystore_destroy); +#else +ISC_REFCOUNT_IMPL(dns_keystore, dns__keystore_destroy); +#endif + +const char * +dns_keystore_name(dns_keystore_t *keystore) { + REQUIRE(DNS_KEYSTORE_VALID(keystore)); + + return (keystore->name); +} + +const char * +dns_keystore_engine(dns_keystore_t *keystore) { + REQUIRE(DNS_KEYSTORE_VALID(keystore)); + + return (keystore->engine); +} + +const char * +dns_keystore_directory(dns_keystore_t *keystore, const char *keydir) { + if (keystore == NULL) { + return (keydir); + } + + INSIST(DNS_KEYSTORE_VALID(keystore)); + + if (keystore->directory == NULL) { + return (keydir); + } + + return (keystore->directory); +} + +void +dns_keystore_setdirectory(dns_keystore_t *keystore, const char *dir) { + REQUIRE(DNS_KEYSTORE_VALID(keystore)); + + if (keystore->directory != NULL) { + isc_mem_free(keystore->mctx, keystore->directory); + } + keystore->directory = (dir == NULL) + ? NULL + : isc_mem_strdup(keystore->mctx, dir); +} + +const char * +dns_keystore_pkcs11uri(dns_keystore_t *keystore) { + REQUIRE(DNS_KEYSTORE_VALID(keystore)); + + return (keystore->pkcs11uri); +} + +void +dns_keystore_setpkcs11uri(dns_keystore_t *keystore, const char *uri) { + REQUIRE(DNS_KEYSTORE_VALID(keystore)); + + if (keystore->pkcs11uri != NULL) { + isc_mem_free(keystore->mctx, keystore->pkcs11uri); + } + keystore->pkcs11uri = (uri == NULL) + ? NULL + : isc_mem_strdup(keystore->mctx, uri); +} + +static isc_result_t +buildpkcs11label(const char *uri, const dns_name_t *zname, const char *policy, + int flags, isc_buffer_t *buf) { + bool ksk = ((flags & DNS_KEYFLAG_KSK) != 0); + char timebuf[18]; + isc_time_t now = isc_time_now(); + isc_result_t result; + dns_fixedname_t fname; + dns_name_t *pname = dns_fixedname_initname(&fname); + + /* uri + object */ + if (isc_buffer_availablelength(buf) < strlen(uri) + strlen(";object=")) + { + return (ISC_R_NOSPACE); + } + isc_buffer_putstr(buf, uri); + isc_buffer_putstr(buf, ";object="); + /* zone name */ + result = dns_name_tofilenametext(zname, false, buf); + if (result != ISC_R_SUCCESS) { + return (result); + } + /* + * policy name + * + * Note that strlen(policy) is not the actual length, but if this + * already does not fit, the escaped version returned from + * dns_name_tofilenametext() certainly won't fit. + */ + if (isc_buffer_availablelength(buf) < (strlen(policy) + 1)) { + return (ISC_R_NOSPACE); + } + isc_buffer_putstr(buf, "-"); + result = dns_name_fromstring(pname, policy, dns_rootname, 0, NULL); + if (result != ISC_R_SUCCESS) { + return (result); + } + result = dns_name_tofilenametext(pname, false, buf); + if (result != ISC_R_SUCCESS) { + return (result); + } + /* key type + current time */ + isc_time_formatshorttimestamp(&now, timebuf, sizeof(timebuf)); + return isc_buffer_printf(buf, "-%s-%s", ksk ? "ksk" : "zsk", timebuf); +} + +isc_result_t +dns_keystore_keygen(dns_keystore_t *keystore, const dns_name_t *origin, + const char *policy, dns_rdataclass_t rdclass, + isc_mem_t *mctx, uint32_t alg, int size, int flags, + dst_key_t **dstkey) { + isc_result_t result; + dst_key_t *newkey = NULL; + const char *uri = NULL; + + REQUIRE(DNS_KEYSTORE_VALID(keystore)); + REQUIRE(dns_name_isvalid(origin)); + REQUIRE(policy != NULL); + REQUIRE(mctx != NULL); + REQUIRE(dstkey != NULL && *dstkey == NULL); + + uri = dns_keystore_pkcs11uri(keystore); + if (uri != NULL) { + /* + * Create the PKCS#11 label. + * The label consists of the configured URI, and the object + * parameter. The object parameter needs to be unique. We + * know that for a given point in time, there will be at most + * one key per type created for each zone in a given DNSSEC + * policy. Hence the object is constructed out of the following + * parts: the zone name, policy name, key type, and the + * current time. + * + * The object may not contain any characters that conflict with + * special characters in the PKCS#11 URI scheme syntax (see + * RFC 7512, Section 2.3). Therefore, we mangle the zone name + * and policy name through 'dns_name_tofilenametext()'. We + * could create a new function to convert a name to PKCS#11 + * text, but this existing function will suffice. + */ + char label[NAME_MAX]; + isc_buffer_t buf; + isc_buffer_init(&buf, label, sizeof(label)); + result = buildpkcs11label(uri, origin, policy, flags, &buf); + if (result != ISC_R_SUCCESS) { + char namebuf[DNS_NAME_FORMATSIZE]; + dns_name_format(origin, namebuf, sizeof(namebuf)); + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_ERROR, + "keystore: failed to create PKCS#11 object " + "for zone %s, policy %s: %s", + namebuf, policy, isc_result_totext(result)); + return (result); + } + + /* Generate the key */ + result = dst_key_generate(origin, alg, size, 0, flags, + DNS_KEYPROTO_DNSSEC, rdclass, label, + mctx, &newkey, NULL); + + if (result != ISC_R_SUCCESS) { + isc_log_write( + dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_ERROR, + "keystore: failed to generate PKCS#11 object " + "%s: %s", + label, isc_result_totext(result)); + return (result); + } + isc_log_write(dns_lctx, DNS_LOGCATEGORY_DNSSEC, + DNS_LOGMODULE_DNSSEC, ISC_LOG_ERROR, + "keystore: generated PKCS#11 object %s", label); + } else { + result = dst_key_generate(origin, alg, size, 0, flags, + DNS_KEYPROTO_DNSSEC, rdclass, NULL, + mctx, &newkey, NULL); + } + + if (result == ISC_R_SUCCESS) { + *dstkey = newkey; + } + return (result); +} + +isc_result_t +dns_keystorelist_find(dns_keystorelist_t *list, const char *name, + dns_keystore_t **kspp) { + dns_keystore_t *keystore = NULL; + + REQUIRE(kspp != NULL && *kspp == NULL); + + if (list == NULL) { + return (ISC_R_NOTFOUND); + } + + for (keystore = ISC_LIST_HEAD(*list); keystore != NULL; + keystore = ISC_LIST_NEXT(keystore, link)) + { + if (strcmp(keystore->name, name) == 0) { + break; + } + } + + if (keystore == NULL) { + return (ISC_R_NOTFOUND); + } + + dns_keystore_attach(keystore, kspp); + return (ISC_R_SUCCESS); +} diff --git a/lib/dns/opensslecdsa_link.c b/lib/dns/opensslecdsa_link.c index b54e43ade3..2c99650285 100644 --- a/lib/dns/opensslecdsa_link.c +++ b/lib/dns/opensslecdsa_link.c @@ -410,13 +410,78 @@ opensslecdsa_create_pkey(unsigned int key_alg, bool private, #if OPENSSL_VERSION_NUMBER >= 0x30000000L static isc_result_t -opensslecdsa_generate_pkey(unsigned int key_alg, EVP_PKEY **retkey) { +opensslecdsa_generate_pkey_with_uri(int group_nid, const char *label, + EVP_PKEY **retkey) { + int status; + isc_result_t ret; + char *uri = UNCONST(label); + EVP_PKEY_CTX *ctx = NULL; + OSSL_PARAM params[3]; + + /* Generate the key's parameters. */ + params[0] = OSSL_PARAM_construct_utf8_string("pkcs11_uri", uri, 0); + params[1] = OSSL_PARAM_construct_utf8_string( + "pkcs11_key_usage", (char *)"digitalSignature", 0); + params[2] = OSSL_PARAM_construct_end(); + + ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", "provider=pkcs11"); + if (ctx == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_new_from_name", + DST_R_OPENSSLFAILURE)); + } + + status = EVP_PKEY_keygen_init(ctx); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen_init", + DST_R_OPENSSLFAILURE)); + } + + status = EVP_PKEY_CTX_set_params(ctx, params); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_set_params", + DST_R_OPENSSLFAILURE)); + } + /* + * Setting the P-384 curve doesn't work correctly when using: + * OSSL_PARAM_construct_utf8_string("ec_paramgen_curve", "P-384", 0); + * + * Instead use the OpenSSL function to set the curve nid param. + */ + status = EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, group_nid); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_set_ec_paramgen_" + "curve_nid", + DST_R_OPENSSLFAILURE)); + } + + /* Generate the key. */ + status = EVP_PKEY_generate(ctx, retkey); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_generate", + DST_R_OPENSSLFAILURE)); + } + + ret = ISC_R_SUCCESS; + +err: + EVP_PKEY_CTX_free(ctx); + return (ret); +} + +static isc_result_t +opensslecdsa_generate_pkey(unsigned int key_alg, const char *label, + EVP_PKEY **retkey) { isc_result_t ret; EVP_PKEY_CTX *ctx = NULL; EVP_PKEY *params_pkey = NULL; int group_nid = opensslecdsa_key_alg_to_group_nid(key_alg); int status; + if (label != NULL) { + return (opensslecdsa_generate_pkey_with_uri(group_nid, label, + retkey)); + } + /* Generate the key's parameters. */ ctx = EVP_PKEY_CTX_new_from_name(NULL, "EC", NULL); if (ctx == NULL) { @@ -498,11 +563,16 @@ opensslecdsa_extract_private_key(const dst_key_t *key, unsigned char *buf, #else static isc_result_t -opensslecdsa_generate_pkey(unsigned int key_alg, EVP_PKEY **retkey) { +opensslecdsa_generate_pkey(unsigned int key_alg, const char *label, + EVP_PKEY **retkey) { isc_result_t ret; EC_KEY *eckey = NULL; EVP_PKEY *pkey = NULL; - int group_nid = opensslecdsa_key_alg_to_group_nid(key_alg); + int group_nid; + + UNUSED(label); + + group_nid = opensslecdsa_key_alg_to_group_nid(key_alg); eckey = EC_KEY_new_by_curve_name(group_nid); if (eckey == NULL) { @@ -815,7 +885,7 @@ opensslecdsa_generate(dst_key_t *key, int unused, void (*callback)(int)) { UNUSED(unused); UNUSED(callback); - ret = opensslecdsa_generate_pkey(key->key_alg, &pkey); + ret = opensslecdsa_generate_pkey(key->key_alg, key->label, &pkey); if (ret != ISC_R_SUCCESS) { return (ret); } diff --git a/lib/dns/opensslrsa_link.c b/lib/dns/opensslrsa_link.c index 4c264c183a..6e26f8651b 100644 --- a/lib/dns/opensslrsa_link.c +++ b/lib/dns/opensslrsa_link.c @@ -366,13 +366,17 @@ progress_cb(int p, int n, BN_GENCB *cb) { } static isc_result_t -opensslrsa_generate_pkey(unsigned int key_size, BIGNUM *e, +opensslrsa_generate_pkey(unsigned int key_size, const char *label, BIGNUM *e, void (*callback)(int), EVP_PKEY **retkey) { - RSA *rsa = RSA_new(); - EVP_PKEY *pkey = EVP_PKEY_new(); + RSA *rsa = NULL; + EVP_PKEY *pkey = NULL; BN_GENCB *cb = NULL; isc_result_t ret; + UNUSED(label); + + rsa = RSA_new(); + pkey = EVP_PKEY_new(); if (rsa == NULL || pkey == NULL) { DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); } @@ -493,11 +497,62 @@ progress_cb(EVP_PKEY_CTX *ctx) { } static isc_result_t -opensslrsa_generate_pkey(unsigned int key_size, BIGNUM *e, +opensslrsa_generate_pkey_with_uri(size_t key_size, const char *label, + EVP_PKEY **retkey) { + EVP_PKEY_CTX *ctx = NULL; + OSSL_PARAM params[4]; + char *uri = UNCONST(label); + isc_result_t ret; + int status; + + params[0] = OSSL_PARAM_construct_utf8_string("pkcs11_uri", uri, 0); + params[1] = OSSL_PARAM_construct_utf8_string( + "pkcs11_key_usage", (char *)"digitalSignature", 0); + params[2] = OSSL_PARAM_construct_size_t("rsa_keygen_bits", &key_size); + params[3] = OSSL_PARAM_construct_end(); + + ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", "provider=pkcs11"); + if (ctx == NULL) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_new_from_name", + DST_R_OPENSSLFAILURE)); + } + + status = EVP_PKEY_keygen_init(ctx); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_keygen_init", + DST_R_OPENSSLFAILURE)); + } + + status = EVP_PKEY_CTX_set_params(ctx, params); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_CTX_set_params", + DST_R_OPENSSLFAILURE)); + } + + status = EVP_PKEY_generate(ctx, retkey); + if (status != 1) { + DST_RET(dst__openssl_toresult2("EVP_PKEY_generate", + DST_R_OPENSSLFAILURE)); + } + + ret = ISC_R_SUCCESS; +err: + EVP_PKEY_CTX_free(ctx); + return (ret); +} + +static isc_result_t +opensslrsa_generate_pkey(unsigned int key_size, const char *label, BIGNUM *e, void (*callback)(int), EVP_PKEY **retkey) { - EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + EVP_PKEY_CTX *ctx; isc_result_t ret; + if (label != NULL) { + return (opensslrsa_generate_pkey_with_uri(key_size, label, + retkey)); + } + + ctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); if (ctx == NULL) { DST_RET(dst__openssl_toresult(DST_R_OPENSSLFAILURE)); } @@ -669,7 +724,8 @@ opensslrsa_generate(dst_key_t *key, int exp, void (*callback)(int)) { BN_set_bit(e, 32); } - ret = opensslrsa_generate_pkey(key->key_size, e, callback, &pkey); + ret = opensslrsa_generate_pkey(key->key_size, key->label, e, callback, + &pkey); if (ret != ISC_R_SUCCESS) { goto err; } diff --git a/lib/dns/update.c b/lib/dns/update.c index 6bef476fd5..cbcbe1c139 100644 --- a/lib/dns/update.c +++ b/lib/dns/update.c @@ -1056,13 +1056,20 @@ find_zone_keys(dns_zone_t *zone, isc_mem_t *mctx, unsigned int maxkeys, unsigned int count = 0; isc_result_t result; isc_stdtime_t now = isc_stdtime_now(); + dns_kasp_t *kasp; + dns_keystorelist_t *keystores; + const char *keydir; ISC_LIST_INIT(keylist); + kasp = dns_zone_getkasp(zone); + keydir = dns_zone_getkeydirectory(zone); + keystores = dns_zone_getkeystores(zone); + dns_zone_lock_keyfiles(zone); - result = dns_dnssec_findmatchingkeys(dns_zone_getorigin(zone), - dns_zone_getkeydirectory(zone), - now, mctx, &keylist); + result = dns_dnssec_findmatchingkeys(dns_zone_getorigin(zone), kasp, + keydir, keystores, now, mctx, + &keylist); dns_zone_unlock_keyfiles(zone); if (result != ISC_R_SUCCESS) { diff --git a/lib/dns/zone.c b/lib/dns/zone.c index 0e4be36948..f0360e4d44 100644 --- a/lib/dns/zone.c +++ b/lib/dns/zone.c @@ -218,6 +218,13 @@ typedef struct dns_include dns_include_t; #define ZONEDB_LOCK(l, t) RWLOCK((l), (t)) #define ZONEDB_UNLOCK(l, t) RWUNLOCK((l), (t)) +#define RETERR(x) \ + do { \ + result = (x); \ + if (result != ISC_R_SUCCESS) \ + goto failure; \ + } while (0) + #ifdef ENABLE_AFL extern bool dns_fuzzing_resolver; #endif /* ifdef ENABLE_AFL */ @@ -303,6 +310,7 @@ struct dns_zone { isc_stdtime_t log_key_expired_timer; char *keydirectory; dns_keyfileio_t *kfio; + dns_keystorelist_t *keystores; uint32_t maxrefresh; uint32_t minrefresh; @@ -6056,6 +6064,207 @@ was_dumping(dns_zone_t *zone) { return (false); } +static isc_result_t +keyfromfile(dns_zone_t *zone, dst_key_t *pubkey, isc_mem_t *mctx, + dst_key_t **key) { + const char *directory = zone->keydirectory; + dns_kasp_t *kasp = zone->kasp; + dst_key_t *foundkey = NULL; + isc_result_t result = ISC_R_NOTFOUND; + + if (kasp == NULL || (strcmp(dns_kasp_getname(kasp), "none") == 0) || + (strcmp(dns_kasp_getname(kasp), "insecure") == 0)) + { + result = dst_key_fromfile( + dst_key_name(pubkey), dst_key_id(pubkey), + dst_key_alg(pubkey), + (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | DST_TYPE_STATE), + directory, mctx, &foundkey); + } else { + for (dns_kasp_key_t *kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); + kkey != NULL; kkey = ISC_LIST_NEXT(kkey, link)) + { + dns_keystore_t *ks = dns_kasp_key_keystore(kkey); + directory = dns_keystore_directory(ks, + zone->keydirectory); + + result = dst_key_fromfile( + dst_key_name(pubkey), dst_key_id(pubkey), + dst_key_alg(pubkey), + (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | + DST_TYPE_STATE), + directory, mctx, &foundkey); + if (result == ISC_R_SUCCESS) { + break; + } + } + } + + *key = foundkey; + return (result); +} + +#define is_zone_key(key) \ + ((dst_key_flags(key) & DNS_KEYFLAG_OWNERMASK) == DNS_KEYOWNER_ZONE) + +static isc_result_t +findzonekeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + dns_dbnode_t *node, const dns_name_t *name, isc_stdtime_t now, + isc_mem_t *mctx, unsigned int maxkeys, dst_key_t **keys, + unsigned int *nkeys) { + dns_rdataset_t rdataset; + dns_rdata_t rdata = DNS_RDATA_INIT; + isc_result_t result; + dst_key_t *pubkey = NULL; + unsigned int count = 0; + + *nkeys = 0; + memset(keys, 0, sizeof(*keys) * maxkeys); + dns_rdataset_init(&rdataset); + RETERR(dns_db_findrdataset(db, node, ver, dns_rdatatype_dnskey, 0, 0, + &rdataset, NULL)); + RETERR(dns_rdataset_first(&rdataset)); + while (result == ISC_R_SUCCESS && count < maxkeys) { + pubkey = NULL; + dns_rdataset_current(&rdataset, &rdata); + RETERR(dns_dnssec_keyfromrdata(name, &rdata, mctx, &pubkey)); + dst_key_setttl(pubkey, rdataset.ttl); + + if (!is_zone_key(pubkey) || + (dst_key_flags(pubkey) & DNS_KEYTYPE_NOAUTH) != 0) + { + goto next; + } + /* Corrupted .key file? */ + if (!dns_name_equal(name, dst_key_name(pubkey))) { + goto next; + } + keys[count] = NULL; + result = keyfromfile(zone, pubkey, mctx, &keys[count]); + + /* + * If the key was revoked and the private file + * doesn't exist, maybe it was revoked internally + * by named. Try loading the unrevoked version. + */ + if (result == ISC_R_FILENOTFOUND) { + uint32_t flags; + flags = dst_key_flags(pubkey); + if ((flags & DNS_KEYFLAG_REVOKE) != 0) { + dst_key_setflags(pubkey, + flags & ~DNS_KEYFLAG_REVOKE); + result = keyfromfile(zone, pubkey, mctx, + &keys[count]); + if (result == ISC_R_SUCCESS && + dst_key_pubcompare(pubkey, keys[count], + false)) + { + dst_key_setflags(keys[count], flags); + } + dst_key_setflags(pubkey, flags); + } + } + + if (result != ISC_R_SUCCESS) { + char filename[DNS_NAME_FORMATSIZE + + DNS_SECALG_FORMATSIZE + + sizeof("key file for //65535")]; + isc_result_t result2; + isc_buffer_t buf; + + isc_buffer_init(&buf, filename, sizeof(filename)); + result2 = dst_key_getfilename( + dst_key_name(pubkey), dst_key_id(pubkey), + dst_key_alg(pubkey), + (DST_TYPE_PUBLIC | DST_TYPE_PRIVATE | + DST_TYPE_STATE), + NULL, mctx, &buf); + if (result2 != ISC_R_SUCCESS) { + char namebuf[DNS_NAME_FORMATSIZE]; + char algbuf[DNS_SECALG_FORMATSIZE]; + + dns_name_format(dst_key_name(pubkey), namebuf, + sizeof(namebuf)); + dns_secalg_format(dst_key_alg(pubkey), algbuf, + sizeof(algbuf)); + snprintf(filename, sizeof(filename) - 1, + "key file for %s/%s/%d", namebuf, + algbuf, dst_key_id(pubkey)); + } + + isc_log_write(dns_lctx, DNS_LOGCATEGORY_GENERAL, + DNS_LOGMODULE_DNSSEC, ISC_LOG_WARNING, + "dns_zone_findkeys: error reading %s: %s", + filename, isc_result_totext(result)); + } + + if (result == ISC_R_FILENOTFOUND || result == ISC_R_NOPERM) { + keys[count] = pubkey; + pubkey = NULL; + count++; + goto next; + } + + if (result != ISC_R_SUCCESS) { + goto failure; + } + + /* + * If a key is marked inactive, skip it + */ + if (!dns_dnssec_keyactive(keys[count], now)) { + dst_key_setinactive(pubkey, true); + dst_key_free(&keys[count]); + keys[count] = pubkey; + pubkey = NULL; + count++; + goto next; + } + + /* + * Whatever the key's default TTL may have + * been, the rdataset TTL takes priority. + */ + dst_key_setttl(keys[count], rdataset.ttl); + + if ((dst_key_flags(keys[count]) & DNS_KEYTYPE_NOAUTH) != 0) { + /* We should never get here. */ + dst_key_free(&keys[count]); + goto next; + } + count++; + next: + if (pubkey != NULL) { + dst_key_free(&pubkey); + } + dns_rdata_reset(&rdata); + result = dns_rdataset_next(&rdataset); + } + if (result != ISC_R_NOMORE) { + goto failure; + } + if (count == 0) { + result = ISC_R_NOTFOUND; + } else { + result = ISC_R_SUCCESS; + } + +failure: + if (dns_rdataset_isassociated(&rdataset)) { + dns_rdataset_disassociate(&rdataset); + } + if (pubkey != NULL) { + dst_key_free(&pubkey); + } + if (result != ISC_R_SUCCESS) { + while (count > 0) { + dst_key_free(&keys[--count]); + } + } + *nkeys = count; + return (result); +} + /*% * Find up to 'maxkeys' DNSSEC keys used for signing version 'ver' of database * 'db' for zone 'zone' in its key directory, then load these keys into 'keys'. @@ -6063,21 +6272,23 @@ was_dumping(dns_zone_t *zone) { * 'now'. Store the number of keys found in 'nkeys'. */ isc_result_t -dns__zone_findkeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, - isc_stdtime_t now, isc_mem_t *mctx, unsigned int maxkeys, - dst_key_t **keys, unsigned int *nkeys) { +dns_zone_findkeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, + isc_stdtime_t now, isc_mem_t *mctx, unsigned int maxkeys, + dst_key_t **keys, unsigned int *nkeys) { isc_result_t result; dns_dbnode_t *node = NULL; - const char *directory = dns_zone_getkeydirectory(zone); + + REQUIRE(DNS_ZONE_VALID(zone)); + REQUIRE(mctx != NULL); + REQUIRE(nkeys != NULL); + REQUIRE(keys != NULL); CHECK(dns_db_findnode(db, dns_db_origin(db), false, &node)); - memset(keys, 0, sizeof(*keys) * maxkeys); dns_zone_lock_keyfiles(zone); - result = dns_dnssec_findzonekeys(db, ver, node, dns_db_origin(db), - directory, now, mctx, maxkeys, keys, - nkeys); + result = findzonekeys(zone, db, ver, node, dns_db_origin(db), now, mctx, + maxkeys, keys, nkeys); dns_zone_unlock_keyfiles(zone); @@ -6120,8 +6331,8 @@ dns_zone_getdnsseckeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, /* Get keys from private key files. */ dns_zone_lock_keyfiles(zone); - result = dns_dnssec_findmatchingkeys(origin, dir, now, - dns_zone_getmctx(zone), keys); + result = dns_dnssec_findmatchingkeys(origin, kasp, dir, zone->keystores, + now, dns_zone_getmctx(zone), keys); dns_zone_unlock_keyfiles(zone); if (result != ISC_R_SUCCESS && result != ISC_R_NOTFOUND) { @@ -6134,8 +6345,8 @@ dns_zone_getdnsseckeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dns_rdatatype_none, 0, &keyset, NULL); if (result == ISC_R_SUCCESS) { CHECK(dns_dnssec_keylistfromrdataset( - origin, dir, dns_zone_getmctx(zone), &keyset, NULL, - NULL, false, false, &dnskeys)); + origin, kasp, dir, dns_zone_getmctx(zone), &keyset, + NULL, NULL, false, false, &dnskeys)); } else if (result != ISC_R_NOTFOUND) { CHECK(result); } @@ -6751,11 +6962,11 @@ zone_resigninc(dns_zone_t *zone) { now = isc_stdtime_now(); - result = dns__zone_findkeys(zone, db, version, now, zone->mctx, - DNS_MAXZONEKEYS, zone_keys, &nkeys); + result = dns_zone_findkeys(zone, db, version, now, zone->mctx, + DNS_MAXZONEKEYS, zone_keys, &nkeys); if (result != ISC_R_SUCCESS) { dns_zone_log(zone, ISC_LOG_ERROR, - "zone_resigninc:dns__zone_findkeys -> %s", + "zone_resigninc:dns_zone_findkeys -> %s", isc_result_totext(result)); goto failure; } @@ -7986,11 +8197,11 @@ zone_nsec3chain(dns_zone_t *zone) { now = isc_stdtime_now(); - result = dns__zone_findkeys(zone, db, version, now, zone->mctx, - DNS_MAXZONEKEYS, zone_keys, &nkeys); + result = dns_zone_findkeys(zone, db, version, now, zone->mctx, + DNS_MAXZONEKEYS, zone_keys, &nkeys); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, - "zone_nsec3chain:dns__zone_findkeys -> %s", + "zone_nsec3chain:dns_zone_findkeys -> %s", isc_result_totext(result)); goto failure; } @@ -9071,11 +9282,11 @@ zone_sign(dns_zone_t *zone) { now = isc_stdtime_now(); - result = dns__zone_findkeys(zone, db, version, now, zone->mctx, - DNS_MAXZONEKEYS, zone_keys, &nkeys); + result = dns_zone_findkeys(zone, db, version, now, zone->mctx, + DNS_MAXZONEKEYS, zone_keys, &nkeys); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, - "zone_sign:dns__zone_findkeys -> %s", + "zone_sign:dns_zone_findkeys -> %s", isc_result_totext(result)); goto cleanup; } @@ -15920,6 +16131,9 @@ dns_zone_dnskey_inuse(dns_zone_t *zone, dns_rdata_t *rdata, bool *inuse) { isc_result_t result = ISC_R_SUCCESS; isc_stdtime_t now = isc_stdtime_now(); isc_mem_t *mctx; + dns_kasp_t *kasp; + dns_keystorelist_t *keystores; + const char *keydir; REQUIRE(DNS_ZONE_VALID(zone)); REQUIRE(dns_rdatatype_iskeymaterial(rdata->type)); @@ -15930,10 +16144,14 @@ dns_zone_dnskey_inuse(dns_zone_t *zone, dns_rdata_t *rdata, bool *inuse) { *inuse = false; + kasp = dns_zone_getkasp(zone); + keydir = dns_zone_getkeydirectory(zone); + keystores = dns_zone_getkeystores(zone); + dns_zone_lock_keyfiles(zone); - result = dns_dnssec_findmatchingkeys(dns_zone_getorigin(zone), - dns_zone_getkeydirectory(zone), - now, mctx, &keylist); + result = dns_dnssec_findmatchingkeys(dns_zone_getorigin(zone), kasp, + keydir, keystores, now, mctx, + &keylist); dns_zone_unlock_keyfiles(zone); if (result == ISC_R_NOTFOUND) { return (ISC_R_SUCCESS); @@ -19414,6 +19632,32 @@ dns_zone_getkeydirectory(dns_zone_t *zone) { return (zone->keydirectory); } +void +dns_zone_setkeystores(dns_zone_t *zone, dns_keystorelist_t *keystores) { + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + zone->keystores = keystores; + UNLOCK_ZONE(zone); +} + +dns_keystorelist_t * +dns_zone_getkeystores(dns_zone_t *zone) { + dns_keystorelist_t *ks = NULL; + + REQUIRE(DNS_ZONE_VALID(zone)); + + LOCK_ZONE(zone); + if (inline_raw(zone) && zone->secure != NULL) { + ks = zone->secure->keystores; + } else { + ks = zone->keystores; + } + UNLOCK_ZONE(zone); + + return (ks); +} + unsigned int dns_zonemgr_getcount(dns_zonemgr_t *zmgr, int state) { dns_zone_t *zone; @@ -20105,11 +20349,11 @@ sign_apex(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, dst_key_t *zone_keys[DNS_MAXZONEKEYS]; unsigned int nkeys = 0, i; - result = dns__zone_findkeys(zone, db, ver, now, zone->mctx, - DNS_MAXZONEKEYS, zone_keys, &nkeys); + result = dns_zone_findkeys(zone, db, ver, now, zone->mctx, + DNS_MAXZONEKEYS, zone_keys, &nkeys); if (result != ISC_R_SUCCESS) { dnssec_log(zone, ISC_LOG_ERROR, - "sign_apex:dns__zone_findkeys -> %s", + "sign_apex:dns_zone_findkeys -> %s", isc_result_totext(result)); return (result); } @@ -20321,7 +20565,6 @@ static bool do_checkds(dns_zone_t *zone, dst_key_t *key, isc_stdtime_t now, bool dspublish) { dns_kasp_t *kasp = zone->kasp; - const char *dir = dns_zone_getkeydirectory(zone); isc_result_t result; uint32_t count = 0; uint32_t num; @@ -20372,7 +20615,7 @@ do_checkds(dns_zone_t *zone, dst_key_t *key, isc_stdtime_t now, dspublish ? "published" : "withdrawn", dst_key_id(key)); dns_zone_lock_keyfiles(zone); - result = dns_keymgr_checkds_id(kasp, &zone->checkds_ok, dir, now, now, + result = dns_keymgr_checkds_id(kasp, &zone->checkds_ok, now, now, dspublish, dst_key_id(key), dst_key_alg(key)); dns_zone_unlock_keyfiles(zone); @@ -21574,7 +21817,6 @@ zone_rekey(dns_zone_t *zone) { dns_rdataset_init(&keysigs); dns_rdataset_init(&cdsset); dns_rdataset_init(&cdnskeyset); - dir = dns_zone_getkeydirectory(zone); mctx = zone->mctx; dns_diff_init(mctx, &diff); dns_diff_init(mctx, &_sig_diff); @@ -21588,6 +21830,7 @@ zone_rekey(dns_zone_t *zone) { now = isc_time_seconds(&timenow); kasp = zone->kasp; + dir = dns_zone_getkeydirectory(zone); dnssec_log(zone, ISC_LOG_INFO, "reconfiguring zone keys"); @@ -21634,8 +21877,8 @@ zone_rekey(dns_zone_t *zone) { dns_zone_lock_keyfiles(zone); result = dns_dnssec_keylistfromrdataset( - &zone->origin, dir, mctx, &keyset, &keysigs, &soasigs, - false, false, &dnskeys); + &zone->origin, kasp, dir, mctx, &keyset, &keysigs, + &soasigs, false, false, &dnskeys); dns_zone_unlock_keyfiles(zone); @@ -21696,8 +21939,8 @@ zone_rekey(dns_zone_t *zone) { KASP_LOCK(kasp); dns_zone_lock_keyfiles(zone); - result = dns_dnssec_findmatchingkeys(&zone->origin, dir, now, mctx, - &keys); + result = dns_dnssec_findmatchingkeys(&zone->origin, kasp, dir, + zone->keystores, now, mctx, &keys); dns_zone_unlock_keyfiles(zone); if (result != ISC_R_SUCCESS) { @@ -21733,7 +21976,7 @@ zone_rekey(dns_zone_t *zone) { if (result == ISC_R_SUCCESS || result == ISC_R_NOTFOUND) { dns_zone_lock_keyfiles(zone); result = dns_keymgr_run(&zone->origin, zone->rdclass, - dir, mctx, &keys, &dnskeys, + mctx, &keys, &dnskeys, dir, kasp, now, &nexttime); dns_zone_unlock_keyfiles(zone); diff --git a/lib/dns/zone_p.h b/lib/dns/zone_p.h index fcbd3711c2..16ee0e522a 100644 --- a/lib/dns/zone_p.h +++ b/lib/dns/zone_p.h @@ -29,11 +29,6 @@ typedef struct { bool offline; } dns__zonediff_t; -isc_result_t -dns__zone_findkeys(dns_zone_t *zone, dns_db_t *db, dns_dbversion_t *ver, - isc_stdtime_t now, isc_mem_t *mctx, unsigned int maxkeys, - dst_key_t **keys, unsigned int *nkeys); - isc_result_t dns__zone_updatesigs(dns_diff_t *diff, dns_db_t *db, dns_dbversion_t *version, dst_key_t *zone_keys[], unsigned int nkeys, diff --git a/lib/isccfg/check.c b/lib/isccfg/check.c index c4e09f1c88..c4ddf1940d 100644 --- a/lib/isccfg/check.c +++ b/lib/isccfg/check.c @@ -47,6 +47,7 @@ #include #include #include +#include #include #include #include @@ -77,8 +78,9 @@ fileexist(const cfg_obj_t *obj, isc_symtab_t *symtab, bool writeable, isc_log_t *logctxlogc); static isc_result_t -keydirexist(const cfg_obj_t *zcgf, const char *dir, const char *kaspnamestr, - isc_symtab_t *symtab, isc_log_t *logctx, isc_mem_t *mctx); +keydirexist(const cfg_obj_t *zcgf, const char *optname, dns_name_t *zname, + const char *dirname, const char *kaspnamestr, isc_symtab_t *symtab, + isc_log_t *logctx, isc_mem_t *mctx); static const cfg_obj_t * find_maplist(const cfg_obj_t *config, const char *listname, const char *name); @@ -1200,6 +1202,8 @@ check_options(const cfg_obj_t *options, const cfg_obj_t *config, const char *str; isc_buffer_t b; uint32_t lifetime = 3600; + dns_keystorelist_t kslist; + dns_keystore_t *ks = NULL, *ks_next = NULL; const char *ccalg = "siphash24"; cfg_aclconfctx_t *actx = NULL; static const char *sources[] = { @@ -1329,6 +1333,108 @@ check_options(const cfg_obj_t *options, const cfg_obj_t *config, } } + /* + * Check key-store. + */ + ISC_LIST_INIT(kslist); + + obj = NULL; + (void)cfg_map_get(options, "key-store", &obj); + if (obj != NULL) { + if (optlevel != optlevel_config) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "may only be configured at the top level"); + if (result == ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + } + } else if (cfg_obj_islist(obj)) { + for (element = cfg_list_first(obj); element != NULL; + element = cfg_list_next(element)) + { + isc_result_t ret; + const char *val; + cfg_obj_t *kconfig = cfg_listelt_value(element); + const cfg_obj_t *kopt; + const cfg_obj_t *kobj = NULL; + if (!cfg_obj_istuple(kconfig)) { + continue; + } + val = cfg_obj_asstring( + cfg_tuple_get(kconfig, "name")); + if (strcmp(DNS_KEYSTORE_KEYDIRECTORY, val) == 0) + { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "name '%s' not allowed", + DNS_KEYSTORE_KEYDIRECTORY); + if (result == ISC_R_SUCCESS) { + result = ISC_R_FAILURE; + continue; + } + } + + kopt = cfg_tuple_get(kconfig, "options"); + if (cfg_map_get(kopt, "directory", &kobj) == + ISC_R_SUCCESS) + { + val = cfg_obj_asstring(kobj); + ret = isc_file_isdirectory(val); + switch (ret) { + case ISC_R_SUCCESS: + break; + case ISC_R_FILENOTFOUND: + cfg_obj_log( + obj, logctx, + ISC_LOG_WARNING, + "key-store directory: " + "'%s' does not exist", + val); + break; + case ISC_R_INVALIDFILE: + cfg_obj_log( + obj, logctx, + ISC_LOG_WARNING, + "key-store directory: " + "'%s' is not a " + "directory", + val); + break; + default: + cfg_obj_log( + obj, logctx, + ISC_LOG_WARNING, + "key-store directory: " + "'%s' %s", + val, + isc_result_totext(ret)); + if (result == ISC_R_SUCCESS) { + result = ret; + } + } + } + + ret = cfg_keystore_fromconfig(kconfig, mctx, + logctx, NULL, + &kslist, NULL); + if (ret != ISC_R_SUCCESS) { + if (result == ISC_R_SUCCESS) { + result = ret; + } + } + } + } + } + + /* + * Add default key-store "key-directory". + */ + tresult = cfg_keystore_fromconfig(NULL, mctx, logctx, NULL, &kslist, + NULL); + if (tresult != ISC_R_SUCCESS) { + if (result == ISC_R_SUCCESS) { + result = tresult; + } + } + /* * Check dnssec-policy. */ @@ -1366,7 +1472,8 @@ check_options(const cfg_obj_t *options, const cfg_obj_t *config, ret = cfg_kasp_fromconfig( kconfig, NULL, check_algorithms, - mctx, logctx, &list, &kasp); + mctx, logctx, &kslist, &list, + &kasp); if (ret != ISC_R_SUCCESS) { if (result == ISC_R_SUCCESS) { result = ret; @@ -1407,6 +1514,18 @@ check_options(const cfg_obj_t *options, const cfg_obj_t *config, } } + /* + * Cleanup key-store. + */ + for (ks = ISC_LIST_HEAD(kslist); ks != NULL; ks = ks_next) { + ks_next = ISC_LIST_NEXT(ks, link); + ISC_LIST_UNLINK(kslist, ks, link); + dns_keystore_detach(&ks); + } + + /* + * Other checks. + */ obj = NULL; cfg_map_get(options, "max-rsa-exponent-size", &obj); if (obj != NULL) { @@ -2800,6 +2919,129 @@ cleanup: return (retval); } +static isc_result_t +check_keydir(const cfg_obj_t *config, const cfg_obj_t *zconfig, + dns_name_t *zname, const char *name, const char *keydir, + isc_symtab_t *keydirs, isc_log_t *logctx, isc_mem_t *mctx) { + const char *dir = keydir; + const cfg_listelt_t *element; + isc_result_t ret, result = ISC_R_SUCCESS; + bool do_cleanup = false; + bool done = false; + bool keystore = false; + + const cfg_obj_t *kasps = NULL; + dns_kasp_t *kasp = NULL, *kasp_next = NULL; + dns_kasplist_t kasplist; + + const cfg_obj_t *keystores = NULL; + dns_keystore_t *ks = NULL, *ks_next = NULL; + dns_keystorelist_t kslist; + + /* If no dnssec-policy or key-store, use the dir (key-directory) */ + (void)cfg_map_get(config, "dnssec-policy", &kasps); + (void)cfg_map_get(config, "key-store", &keystores); + if (kasps == NULL || keystores == NULL) { + goto check; + } + + ISC_LIST_INIT(kasplist); + ISC_LIST_INIT(kslist); + do_cleanup = true; + + /* + * Build the keystore list. + */ + for (element = cfg_list_first(keystores); element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *kcfg = cfg_listelt_value(element); + (void)cfg_keystore_fromconfig(kcfg, mctx, logctx, NULL, &kslist, + NULL); + } + (void)cfg_keystore_fromconfig(NULL, mctx, logctx, NULL, &kslist, NULL); + + /* + * Look for the dnssec-policy by name, which is the dnssec-policy + * for the zone in question. + */ + for (element = cfg_list_first(kasps); element != NULL; + element = cfg_list_next(element)) + { + cfg_obj_t *kconfig = cfg_listelt_value(element); + const cfg_obj_t *kaspobj = NULL; + + if (!cfg_obj_istuple(kconfig)) { + continue; + } + + kaspobj = cfg_tuple_get(kconfig, "name"); + if (strcmp(name, cfg_obj_asstring(kaspobj)) != 0) { + continue; + } + + ret = cfg_kasp_fromconfig(kconfig, NULL, false, mctx, logctx, + &kslist, &kasplist, &kasp); + if (ret != ISC_R_SUCCESS) { + kasp = NULL; + } + break; + } + if (kasp == NULL) { + goto check; + } + + /* Check key-stores of keys */ + dns_kasp_freeze(kasp); + for (dns_kasp_key_t *kkey = ISC_LIST_HEAD(dns_kasp_keys(kasp)); + kkey != NULL; kkey = ISC_LIST_NEXT(kkey, link)) + { + dns_keystore_t *kks = dns_kasp_key_keystore(kkey); + dir = dns_keystore_directory(kks, keydir); + keystore = (kks != NULL && strcmp(DNS_KEYSTORE_KEYDIRECTORY, + dns_keystore_name(kks)) != 0); + + ret = keydirexist(zconfig, + keystore ? "key-store directory" + : "key-directory", + zname, dir, name, keydirs, logctx, mctx); + if (ret != ISC_R_SUCCESS) { + result = ret; + } + } + dns_kasp_thaw(kasp); + done = true; + +check: + if (!done) { + ret = keydirexist(zconfig, "key-directory", zname, dir, name, + keydirs, logctx, mctx); + if (ret != ISC_R_SUCCESS) { + result = ret; + } + } + + if (do_cleanup) { + if (kasp != NULL) { + dns_kasp_detach(&kasp); + } + for (kasp = ISC_LIST_HEAD(kasplist); kasp != NULL; + kasp = kasp_next) + { + kasp_next = ISC_LIST_NEXT(kasp, link); + ISC_LIST_UNLINK(kasplist, kasp, link); + dns_kasp_detach(&kasp); + } + for (ks = ISC_LIST_HEAD(kslist); ks != NULL; ks = ks_next) { + ks_next = ISC_LIST_NEXT(ks, link); + ISC_LIST_UNLINK(kslist, ks, link); + dns_keystore_detach(&ks); + } + } + + return (result); +} + static isc_result_t check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, const cfg_obj_t *config, isc_symtab_t *symtab, @@ -3068,8 +3310,6 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, (void)cfg_map_get(goptions, "dnssec-policy", &obj); } if (obj != NULL) { - const cfg_obj_t *kasps = NULL; - kaspname = cfg_obj_asstring(obj); if (strcmp(kaspname, "default") == 0) { has_dnssecpolicy = true; @@ -3081,6 +3321,7 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, has_dnssecpolicy = false; kasp_inlinesigning = false; } else { + const cfg_obj_t *kasps = NULL; (void)cfg_map_get(config, "dnssec-policy", &kasps); for (element = cfg_list_first(kasps); element != NULL; element = cfg_list_next(element)) @@ -3653,19 +3894,18 @@ check_zoneconf(const cfg_obj_t *zconfig, const cfg_obj_t *voptions, } /* - * Make sure there is no other zone with the same - * key-directory and a different dnssec-policy. + * Make sure there is no other zone with the same key directory (from + * (key-directory or key-store/directory) and a different dnssec-policy. */ if (zname != NULL) { - char keydirbuf[DNS_NAME_FORMATSIZE + 128]; - char *tmp = keydirbuf; - size_t len = sizeof(keydirbuf); - dns_name_format(zname, keydirbuf, sizeof(keydirbuf)); - len -= strlen(tmp); - tmp += strlen(tmp); - (void)snprintf(tmp, len, "/%s", (dir == NULL) ? "(null)" : dir); - tresult = keydirexist(zconfig, (const char *)keydirbuf, - kaspname, keydirs, logctx, mctx); + if (has_dnssecpolicy) { + tresult = check_keydir(config, zconfig, zname, kaspname, + dir, keydirs, logctx, mctx); + } else { + tresult = keydirexist(zconfig, "key-directory", zname, + dir, kaspname, keydirs, logctx, + mctx); + } if (tresult != ISC_R_SUCCESS) { result = tresult; } @@ -3885,16 +4125,33 @@ fileexist(const cfg_obj_t *obj, isc_symtab_t *symtab, bool writeable, } static isc_result_t -keydirexist(const cfg_obj_t *zcfg, const char *keydir, const char *kaspnamestr, - isc_symtab_t *symtab, isc_log_t *logctx, isc_mem_t *mctx) { +keydirexist(const cfg_obj_t *zcfg, const char *optname, dns_name_t *zname, + const char *dirname, const char *kaspnamestr, isc_symtab_t *symtab, + isc_log_t *logctx, isc_mem_t *mctx) { isc_result_t result; isc_symvalue_t symvalue; char *symkey; + char keydirbuf[DNS_NAME_FORMATSIZE + 128]; + char *keydir = keydirbuf; + size_t len = sizeof(keydirbuf); + size_t n; if (kaspnamestr == NULL || strcmp(kaspnamestr, "none") == 0) { return (ISC_R_SUCCESS); } + dns_name_format(zname, keydirbuf, sizeof(keydirbuf)); + len -= strlen(keydir); + keydir += strlen(keydir); + n = snprintf(keydir, len, "/%s", (dirname == NULL) ? "." : dirname); + if (n > len) { + cfg_obj_log(zcfg, logctx, ISC_LOG_WARNING, + "%s '%s' truncated because too long, may cause " + "false positives in key directory in use checks", + optname, (dirname == NULL) ? "." : dirname); + } + keydir = keydirbuf; + result = isc_symtab_lookup(symtab, keydir, 0, &symvalue); if (result == ISC_R_SUCCESS) { const cfg_obj_t *kasp = NULL; @@ -3916,9 +4173,9 @@ keydirexist(const cfg_obj_t *zcfg, const char *keydir, const char *kaspnamestr, } cfg_obj_log(zcfg, logctx, ISC_LOG_ERROR, - "key-directory '%s' already in use by zone %s with " + "%s '%s' already in use by zone %s with " "policy %s: %s:%u", - keydir, + optname, keydir, cfg_obj_asstring(cfg_tuple_get(exist, "name")), cfg_obj_asstring(kasp), file, line); return (ISC_R_EXISTS); diff --git a/lib/isccfg/include/isccfg/kaspconf.h b/lib/isccfg/include/isccfg/kaspconf.h index 744a327695..ccc1cecc5d 100644 --- a/lib/isccfg/include/isccfg/kaspconf.h +++ b/lib/isccfg/include/isccfg/kaspconf.h @@ -26,13 +26,17 @@ ISC_LANG_BEGINDECLS isc_result_t cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, bool check_algorithms, isc_mem_t *mctx, isc_log_t *logctx, - dns_kasplist_t *kasplist, dns_kasp_t **kaspp); + dns_keystorelist_t *keystorelist, dns_kasplist_t *kasplist, + dns_kasp_t **kaspp); /*%< * Create and configure a KASP. If 'default_kasp' is not NULL, the built-in * default configuration is used to set values that are not explicitly set in - * the policy. If a 'kasplist' is provided, a lookup happens and if a KASP - * already exists with the same name, no new KASP is created, and no attach to - * 'kaspp' happens. + * the policy. + * + * If a 'kasplist' is provided, a lookup happens and if a KASP already exists + * with the same name, no new KASP is created, and no attach to 'kaspp' happens. + * + * The 'keystorelist' is where to lookup key stores if KASP keys are using them. * * If 'check_algorithms' is true then the dnssec-policy DNSSEC key * algorithms are checked against those supported by the crypto provider. @@ -56,4 +60,33 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, *\li Other errors are possible. */ +isc_result_t +cfg_keystore_fromconfig(const cfg_obj_t *config, isc_mem_t *mctx, + isc_log_t *logctx, const char *engine, + dns_keystorelist_t *keystorelist, + dns_keystore_t **kspp); +/*%< + * Create and configure a key store. If a 'keystorelist' is provided, a lookup + * happens and if a keystore already exists with the same name, no new one is + * created, and no attach to 'kspp' happens. + * + * Requires: + * + *\li config != NULL + + *\li 'mctx' is a valid memory context. + * + *\li 'logctx' is a valid logging context. + * + *\li kspp == NULL || *kspp == NULL + * + * Returns: + * + *\li #ISC_R_SUCCESS If creating and configuring the keystore succeeds. + *\li #ISC_R_EXISTS If 'keystorelist' already has a keystore with 'name'. + *\li #ISC_R_NOMEMORY + * + *\li Other errors are possible. + */ + ISC_LANG_ENDDECLS diff --git a/lib/isccfg/kaspconf.c b/lib/isccfg/kaspconf.c index 1a2b0da47c..f756ed97da 100644 --- a/lib/isccfg/kaspconf.c +++ b/lib/isccfg/kaspconf.c @@ -24,6 +24,7 @@ #include #include +#include #include #include #include @@ -89,12 +90,30 @@ get_duration(const cfg_obj_t **maps, const char *option, const char *dfl) { return (cfg_obj_asduration(obj)); } +/* + * Utility function for configuring strings. + */ +static const char * +get_string(const cfg_obj_t **maps, const char *option) { + const cfg_obj_t *obj; + isc_result_t result; + obj = NULL; + + result = confget(maps, option, &obj); + if (result == ISC_R_NOTFOUND) { + return (NULL); + } + INSIST(result == ISC_R_SUCCESS); + return (cfg_obj_asstring(obj)); +} + /* * Create a new kasp key derived from configuration. */ static isc_result_t cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, bool check_algorithms, isc_log_t *logctx, + dns_keystorelist_t *keystorelist, uint32_t ksk_min_lifetime, uint32_t zsk_min_lifetime) { isc_result_t result; dns_kasp_key_t *key = NULL; @@ -111,8 +130,15 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, key->lifetime = 0; /* unlimited */ key->algorithm = DNS_KEYALG_ECDSA256; key->length = -1; + result = dns_keystorelist_find(keystorelist, + DNS_KEYSTORE_KEYDIRECTORY, + &key->keystore); + if (result != ISC_R_SUCCESS) { + goto cleanup; + } } else { const char *rolestr = NULL; + const char *keydir = NULL; const cfg_obj_t *obj = NULL; isc_consttextregion_t alg; bool error = false; @@ -127,6 +153,29 @@ cfg_kaspkey_fromconfig(const cfg_obj_t *config, dns_kasp_t *kasp, key->role |= DNS_KASP_KEY_ROLE_ZSK; } + obj = cfg_tuple_get(config, "keystorage"); + if (cfg_obj_isstring(obj)) { + keydir = cfg_obj_asstring(obj); + } + if (keydir == NULL) { + keydir = DNS_KEYSTORE_KEYDIRECTORY; + } + result = dns_keystorelist_find(keystorelist, keydir, + &key->keystore); + if (result == ISC_R_NOTFOUND) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnssec-policy: keystore %s does not exist", + keydir); + result = ISC_R_FAILURE; + goto cleanup; + } else if (result != ISC_R_SUCCESS) { + cfg_obj_log(obj, logctx, ISC_LOG_ERROR, + "dnssec-policy: bad keystore %s", keydir); + result = ISC_R_FAILURE; + goto cleanup; + } + INSIST(key->keystore != NULL); + key->lifetime = 0; /* unlimited */ obj = cfg_tuple_get(config, "lifetime"); if (cfg_obj_isduration(obj)) { @@ -349,7 +398,8 @@ add_digest(dns_kasp_t *kasp, const cfg_obj_t *digest, isc_log_t *logctx) { isc_result_t cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, bool check_algorithms, isc_mem_t *mctx, isc_log_t *logctx, - dns_kasplist_t *kasplist, dns_kasp_t **kaspp) { + dns_keystorelist_t *keystorelist, dns_kasplist_t *kasplist, + dns_kasp_t **kaspp) { isc_result_t result; const cfg_obj_t *maps[2]; const cfg_obj_t *koptions = NULL; @@ -524,8 +574,13 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, cfg_obj_t *kobj = cfg_listelt_value(element); result = cfg_kaspkey_fromconfig( kobj, kasp, check_algorithms, logctx, - ksk_min_lifetime, zsk_min_lifetime); + keystorelist, ksk_min_lifetime, + zsk_min_lifetime); if (result != ISC_R_SUCCESS) { + cfg_obj_log(kobj, logctx, ISC_LOG_ERROR, + "dnssec-policy: failed to " + "configure keys (%s)", + isc_result_totext(result)); goto cleanup; } } @@ -596,9 +651,12 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, new_key = NULL; result = dns_kasp_key_create(kasp, &new_key); if (result != ISC_R_SUCCESS) { + cfg_obj_log(config, logctx, ISC_LOG_ERROR, + "dnssec-policy: failed to " + "configure keys (%s)", + isc_result_totext(result)); goto cleanup; } - if (dns_kasp_key_ksk(key)) { new_key->role |= DNS_KASP_KEY_ROLE_KSK; } @@ -608,6 +666,16 @@ cfg_kasp_fromconfig(const cfg_obj_t *config, dns_kasp_t *default_kasp, new_key->lifetime = dns_kasp_key_lifetime(key); new_key->algorithm = dns_kasp_key_algorithm(key); new_key->length = dns_kasp_key_size(key); + result = dns_keystorelist_find( + keystorelist, DNS_KEYSTORE_KEYDIRECTORY, + &new_key->keystore); + if (result != ISC_R_SUCCESS) { + cfg_obj_log(config, logctx, ISC_LOG_ERROR, + "dnssec-policy: failed to " + "find keystore (%s)", + isc_result_totext(result)); + goto cleanup; + } dns_kasp_addkey(kasp, new_key); } } @@ -655,3 +723,75 @@ cleanup: dns_kasp_detach(&kasp); return (result); } + +isc_result_t +cfg_keystore_fromconfig(const cfg_obj_t *config, isc_mem_t *mctx, + isc_log_t *logctx, const char *engine, + dns_keystorelist_t *keystorelist, + dns_keystore_t **kspp) { + isc_result_t result; + const cfg_obj_t *maps[2]; + const cfg_obj_t *koptions = NULL; + const char *name = NULL; + const char *keydirectory = DNS_KEYSTORE_KEYDIRECTORY; + dns_keystore_t *keystore = NULL; + int i = 0; + + if (config != NULL) { + name = cfg_obj_asstring(cfg_tuple_get(config, "name")); + } else { + name = keydirectory; + } + INSIST(name != NULL); + + result = dns_keystorelist_find(keystorelist, name, &keystore); + + if (result == ISC_R_SUCCESS) { + cfg_obj_log(config, logctx, ISC_LOG_ERROR, + "key-store: duplicate key-store found '%s'", name); + dns_keystore_detach(&keystore); + return (ISC_R_EXISTS); + } + if (result != ISC_R_NOTFOUND) { + cfg_obj_log(config, logctx, ISC_LOG_ERROR, + "key-store: lookup '%s' failed: %s", name, + isc_result_totext(result)); + return (result); + } + + /* + * No key-store with configured name was found in list, create new one. + */ + INSIST(keystore == NULL); + result = dns_keystore_create(mctx, name, engine, &keystore); + if (result != ISC_R_SUCCESS) { + return (result); + } + INSIST(keystore != NULL); + + /* Now configure. */ + INSIST(DNS_KEYSTORE_VALID(keystore)); + + if (config != NULL) { + koptions = cfg_tuple_get(config, "options"); + maps[i++] = koptions; + maps[i] = NULL; + dns_keystore_setdirectory(keystore, + get_string(maps, "directory")); + dns_keystore_setpkcs11uri(keystore, + get_string(maps, "pkcs11-uri")); + } + + /* Append it to the list for future lookups. */ + ISC_LIST_APPEND(*keystorelist, keystore, link); + INSIST(!(ISC_LIST_EMPTY(*keystorelist))); + + /* Success: Attach the keystore to the pointer and return. */ + if (kspp != NULL) { + INSIST(*kspp == NULL); + dns_keystore_attach(keystore, kspp); + } + + /* Don't detach as keystore is on '*keystorelist' */ + return (ISC_R_SUCCESS); +} diff --git a/lib/isccfg/namedconf.c b/lib/isccfg/namedconf.c index 625edc7f4f..8465139e0b 100644 --- a/lib/isccfg/namedconf.c +++ b/lib/isccfg/namedconf.c @@ -105,6 +105,7 @@ static cfg_type_t cfg_type_http_description; static cfg_type_t cfg_type_ixfrdifftype; static cfg_type_t cfg_type_ixfrratio; static cfg_type_t cfg_type_key; +static cfg_type_t cfg_type_keystore; static cfg_type_t cfg_type_logfile; static cfg_type_t cfg_type_logging; static cfg_type_t cfg_type_logseverity; @@ -477,7 +478,6 @@ static cfg_tuplefielddef_t dnssecpolicy_fields[] = { { "options", &cfg_type_dnssecpolicyopts, 0 }, { NULL, NULL, 0 } }; - static cfg_type_t cfg_type_dnssecpolicy = { "dnssec-policy", cfg_parse_tuple, cfg_print_tuple, cfg_doc_tuple, &cfg_rep_tuple, dnssecpolicy_fields @@ -582,10 +582,58 @@ static cfg_type_t cfg_type_dnsseckeyrole = { /*% * DNSSEC key storage types. */ -static const char *dnsseckeystore_enums[] = { "key-directory", NULL }; -static cfg_type_t cfg_type_dnsseckeystore = { - "dnssec-key-storage", parse_optional_enum, cfg_print_ustring, - doc_optional_enum, &cfg_rep_string, dnsseckeystore_enums +static keyword_type_t keystore_kw = { "key-store", &cfg_type_astring }; +static cfg_type_t cfg_type_keystorage = { "keystorage", parse_keyvalue, + print_keyvalue, doc_keyvalue, + &cfg_rep_string, &keystore_kw }; + +static isc_result_t +parse_keystore(cfg_parser_t *pctx, const cfg_type_t *type, cfg_obj_t **ret) { + isc_result_t result; + cfg_obj_t *obj = NULL; + + UNUSED(type); + + CHECK(cfg_peektoken(pctx, 0)); + if (pctx->token.type == isc_tokentype_string && + strcasecmp(TOKEN_STRING(pctx), "key-directory") == 0) + { + CHECK(cfg_parse_obj(pctx, &cfg_type_ustring, &obj)); + } else if (pctx->token.type == isc_tokentype_string && + strcasecmp(TOKEN_STRING(pctx), "key-store") == 0) + { + CHECK(cfg_parse_obj(pctx, &cfg_type_keystorage, &obj)); + } else { + CHECK(cfg_parse_void(pctx, NULL, &obj)); + } + + *ret = obj; +cleanup: + return (result); +} + +static void +doc_keystore(cfg_printer_t *pctx, const cfg_type_t *type) { + UNUSED(type); + + cfg_print_cstr(pctx, "[ key-directory | key-store ]"); +} + +static void +print_keystore(cfg_printer_t *pctx, const cfg_obj_t *obj) { + REQUIRE(pctx != NULL); + REQUIRE(obj != NULL); + REQUIRE(obj->type->rep == &cfg_rep_string); + + if (strcasecmp(cfg_obj_asstring(obj), "key-directory") != 0) { + cfg_print_cstr(pctx, "key-store "); + } + cfg_print_ustring(pctx, obj); +} + +static cfg_type_t cfg_type_optional_keystore = { + "optionalkeystorage", parse_keystore, print_keystore, + doc_keystore, &cfg_rep_string, &keystore_kw }; /*% @@ -604,7 +652,7 @@ static cfg_type_t cfg_type_lifetime = { "lifetime", parse_keyvalue, static cfg_tuplefielddef_t kaspkey_fields[] = { { "role", &cfg_type_dnsseckeyrole, 0 }, - { "keystore-type", &cfg_type_dnsseckeystore, 0 }, + { "keystorage", &cfg_type_optional_keystore, 0 }, { "lifetime", &cfg_type_lifetime, 0 }, { "algorithm", &cfg_type_algorithm, 0 }, { "length", &cfg_type_optional_uint32, 0 }, @@ -1143,6 +1191,7 @@ static cfg_clausedef_t namedconf_clauses[] = { { "http", &cfg_type_http_description, CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_NOTCONFIGURED }, #endif + { "key-store", &cfg_type_keystore, CFG_CLAUSEFLAG_MULTI }, { "logging", &cfg_type_logging, 0 }, { "lwres", NULL, CFG_CLAUSEFLAG_MULTI | CFG_CLAUSEFLAG_ANCIENT }, { "masters", &cfg_type_remoteservers, @@ -2549,6 +2598,30 @@ static cfg_type_t cfg_type_key = { "key", cfg_parse_named_map, cfg_print_map, cfg_doc_map, &cfg_rep_map, key_clausesets }; +/*% + * A key-store statement. + */ +static cfg_clausedef_t keystore_clauses[] = { + { "directory", &cfg_type_astring, 0 }, + { "pkcs11-uri", &cfg_type_qstring, 0 }, + { NULL, NULL, 0 } +}; + +static cfg_clausedef_t *keystore_clausesets[] = { keystore_clauses, NULL }; +static cfg_type_t cfg_type_keystoreopts = { + "keystoreopts", cfg_parse_map, cfg_print_map, + cfg_doc_map, &cfg_rep_map, keystore_clausesets +}; + +static cfg_tuplefielddef_t keystore_fields[] = { + { "name", &cfg_type_astring, 0 }, + { "options", &cfg_type_keystoreopts, 0 }, + { NULL, NULL, 0 } +}; +static cfg_type_t cfg_type_keystore = { "key-store", cfg_parse_tuple, + cfg_print_tuple, cfg_doc_tuple, + &cfg_rep_tuple, keystore_fields }; + /*% * Clauses that can be found in a 'server' statement. * diff --git a/tests/dns/sigs_test.c b/tests/dns/sigs_test.c index 94d94fbb39..0910504003 100644 --- a/tests/dns/sigs_test.c +++ b/tests/dns/sigs_test.c @@ -315,8 +315,8 @@ ISC_RUN_TEST_IMPL(updatesigs_next) { result = dns_zone_setkeydirectory(zone, TESTS_DIR "/testkeys"); assert_int_equal(result, ISC_R_SUCCESS); - result = dns__zone_findkeys(zone, db, NULL, now, mctx, DNS_MAXZONEKEYS, - zone_keys, &nkeys); + result = dns_zone_findkeys(zone, db, NULL, now, mctx, DNS_MAXZONEKEYS, + zone_keys, &nkeys); assert_int_equal(result, ISC_R_SUCCESS); assert_int_equal(nkeys, 2);