diff --git a/doc/configuration.rst b/doc/configuration.rst index 448523570402bafa8bd18b822cd03b57007189ff..97978b707e96db1873b63456234c09f8217eb2ee 100644 --- a/doc/configuration.rst +++ b/doc/configuration.rst @@ -438,3 +438,15 @@ of the limitations will be hopefully removed in the near future. - Legacy key import requires a private key. - Legacy key export is not implemented. - DS record export is not implemented. + +.. _dnssec-keyusage: + +DNSSEC keys used by multiple zones +---------------------------------- + +Using same key for multiple zones with automatic key management is possible. +However, all zones must be listed in keyusage (keys directory) or they will be deleted, +when they retire in any zone. + +If keys are added manually as published, but not active (for next rollover event), they are added automatically. + diff --git a/src/dnssec/Makefile.am b/src/dnssec/Makefile.am index 764cb424db198acf4987e2a06c5c2f3fd25a0179..13ac2c45e93ff3980e69fc112b65bd1bee1b398c 100644 --- a/src/dnssec/Makefile.am +++ b/src/dnssec/Makefile.am @@ -65,6 +65,7 @@ include_dnssec_HEADERS = \ lib/dnssec/keyid.h \ lib/dnssec/keystate.h \ lib/dnssec/keystore.h \ + lib/dnssec/keyusage.h \ lib/dnssec/keytag.h \ lib/dnssec/list.h \ lib/dnssec/nsec.h \ @@ -124,6 +125,7 @@ libdnssec_la_SOURCES = \ lib/keystore/pkcs11.c \ lib/keystore/pkcs8.c \ lib/keystore/pkcs8_dir.c \ + lib/keyusage/keyusage.c \ lib/list/list.c \ lib/list/ucw_clists.h \ lib/nsec/bitmap.c \ diff --git a/src/dnssec/lib/dnssec/keyusage.h b/src/dnssec/lib/dnssec/keyusage.h new file mode 100644 index 0000000000000000000000000000000000000000..916d5a2d072d989e1e67fa1efd6b64300c914c21 --- /dev/null +++ b/src/dnssec/lib/dnssec/keyusage.h @@ -0,0 +1,40 @@ +/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#pragma once + +#include <dnssec/list.h> + +typedef struct record { + char *keytag; + dnssec_list_t *zones; +} record_keyusage_t; + +typedef dnssec_list_t dnssec_keyusage_t; + +int dnssec_keyusage_add(dnssec_keyusage_t *keyusage, const char *keytag, char *zone); + +int dnssec_keyusage_remove(dnssec_keyusage_t *keyusage, const char *keytag, char *zone); + +bool dnssec_keyusage_is_used(dnssec_keyusage_t *keyusage, const char *keytag); + +int dnssec_keyusage_load(dnssec_keyusage_t *keyusage, const char *filename); + +int dnssec_keyusage_save(dnssec_keyusage_t *keyusage, const char *filename); + +dnssec_keyusage_t *dnssec_keyusage_new(void); + +void dnssec_keyusage_free(dnssec_keyusage_t *keyusage); diff --git a/src/dnssec/lib/event/action/initial_key.c b/src/dnssec/lib/event/action/initial_key.c index bbe7fcb4d17e43aee59f6e4134a0723a6baff8c4..3e43e413fb172901aed06f7e3eae1891a1ee208f 100644 --- a/src/dnssec/lib/event/action/initial_key.c +++ b/src/dnssec/lib/event/action/initial_key.c @@ -22,6 +22,7 @@ #include "shared.h" #include "event/action.h" #include "event/utils.h" +#include "dnssec/keyusage.h" /*! * Scan zone keys and check if ZSK and KSK key exists. @@ -58,6 +59,19 @@ static int generate_initial_key(dnssec_event_ctx_t *ctx, bool ksk) return r; } + if (!ksk) { + char *path; + if (asprintf(&path, "%s/keyusage", ctx->kasp->functions->base_path(ctx->kasp->ctx)) == -1){ + return DNSSEC_ENOMEM; + } + dnssec_keyusage_t *keyusage = dnssec_keyusage_new(); + dnssec_keyusage_load(keyusage, path); + dnssec_keyusage_add(keyusage, key->id, ctx->zone->name); + dnssec_keyusage_save(keyusage, path); + dnssec_keyusage_free(keyusage); + free(path); + } + key->timing.active = ctx->now; key->timing.publish = ctx->now; diff --git a/src/dnssec/lib/event/action/zsk_rollover.c b/src/dnssec/lib/event/action/zsk_rollover.c index 9fd1447700c5db60bae9e8cbb2cc8ba058c08b12..24f64c99b4423ec37e8bfa56f17bfca946099f07 100644 --- a/src/dnssec/lib/event/action/zsk_rollover.c +++ b/src/dnssec/lib/event/action/zsk_rollover.c @@ -18,6 +18,7 @@ #include "dnssec/error.h" #include "dnssec/event.h" +#include "dnssec/keyusage.h" #include "event/action.h" #include "dnssec/keystate.h" #include "event/utils.h" @@ -169,6 +170,17 @@ static int exec_new_signatures(dnssec_event_ctx_t *ctx) active->timing.retire = ctx->now; rolling->timing.active = ctx->now; + char *path; + if (asprintf(&path, "%s/keyusage", ctx->kasp->functions->base_path(ctx->kasp->ctx)) == -1){ + return DNSSEC_ENOMEM; + } + dnssec_keyusage_t *keyusage = dnssec_keyusage_new(); + dnssec_keyusage_load(keyusage, path); + dnssec_keyusage_add(keyusage, rolling->id, ctx->zone->name); + dnssec_keyusage_save(keyusage, path); + dnssec_keyusage_free(keyusage); + free(path); + return dnssec_kasp_zone_save(ctx->kasp, ctx->zone); } @@ -179,7 +191,33 @@ static int exec_remove_old_key(dnssec_event_ctx_t *ctx) return DNSSEC_EINVAL; } + char *path; + if (asprintf(&path, "%s/keyusage", ctx->kasp->functions->base_path(ctx->kasp->ctx)) == -1){ + return DNSSEC_ENOMEM; + } + + dnssec_keyusage_t *keyusage = dnssec_keyusage_new(); + dnssec_keyusage_load(keyusage, path); + dnssec_keyusage_remove(keyusage, retired->id, ctx->zone->name); + dnssec_keyusage_save(keyusage, path); + retired->timing.remove = ctx->now; + dnssec_list_foreach(item, ctx->zone->keys) { + dnssec_kasp_key_t *key = dnssec_item_get(item); + if (key->id == retired->id) { + dnssec_list_remove(item); + } + } + + if (dnssec_keyusage_is_used(keyusage, retired->id)) { + dnssec_keyusage_free(keyusage); + free(path); + return dnssec_kasp_zone_save(ctx->kasp, ctx->zone); + } + dnssec_keyusage_free(keyusage); + free(path); + + dnssec_keystore_remove_key(ctx->keystore, retired->id); return dnssec_kasp_zone_save(ctx->kasp, ctx->zone); } diff --git a/src/dnssec/lib/keyusage/keyusage.c b/src/dnssec/lib/keyusage/keyusage.c new file mode 100644 index 0000000000000000000000000000000000000000..dc4cfe748489fc49501ec0c91d28f03ddf1d8789 --- /dev/null +++ b/src/dnssec/lib/keyusage/keyusage.c @@ -0,0 +1,317 @@ +/* Copyright (C) 2015 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include "dnssec/keyusage.h" +#include "error.h" +#include "string.h" +#include "dnssec.h" +#include <assert.h> +#include "error.h" +#include "kasp/dir/json.h" +#include "kasp/internal.h" +#include "shared.h" +#include <string.h> + +int dnssec_keyusage_add(dnssec_keyusage_t *keyusage, const char *keytag, char *zone) { + + char *lzone = strdup(zone); + dnssec_list_foreach(item, keyusage) { + + record_keyusage_t *record = dnssec_item_get(item); + if (strcmp(record->keytag, keytag) == 0) { + + if(!dnssec_list_contains(record->zones, lzone)) { + dnssec_list_append(record->zones, lzone); + } + + return DNSSEC_EOK; + } + } + + record_keyusage_t *record = malloc(sizeof(*record)); + record->keytag = strdup(keytag); + record->zones = dnssec_list_new(); + + dnssec_list_append(record->zones, lzone); + dnssec_list_append(keyusage, record); + + return DNSSEC_EOK; +} + +int dnssec_keyusage_remove(dnssec_keyusage_t *keyusage, const char *keytag, char *zone) { + + dnssec_list_foreach(item, keyusage) { + record_keyusage_t *record = dnssec_item_get(item); + + if (strcmp(record->keytag, keytag) == 0) { + dnssec_list_foreach(item2, record->zones) { + + char *tmp = dnssec_item_get(item2); + + if(strcmp(tmp,zone)==0) { + + free(tmp); + dnssec_list_remove(item2); + + if (dnssec_list_is_empty(record->zones)) { + free(record->keytag); + dnssec_list_free(record->zones); + free(record); + dnssec_list_remove(item); + } + + return DNSSEC_EOK; + } + } + + } + } + return DNSSEC_ENOENT; +} + +bool dnssec_keyusage_is_used(dnssec_keyusage_t *keyusage, const char *keytag) { + + if (dnssec_list_is_empty(keyusage)) { + return false; + } + + dnssec_list_foreach(item, keyusage) { + record_keyusage_t *record = dnssec_item_get(item); + + if(record == NULL || record->keytag == NULL) { + return false; + } + + if (strcmp(record->keytag, keytag) == 0) { + return !dnssec_list_is_empty(record->zones); + } + } + return false; +} + +static int import_keyusage(dnssec_keyusage_t *keyusage, const json_t *json) +{ + json_t *jrecord = NULL; + int a, b; + if (!json_is_array(json)) { + if (json_is_null(json)) { + return DNSSEC_EOK; + } else { + return DNSSEC_CONFIG_MALFORMED; + } + } + json_array_foreach(json, a, jrecord) { + json_t *jkeytag = NULL; + jkeytag = json_object_get(jrecord, "keytag"); + + record_keyusage_t *record = malloc(sizeof(*record)); + if (record == NULL) { + return DNSSEC_ENOMEM; + } + + int r = decode_string(jkeytag, &record->keytag); + if (r != DNSSEC_EOK) { + return r; + } + + json_t *jzones = NULL, *jzone = NULL; + jzones = json_object_get(jrecord, "zones"); + record->zones = dnssec_list_new(); + + json_array_foreach(jzones, b, jzone) { + char *zone; + int r = decode_string(jzone, &zone); + if (r != DNSSEC_EOK) { + return r; + } + dnssec_list_append(record->zones, zone); + } + dnssec_list_append(keyusage, record); + } + + return DNSSEC_EOK; +} + +static int export_keyusage(dnssec_keyusage_t *keyusage, json_t **json) +{ + assert(keyusage); + assert(json); + record_keyusage_t *record; + int r; + + if (dnssec_list_is_empty(keyusage)) { + return DNSSEC_EOK; + } + + json_t *jrecords = json_array(); + if (!jrecords) { + return DNSSEC_ENOMEM; + } + + json_t *jkeytag = NULL; + json_t *jzone = NULL; + json_t *jzones = NULL; + + dnssec_list_foreach(item, keyusage) { + record = dnssec_item_get(item); + + json_t *jzones = json_array(); + if (!jzones) { + json_array_clear(jrecords); + return DNSSEC_ENOMEM; + } + + r = encode_string(&record->keytag, &jkeytag); + if (r != DNSSEC_EOK) { + json_decref(jkeytag); + goto error; + } + dnssec_list_foreach(item, record->zones) { + + const char *zone = dnssec_item_get(item); + r = encode_string(&zone, &jzone); + + if (r != DNSSEC_EOK) { + json_decref(jzone); + goto error; + } + + if (json_array_append_new(jzones, jzone)) { + r = DNSSEC_ENOMEM; + goto error; + } + } + json_t *jrecord = json_object(); + if (!jrecord) { + r = DNSSEC_ENOMEM; + goto error; + } + + if (json_object_set(jrecord, "keytag",jkeytag)) { + json_object_clear(jrecord); + r = DNSSEC_ENOMEM; + goto error; + } + json_decref(jkeytag); + + if (json_object_set(jrecord, "zones",jzones)) { + json_object_clear(jrecord); + r = DNSSEC_ENOMEM; + goto error; + } + json_decref(jzones); + + if (json_array_append_new(jrecords, jrecord)) { + json_object_clear(jrecord); + r = DNSSEC_ENOMEM; + goto error; + } + } + *json = jrecords; + + return DNSSEC_EOK; +error: + json_array_clear(jzones); + json_array_clear(jrecords); + return r; + +} + +int dnssec_keyusage_load(dnssec_keyusage_t *keyusage, const char *filename) +{ + assert(keyusage); + assert(filename); + + dnssec_list_clear(keyusage); + + _cleanup_fclose_ FILE *file = fopen(filename, "r"); + if (!file) { + return DNSSEC_NOT_FOUND; + } + + json_error_t error = { 0 }; + _json_cleanup_ json_t *json = json_loadf(file, JSON_LOAD_OPTIONS, &error); + if (!json) { + if (error.position != 1) { + return DNSSEC_CONFIG_MALFORMED; + } else { + return DNSSEC_EOK; + } + } + + return import_keyusage(keyusage, json); +} + +int dnssec_keyusage_save(dnssec_keyusage_t *keyusage, const char *filename) +{ + assert(keyusage); + assert(filename); + + _json_cleanup_ json_t *json = NULL; + int r = export_keyusage(keyusage, &json); + if (r != DNSSEC_EOK) { + return r; + } + + _cleanup_fclose_ FILE *file = fopen(filename, "w"); + if (!file) { + return DNSSEC_NOT_FOUND; + } + + if (json) + { + r = json_dumpf(json, file, JSON_DUMP_OPTIONS); + if (r != DNSSEC_EOK) { + return r; + } + } + + fputc('\n', file); + return DNSSEC_EOK; +} + +/* -- public API ----------------------------------------------------------- */ + +_public_ +dnssec_keyusage_t *dnssec_keyusage_new() +{ + dnssec_keyusage_t *keyusage = dnssec_list_new(); + return keyusage; +} + +_public_ +void dnssec_keyusage_free(dnssec_keyusage_t *keyusage) +{ + if (keyusage == NULL) { + return; + } + + if (!dnssec_list_is_empty(keyusage)) { + dnssec_list_foreach(item, keyusage) { + record_keyusage_t *record = dnssec_item_get(item); + + free(record->keytag); + dnssec_list_foreach(item2, record->zones) { + char *zone = dnssec_item_get(item2); + free(zone); + } + dnssec_list_free(record->zones); + free(record); + } + } + dnssec_list_free(keyusage); + keyusage = NULL; +} diff --git a/src/dnssec/tests/.gitignore b/src/dnssec/tests/.gitignore index bdbb8facbb25f061778b53b874d9b526967700ae..5075e739c2e7c3b1b4b0a6d28519d537cdcf2f71 100644 --- a/src/dnssec/tests/.gitignore +++ b/src/dnssec/tests/.gitignore @@ -9,6 +9,7 @@ /event_nsec3_resalt /kasp_dir_escape /kasp_dir_file +/keyusage /kasp_policy /kasp_store /key diff --git a/src/dnssec/tests/Makefile.am b/src/dnssec/tests/Makefile.am index 2821b55c3f8ca692bf6a67bfe28434ff7f8cfca2..6f3fed12f8715ce48607089dc869b7eec0d84cfc 100644 --- a/src/dnssec/tests/Makefile.am +++ b/src/dnssec/tests/Makefile.am @@ -34,6 +34,7 @@ check_PROGRAMS = \ keystore_pkcs8 \ keystore_pkcs8_dir \ keytag \ + keyusage \ list \ nsec_bitmap \ nsec_hash \ diff --git a/src/dnssec/tests/keyusage.c b/src/dnssec/tests/keyusage.c new file mode 100644 index 0000000000000000000000000000000000000000..31f21140743ac6c2e3b879e90b8f2e77e55e0aae --- /dev/null +++ b/src/dnssec/tests/keyusage.c @@ -0,0 +1,108 @@ +/* Copyright (C) 2016 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz> + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see <http://www.gnu.org/licenses/>. + */ + +#include <tap/basic.h> +#include <string.h> +#include <stdio.h> + +#include "dnssec/keyusage.h" +#include "dnssec/error.h" +#include "dnssec.h" + +static void test_keyusage_empty(void) +{ + diag("%s", __func__); + + dnssec_keyusage_t *k = dnssec_keyusage_new(); + + remove("/tmp/keyusage.json"); + + ok(dnssec_keyusage_load(k, "/tmp/keyusage.json")== DNSSEC_NOT_FOUND, "kayusage_load empty, no keyusage"); + + ok(dnssec_list_is_empty(k), "List of records is empty."); + + ok(dnssec_keyusage_save(k, "/tmp/keyusage.json") == DNSSEC_EOK , "kayusage_save empty"); + + ok(dnssec_keyusage_load(k, "/tmp/keyusage.json")== DNSSEC_EOK , "kayusage_load empty"); + + ok(dnssec_list_is_empty(k), "List of records is empty."); + + dnssec_keyusage_free(k); +} + +static void test_keyusage_basic(void) +{ + diag("%s", __func__); + + dnssec_keyusage_t *k = NULL; + + k = dnssec_keyusage_new(); + ok(dnssec_list_is_empty(k), "List of records is empty."); + + dnssec_keyusage_add(k, "prvni", "zona"); + + ok(!dnssec_list_is_empty(k), "List of records is not empty."); + // Check added key being used + ok(dnssec_keyusage_is_used(k, "prvni"), "Key is used."); + // Remove added key and check empty record + dnssec_keyusage_remove(k, "prvni", "zona"); + ok(dnssec_list_is_empty(k), "List of records is empty."); + ok(!dnssec_keyusage_is_used(k, "prvni"), "Key is not used."); + // Check if key is used when one of zones is deleted + dnssec_keyusage_add(k, "prvni", "zona"); + dnssec_keyusage_add(k, "prvni", "zona2"); + dnssec_keyusage_remove(k, "prvni", "zona"); + ok(dnssec_keyusage_is_used(k, "prvni"), "Key is used."); + + // Check if key is unused after deleting both zones + dnssec_keyusage_remove(k, "prvni", "zona2"); + ok(!dnssec_keyusage_is_used(k, "prvni"), "Key is not used."); + + dnssec_keyusage_free(k); +} + +static void test_keyusage_file(void) +{ + dnssec_keyusage_t *k = NULL; + k = dnssec_keyusage_new(); + dnssec_keyusage_add(k, "prvni", "zona"); + dnssec_keyusage_add(k, "prvni", "zona2"); + dnssec_keyusage_add(k, "druhy", "zona"); + + ok(dnssec_keyusage_save(k, "/tmp/keyusage.json") == DNSSEC_EOK , "kayusage_save"); + + dnssec_keyusage_free(k); + k = dnssec_keyusage_new(); + + ok(!dnssec_keyusage_is_used(k, "prvni"), "Key is not used - freed succesfully."); + ok(!dnssec_keyusage_is_used(k, "druhy"), "Key is not used - freed succesfully."); + + ok(dnssec_keyusage_load(k, "/tmp/keyusage.json")== DNSSEC_EOK , "kayusage_load"); + + ok(dnssec_keyusage_is_used(k, "prvni"), "Key is used - loaded succesfully."); + ok(dnssec_keyusage_is_used(k, "druhy"), "Key is used - loaded succesfully."); + + dnssec_keyusage_free(k); +} + +int main(int argc, char *argv[]) +{ + plan_lazy(); + test_keyusage_empty(); + test_keyusage_basic(); + test_keyusage_file(); + return 0; +}