diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 00:53:35 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-06 00:53:35 +0000 |
commit | 69c6a41ffb878ef98c9378ed4b1634a404cfaa7f (patch) | |
tree | b2a4f704565d62fbb129ab9dc3b35977c50e6e7f /src/utils/keymgr | |
parent | Initial commit. (diff) | |
download | knot-upstream.tar.xz knot-upstream.zip |
Adding upstream version 2.7.6.upstream/2.7.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/utils/keymgr')
-rw-r--r-- | src/utils/keymgr/bind_privkey.c | 352 | ||||
-rw-r--r-- | src/utils/keymgr/bind_privkey.h | 72 | ||||
-rw-r--r-- | src/utils/keymgr/functions.c | 879 | ||||
-rw-r--r-- | src/utils/keymgr/functions.h | 45 | ||||
-rw-r--r-- | src/utils/keymgr/main.c | 373 |
5 files changed, 1721 insertions, 0 deletions
diff --git a/src/utils/keymgr/bind_privkey.c b/src/utils/keymgr/bind_privkey.c new file mode 100644 index 0000000..7e140e4 --- /dev/null +++ b/src/utils/keymgr/bind_privkey.c @@ -0,0 +1,352 @@ +/* Copyright (C) 2018 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 <string.h> + +#include "contrib/ctype.h" +#include "contrib/strtonum.h" +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/shared/pem.h" +#include "libdnssec/shared/shared.h" +#include "utils/keymgr/bind_privkey.h" + +/* -- private key params conversion ---------------------------------------- */ + +/*! + * Private key attribute conversion. + */ +typedef struct param_t { + char *name; + size_t offset; + int (*parse_cb)(char *string, void *data); + void (*free_cb)(void *data); +} param_t; + +static int parse_algorithm(char *string, void *_algorithm); +static int parse_binary(char *string, void *_binary); +static int parse_time(char *string, void *_time); + +static void binary_free(void *_binary) +{ + dnssec_binary_t *binary = _binary; + dnssec_binary_free(binary); +} + +/*! + * Know attributes in private key file. + */ +const param_t PRIVKEY_CONVERSION_TABLE[] = { + #define o(field) offsetof(bind_privkey_t, field) + { "Algorithm", o(algorithm), parse_algorithm, NULL }, + { "Modulus", o(modulus), parse_binary, binary_free }, + { "PublicExponent", o(public_exponent), parse_binary, binary_free }, + { "PrivateExponent", o(private_exponent), parse_binary, binary_free }, + { "Prime1", o(prime_one), parse_binary, binary_free }, + { "Prime2", o(prime_two), parse_binary, binary_free }, + { "Exponent1", o(exponent_one), parse_binary, binary_free }, + { "Exponent2", o(exponent_two), parse_binary, binary_free }, + { "Coefficient", o(coefficient), parse_binary, binary_free }, + { "PrivateKey", o(private_key), parse_binary, binary_free }, + { "Created", o(time_created), parse_time, NULL }, + { "Publish", o(time_publish), parse_time, NULL }, + { "Activate", o(time_activate), parse_time, NULL }, + { "Revoke", o(time_revoke), parse_time, NULL }, + { "Inactive", o(time_inactive), parse_time, NULL }, + { "Delete", o(time_delete), parse_time, NULL }, + { NULL } + #undef o +}; + +/* -- attribute parsing ---------------------------------------------------- */ + +/*! + * Parse key algorithm field. + * + * Example: 7 (NSEC3RSASHA1) + * + * Only the numeric value is decoded, the rest of the value is ignored. + */ +static int parse_algorithm(char *string, void *_algorithm) +{ + char *end = string; + while (*end != '\0' && !is_space(*end)) { + end += 1; + } + *end = '\0'; + + uint8_t *algorithm = _algorithm; + int r = str_to_u8(string, algorithm); + + return (r == KNOT_EOK ? DNSSEC_EOK : DNSSEC_INVALID_KEY_ALGORITHM); +} + +/*! + * Parse binary data encoded in Base64. + * + * Example: AQAB + */ +static int parse_binary(char *string, void *_binary) +{ + dnssec_binary_t base64 = { + .data = (uint8_t *)string, + .size = strlen(string) + }; + + dnssec_binary_t *binary = _binary; + return dnssec_binary_from_base64(&base64, binary); +} + +#define LEGACY_DATE_FORMAT "%Y%m%d%H%M%S" + +/*! + * Parse timestamp in a format in \ref LEGACY_DATE_FORMAT. + * + * Example: 20140415151855 + */ +static int parse_time(char *string, void *_time) +{ + struct tm tm = { 0 }; + + char *end = strptime(string, LEGACY_DATE_FORMAT, &tm); + if (end == NULL || *end != '\0') { + return DNSSEC_MALFORMED_DATA; + } + + time_t *time = _time; + *time = timegm(&tm); + + return DNSSEC_EOK; +} + +/* -- key parsing ---------------------------------------------------------- */ + +/*! + * Strip string value of left and right whitespaces. + * + * \param[in,out] value Start of the string. + * \param[in,out] length Length of the string. + * + */ +static void strip(char **value, size_t *length) +{ + // strip from left + while (*length > 0 && is_space(**value)) { + *value += 1; + *length -= 1; + } + // strip from right + while (*length > 0 && is_space((*value)[*length - 1])) { + *length -= 1; + } +} + +/*! + * Parse one line of the private key file. + */ +static int parse_line(bind_privkey_t *params, char *line, size_t length) +{ + assert(params); + assert(line); + + char *separator = memchr(line, ':', length); + if (!separator) { + return DNSSEC_MALFORMED_DATA; + } + + char *key = line; + size_t key_length = separator - key; + strip(&key, &key_length); + + char *value = separator + 1; + size_t value_length = (line + length) - value; + strip(&value, &value_length); + + if (key_length == 0 || value_length == 0) { + return DNSSEC_MALFORMED_DATA; + } + + key[key_length] = '\0'; + value[value_length] = '\0'; + + for (const param_t *p = PRIVKEY_CONVERSION_TABLE; p->name != NULL; p++) { + size_t name_length = strlen(p->name); + if (name_length != key_length) { + continue; + } + + if (strcasecmp(p->name, key) != 0) { + continue; + } + + return p->parse_cb(value, (void *)params + p->offset); + } + + // ignore unknown attributes + + return DNSSEC_EOK; +} + +int bind_privkey_parse(const char *filename, bind_privkey_t *params_ptr) +{ + _cleanup_fclose_ FILE *file = fopen(filename, "r"); + if (!file) { + return DNSSEC_NOT_FOUND; + } + + bind_privkey_t params = { 0 }; + + _cleanup_free_ char *line = NULL; + size_t size = 0; + ssize_t read = 0; + while ((read = getline(&line, &size, file)) != -1) { + int r = parse_line(¶ms, line, read); + if (r != DNSSEC_EOK) { + bind_privkey_free(¶ms); + return r; + } + } + + *params_ptr = params; + + return DNSSEC_EOK; +} + +/* -- freeing -------------------------------------------------------------- */ + +/*! + * Free private key parameters. + */ +void bind_privkey_free(bind_privkey_t *params) +{ + if (!params) { + return; + } + + for (const param_t *p = PRIVKEY_CONVERSION_TABLE; p->name != NULL; p++) { + if (p->free_cb) { + p->free_cb((void *)params + p->offset); + } + } + + clear_struct(params); +} + +/* -- export to PEM -------------------------------------------------------- */ + +static int rsa_params_to_pem(const bind_privkey_t *params, dnssec_binary_t *pem) +{ + _cleanup_x509_privkey_ gnutls_x509_privkey_t key = NULL; + int result = gnutls_x509_privkey_init(&key); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_ENOMEM; + } + + gnutls_datum_t m = binary_to_datum(¶ms->modulus); + gnutls_datum_t e = binary_to_datum(¶ms->public_exponent); + gnutls_datum_t d = binary_to_datum(¶ms->private_exponent); + gnutls_datum_t p = binary_to_datum(¶ms->prime_one); + gnutls_datum_t q = binary_to_datum(¶ms->prime_two); + gnutls_datum_t u = binary_to_datum(¶ms->coefficient); + + result = gnutls_x509_privkey_import_rsa_raw(key, &m, &e, &d, &p, &q, &u); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_IMPORT_ERROR; + } + + return pem_from_x509(key, pem); +} + +/*! + * \see lib/key/convert.h + */ +static gnutls_ecc_curve_t choose_ecdsa_curve(size_t pubkey_size) +{ + switch (pubkey_size) { + case 64: return GNUTLS_ECC_CURVE_SECP256R1; + case 96: return GNUTLS_ECC_CURVE_SECP384R1; + default: return GNUTLS_ECC_CURVE_INVALID; + } +} + +static void ecdsa_extract_public_params(dnssec_key_t *key, gnutls_ecc_curve_t *curve, + gnutls_datum_t *x, gnutls_datum_t *y) +{ + dnssec_binary_t pubkey = { 0 }; + dnssec_key_get_pubkey(key, &pubkey); + + *curve = choose_ecdsa_curve(pubkey.size); + + size_t param_size = pubkey.size / 2; + x->data = pubkey.data; + x->size = param_size; + y->data = pubkey.data + param_size; + y->size = param_size; +} + +static int ecdsa_params_to_pem(dnssec_key_t *dnskey, const bind_privkey_t *params, + dnssec_binary_t *pem) +{ + _cleanup_x509_privkey_ gnutls_x509_privkey_t key = NULL; + int result = gnutls_x509_privkey_init(&key); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_ENOMEM; + } + + gnutls_ecc_curve_t curve = 0; + gnutls_datum_t x = { 0 }; + gnutls_datum_t y = { 0 }; + ecdsa_extract_public_params(dnskey, &curve, &x, &y); + + gnutls_datum_t k = binary_to_datum(¶ms->private_key); + + result = gnutls_x509_privkey_import_ecc_raw(key, curve, &x, &y, &k); + if (result != DNSSEC_EOK) { + return DNSSEC_KEY_IMPORT_ERROR; + } + + gnutls_x509_privkey_fix(key); + + return pem_from_x509(key, pem); +} + +int bind_privkey_to_pem(dnssec_key_t *key, bind_privkey_t *params, dnssec_binary_t *pem) +{ + dnssec_key_algorithm_t algorithm = dnssec_key_get_algorithm(key); + switch (algorithm) { + case DNSSEC_KEY_ALGORITHM_RSA_SHA1: + case DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3: + case DNSSEC_KEY_ALGORITHM_RSA_SHA256: + case DNSSEC_KEY_ALGORITHM_RSA_SHA512: + return rsa_params_to_pem(params, pem); + case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: + case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: + return ecdsa_params_to_pem(key, params, pem); + default: + return DNSSEC_INVALID_KEY_ALGORITHM; + } +} + +void bind_privkey_to_timing(bind_privkey_t *params, knot_kasp_key_timing_t *timing) +{ + // unsupported: time_created, time_revoke + + timing->publish = (knot_time_t)params->time_publish; + timing->ready = 0; + timing->active = (knot_time_t)params->time_activate; + timing->retire = (knot_time_t)params->time_inactive; + timing->remove = (knot_time_t)params->time_delete; +} diff --git a/src/utils/keymgr/bind_privkey.h b/src/utils/keymgr/bind_privkey.h new file mode 100644 index 0000000..6c58e1d --- /dev/null +++ b/src/utils/keymgr/bind_privkey.h @@ -0,0 +1,72 @@ +/* Copyright (C) 2018 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 <stdint.h> +#include <time.h> + +#include "libdnssec/binary.h" +#include "knot/dnssec/kasp/policy.h" + +/*! + * Legacy private key parameters. + */ +typedef struct { + // key information + uint8_t algorithm; + + // RSA + dnssec_binary_t modulus; + dnssec_binary_t public_exponent; + dnssec_binary_t private_exponent; + dnssec_binary_t prime_one; + dnssec_binary_t prime_two; + dnssec_binary_t exponent_one; + dnssec_binary_t exponent_two; + dnssec_binary_t coefficient; + + // ECDSA + dnssec_binary_t private_key; + + // key lifetime + time_t time_created; + time_t time_publish; + time_t time_activate; + time_t time_revoke; + time_t time_inactive; + time_t time_delete; +} bind_privkey_t; + +/*! + * Extract parameters from legacy private key file. + */ +int bind_privkey_parse(const char *filename, bind_privkey_t *params); + +/*! + * Free private key parameters. + */ +void bind_privkey_free(bind_privkey_t *params); + +/*! + * Generate PEM from pub&priv key. + */ +int bind_privkey_to_pem(dnssec_key_t *key, bind_privkey_t *params, dnssec_binary_t *pem); + +/*! + * Extract timing info. + */ +void bind_privkey_to_timing(bind_privkey_t *params, knot_kasp_key_timing_t *timing); diff --git a/src/utils/keymgr/functions.c b/src/utils/keymgr/functions.c new file mode 100644 index 0000000..3672928 --- /dev/null +++ b/src/utils/keymgr/functions.c @@ -0,0 +1,879 @@ +/* Copyright (C) 2018 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 <limits.h> +#include <string.h> +#include <strings.h> +#include <time.h> +#include <fcntl.h> + +#include "utils/keymgr/functions.h" +#include "utils/keymgr/bind_privkey.h" +#include "contrib/base64.h" +#include "contrib/ctype.h" +#include "contrib/tolower.h" +#include "contrib/wire_ctx.h" +#include "libdnssec/error.h" +#include "libdnssec/shared/hex.h" +#include "libdnssec/shared/shared.h" +#include "knot/dnssec/kasp/policy.h" +#include "knot/dnssec/zone-keys.h" +#include "libzscanner/scanner.h" + +static bool is_timestamp(char *arg, knot_kasp_key_timing_t *timing) +{ + knot_time_t *dst = NULL; + + if (strncasecmp(arg, "created=", 8) == 0) { + dst = &timing->created; + } else if (strncasecmp(arg, "publish=", 8) == 0) { + dst = &timing->publish; + } else if (strncasecmp(arg, "ready=", 6) == 0) { + dst = &timing->ready; + } else if (strncasecmp(arg, "active=", 7) == 0) { + dst = &timing->active; + } else if (strncasecmp(arg, "retire=", 7) == 0) { + dst = &timing->retire; + } else if (strncasecmp(arg, "remove=", 7) == 0) { + dst = &timing->remove; + } else if (strncasecmp(arg, "pre_active=", 11) == 0) { + dst = &timing->pre_active; + } else if (strncasecmp(arg, "post_active=", 12) == 0) { + dst = &timing->post_active; + } else if (strncasecmp(arg, "retire_active=", 14) == 0) { + dst = &timing->retire_active; + } else { + return false; + } + + knot_time_t stamp; + int ret = knot_time_parse("YMDhms|'now'+-#u|'t'+-#u|+-#u|'t'+-#|+-#|#", + strchr(arg, '=') + 1, &stamp); + if (ret < 0) { + printf("Invalid timestamp: %s\n", arg); + return true; + } + + *dst = stamp; + + return true; +} + +static bool str2bool(const char *s) +{ + switch (knot_tolower(s[0])) { + case '1': + case 'y': + case 't': + return true; + default: + return false; + } +} + +static void bitmap_set(kdnssec_generate_flags_t *bitmap, int flag, bool onoff) +{ + if (onoff) { + *bitmap |= flag; + } else { + *bitmap &= ~flag; + } +} + +static bool genkeyargs(int argc, char *argv[], bool just_timing, + kdnssec_generate_flags_t *flags, dnssec_key_algorithm_t *algorithm, + uint16_t *keysize, knot_kasp_key_timing_t *timing, + const char **addtopolicy) +{ + // generate algorithms field + char *algnames[256] = { 0 }; + algnames[DNSSEC_KEY_ALGORITHM_RSA_SHA1] = "rsasha1"; + algnames[DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3] = "rsasha1nsec3sha1"; + algnames[DNSSEC_KEY_ALGORITHM_RSA_SHA256] = "rsasha256"; + algnames[DNSSEC_KEY_ALGORITHM_RSA_SHA512] = "rsasha512"; + algnames[DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256] = "ecdsap256sha256"; + algnames[DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384] = "ecdsap384sha384"; + algnames[DNSSEC_KEY_ALGORITHM_ED25519] = "ed25519"; + + // parse args + for (int i = 0; i < argc; i++) { + if (!just_timing && strncasecmp(argv[i], "algorithm=", 10) == 0) { + if (is_digit(argv[i][10]) && atol(argv[i] + 10) < 256) { + *algorithm = atol(argv[i] + 10); + continue; + } + int al; + for (al = 0; al < 256; al++) { + if (algnames[al] != NULL && + strcasecmp(argv[i] + 10, algnames[al]) == 0) { + *algorithm = al; + break; + } + } + if (al == 256) { + printf("Unknown algorithm: %s\n", argv[i] + 10); + return false; + } + } else if (strncasecmp(argv[i], "ksk=", 4) == 0) { + bitmap_set(flags, DNSKEY_GENERATE_KSK, str2bool(argv[i] + 4)); + } else if (strncasecmp(argv[i], "zsk=", 4) == 0) { + bitmap_set(flags, DNSKEY_GENERATE_ZSK, str2bool(argv[i] + 4)); + } else if (!just_timing && strncasecmp(argv[i], "sep=", 4) == 0) { + bitmap_set(flags, DNSKEY_GENERATE_SEP_SPEC, true); + bitmap_set(flags, DNSKEY_GENERATE_SEP_ON, str2bool(argv[i] + 4)); + } else if (!just_timing && strncasecmp(argv[i], "size=", 5) == 0) { + *keysize = atol(argv[i] + 5); + } else if (!just_timing && strncasecmp(argv[i], "addtopolicy=", 12) == 0) { + *addtopolicy = argv[i] + 12; + } else if (!is_timestamp(argv[i], timing)) { + printf("Invalid parameter: %s\n", argv[i]); + return false; + } + } + + return true; +} + +static bool _check_lower(knot_time_t a, knot_time_t b, + const char *a_name, const char *b_name) +{ + if (knot_time_cmp(a, b) > 0) { + fprintf(stderr, "Semantic error: expected '%s' before '%s'.\n", a_name, b_name); + return false; + } + return true; +} + +#define check_lower(t, a, b) if (!_check_lower(t->a, t->b, #a, #b)) return KNOT_ESEMCHECK + +static int check_timers(const knot_kasp_key_timing_t *t) +{ + check_lower(t, publish, active); + check_lower(t, active, retire_active); + check_lower(t, active, retire); + check_lower(t, active, post_active); + if (t->post_active == 0) { + check_lower(t, retire, remove); + } + return KNOT_EOK; +} + +#undef check_lower + +// modifies ctx->policy options, so don't do anything afterwards ! +int keymgr_generate_key(kdnssec_ctx_t *ctx, int argc, char *argv[]) +{ + knot_time_t now = knot_time(), infty = 0; + knot_kasp_key_timing_t gen_timing = { now, infty, now, infty, now, infty, infty, infty, infty }; + kdnssec_generate_flags_t flags = 0; + uint16_t keysize = 0; + const char *addtopolicy = NULL; + if (!genkeyargs(argc, argv, false, &flags, &ctx->policy->algorithm, + &keysize, &gen_timing, &addtopolicy)) { + return KNOT_EINVAL; + } + + int ret = check_timers(&gen_timing); + if (ret != KNOT_EOK) { + return ret; + } + + if ((flags & DNSKEY_GENERATE_KSK) && gen_timing.ready == infty) { + gen_timing.ready = gen_timing.active; + } + + if (keysize > 0) { + if ((flags & DNSKEY_GENERATE_KSK)) { + ctx->policy->ksk_size = keysize; + } else { + ctx->policy->zsk_size = keysize; + } + } + + for (size_t i = 0; i < ctx->zone->num_keys; i++) { + knot_kasp_key_t *kasp_key = &ctx->zone->keys[i]; + if ((kasp_key->is_ksk && (flags & DNSKEY_GENERATE_KSK)) && + dnssec_key_get_algorithm(kasp_key->key) != ctx->policy->algorithm) { + printf("warning: creating key with different algorithm than " + "configured in the policy\n"); + break; + } + } + + knot_kasp_key_t *key = NULL; + ret = kdnssec_generate_key(ctx, flags, &key); + if (ret != KNOT_EOK) { + return ret; + } + + key->timing = gen_timing; + + if (addtopolicy != NULL) { + char *last_policy_last = NULL; + + knot_dname_t *unused = NULL; + ret = kasp_db_get_policy_last(*ctx->kasp_db, addtopolicy, &unused, + &last_policy_last); + knot_dname_free(unused, NULL); + if (ret != KNOT_EOK && ret != KNOT_ENOENT) { + return ret; + } + + ret = kasp_db_set_policy_last(*ctx->kasp_db, addtopolicy, last_policy_last, + ctx->zone->dname, key->id); + free(last_policy_last); + if (ret != KNOT_EOK) { + return ret; + } + } + + ret = kdnssec_ctx_commit(ctx); + + if (ret == KNOT_EOK) { + printf("%s\n", key->id); + } + + return ret; +} + +static void parse_record(zs_scanner_t *scanner) +{ + dnssec_key_t *key = scanner->process.data; + + if (dnssec_key_get_dname(key) != NULL || + scanner->r_type != KNOT_RRTYPE_DNSKEY) { + scanner->state = ZS_STATE_STOP; + return; + } + + dnssec_binary_t rdata = { + .data = scanner->r_data, + .size = scanner->r_data_length + }; + dnssec_key_set_dname(key, scanner->dname); + dnssec_key_set_rdata(key, &rdata); +} + +int bind_pubkey_parse(const char *filename, dnssec_key_t **key_ptr) +{ + dnssec_key_t *key = NULL; + int result = dnssec_key_new(&key); + if (result != DNSSEC_EOK) { + return KNOT_ENOMEM; + } + + uint16_t cls = KNOT_CLASS_IN; + uint32_t ttl = 0; + zs_scanner_t *scanner = malloc(sizeof(zs_scanner_t)); + if (scanner == NULL) { + dnssec_key_free(key); + return KNOT_ENOMEM; + } + + if (zs_init(scanner, ".", cls, ttl) != 0 || + zs_set_input_file(scanner, filename) != 0 || + zs_set_processing(scanner, parse_record, NULL, key) != 0 || + zs_parse_all(scanner) != 0) { + zs_deinit(scanner); + free(scanner); + dnssec_key_free(key); + return KNOT_ENOENT; + } + zs_deinit(scanner); + free(scanner); + + if (dnssec_key_get_dname(key) == NULL) { + dnssec_key_free(key); + return KNOT_INVALID_PUBLIC_KEY; + } + + *key_ptr = key; + return KNOT_EOK; +} + +static char *genname(const char *orig, const char *wantsuff, const char *altsuff) +{ + char *res; + if (orig == NULL || wantsuff == NULL || altsuff == NULL || + (res = malloc(strlen(orig) + strlen(wantsuff) + 1)) == NULL) { + return NULL; + } + strcpy(res, orig); + char *dot = strrchr(res, '.'); + if (dot != NULL && strcmp(dot, wantsuff) == 0) { + ; + } else if (dot != NULL && strcmp(dot, altsuff) == 0) { + strcpy(dot, wantsuff); + } else { + strcat(res, wantsuff); + } + return res; +} + +int keymgr_import_bind(kdnssec_ctx_t *ctx, const char *import_file, bool pub_only) +{ + if (ctx == NULL || import_file == NULL) { + return KNOT_EINVAL; + } + + knot_kasp_key_timing_t timing = { 0 }; + dnssec_key_t *key = NULL; + char *keyid = NULL; + + char *pubname = genname(import_file, ".key", ".private"); + if (pubname == NULL) { + return KNOT_EINVAL; + } + + int ret = bind_pubkey_parse(pubname, &key); + free(pubname); + if (ret != KNOT_EOK) { + goto fail; + } + + if (!pub_only) { + bind_privkey_t bpriv = { 0 }; + + char *privname = genname(import_file, ".private", ".key"); + if (privname == NULL) { + goto fail; + } + + ret = bind_privkey_parse(privname, &bpriv); + free(privname); + if (ret != DNSSEC_EOK) { + goto fail; + } + + dnssec_binary_t pem = { 0 }; + ret = bind_privkey_to_pem(key, &bpriv, &pem); + if (ret != DNSSEC_EOK) { + bind_privkey_free(&bpriv); + goto fail; + } + + bind_privkey_to_timing(&bpriv, &timing); // time created remains always zero + + bind_privkey_free(&bpriv); + + ret = dnssec_keystore_import(ctx->keystore, &pem, &keyid); + dnssec_binary_free(&pem); + if (ret != DNSSEC_EOK) { + goto fail; + } + } else { + timing.publish = ctx->now; + + ret = dnssec_key_get_keyid(key, &keyid); + if (ret != DNSSEC_EOK) { + goto fail; + } + } + + // allocate kasp key + knot_kasp_key_t *kkey = calloc(1, sizeof(*kkey)); + if (!kkey) { + ret = KNOT_ENOMEM; + goto fail; + } + + kkey->id = keyid; + kkey->key = key; + kkey->timing = timing; + kkey->is_pub_only = pub_only; + kkey->is_ksk = (dnssec_key_get_flags(kkey->key) == DNSKEY_FLAGS_KSK); + kkey->is_zsk = !kkey->is_ksk; + + // append to zone + ret = kasp_zone_append(ctx->zone, kkey); + free(kkey); + if (ret != KNOT_EOK) { + goto fail; + } + ret = kdnssec_ctx_commit(ctx); + if (ret == KNOT_EOK) { + printf("%s\n", keyid); + return KNOT_EOK; + } +fail: + dnssec_key_free(key); + free(keyid); + return knot_error_from_libdnssec(ret); +} + +static int import_key(kdnssec_ctx_t *ctx, unsigned backend, const char *param, + int argc, char *argv[]) +{ + if (ctx == NULL || param == NULL) { + return KNOT_EINVAL; + } + + // parse params + knot_time_t now = knot_time(); + knot_kasp_key_timing_t timing = { .publish = now, .active = now }; + kdnssec_generate_flags_t flags = 0; + uint16_t keysize = 0; + if (!genkeyargs(argc, argv, false, &flags, &ctx->policy->algorithm, + &keysize, &timing, NULL)) { + return KNOT_EINVAL; + } + + int ret = check_timers(&timing); + if (ret != KNOT_EOK) { + return ret; + } + + normalize_generate_flags(&flags); + + dnssec_key_t *key = NULL; + char *keyid = NULL; + + if (backend == KEYSTORE_BACKEND_PEM) { + // open file + int fd = open(param, O_RDONLY, 0); + if (fd == -1) { + return knot_map_errno(); + } + + // determine size + off_t fsize = lseek(fd, 0, SEEK_END); + if (fsize == -1) { + close(fd); + return knot_map_errno(); + } + if (lseek(fd, 0, SEEK_SET) == -1) { + close(fd); + return knot_map_errno(); + } + + // alloc memory + dnssec_binary_t pem = { 0 }; + ret = dnssec_binary_alloc(&pem, fsize); + if (ret != DNSSEC_EOK) { + close(fd); + goto fail; + } + + // read pem + ssize_t read_count = read(fd, pem.data, pem.size); + close(fd); + if (read_count == -1) { + dnssec_binary_free(&pem); + ret = knot_map_errno(); + goto fail; + } + + // put pem to kesytore + ret = dnssec_keystore_import(ctx->keystore, &pem, &keyid); + dnssec_binary_free(&pem); + if (ret != DNSSEC_EOK) { + goto fail; + } + } else { + assert(backend == KEYSTORE_BACKEND_PKCS11); + keyid = strdup(param); + } + + // create dnssec key + ret = dnssec_key_new(&key); + if (ret != DNSSEC_EOK) { + goto fail; + } + ret = dnssec_key_set_dname(key, ctx->zone->dname); + if (ret != DNSSEC_EOK) { + goto fail; + } + dnssec_key_set_flags(key, dnskey_flags(flags & DNSKEY_GENERATE_SEP_ON)); + dnssec_key_set_algorithm(key, ctx->policy->algorithm); + + // fill key structure from keystore (incl. pubkey from privkey computation) + ret = dnssec_key_import_keystore(key, ctx->keystore, keyid); + if (ret != DNSSEC_EOK) { + goto fail; + } + + // allocate kasp key + knot_kasp_key_t *kkey = calloc(1, sizeof(*kkey)); + if (kkey == NULL) { + ret = KNOT_ENOMEM; + goto fail; + } + kkey->id = keyid; + kkey->key = key; + kkey->timing = timing; + kkey->is_ksk = (flags & DNSKEY_GENERATE_KSK); + kkey->is_zsk = (flags & DNSKEY_GENERATE_ZSK); + + // append to zone + ret = kasp_zone_append(ctx->zone, kkey); + free(kkey); + if (ret != KNOT_EOK) { + goto fail; + } + ret = kdnssec_ctx_commit(ctx); + if (ret == KNOT_EOK) { + printf("%s\n", keyid); + return KNOT_EOK; + } +fail: + dnssec_key_free(key); + free(keyid); + return knot_error_from_libdnssec(ret); +} + +int keymgr_import_pem(kdnssec_ctx_t *ctx, const char *import_file, int argc, char *argv[]) +{ + return import_key(ctx, KEYSTORE_BACKEND_PEM, import_file, argc, argv); +} + +int keymgr_import_pkcs11(kdnssec_ctx_t *ctx, const char *key_id, int argc, char *argv[]) +{ + return import_key(ctx, KEYSTORE_BACKEND_PKCS11, key_id, argc, argv); +} + +int keymgr_nsec3_salt_print(kdnssec_ctx_t *ctx) +{ + dnssec_binary_t salt_bin; + knot_time_t created; + int ret = kasp_db_load_nsec3salt(*ctx->kasp_db, ctx->zone->dname, + &salt_bin, &created); + switch (ret) { + case KNOT_EOK: + printf("Current salt: "); + if (salt_bin.size == 0) { + printf("-"); + } + for (size_t i = 0; i < salt_bin.size; i++) { + printf("%02X", (unsigned)salt_bin.data[i]); + } + printf("\n"); + free(salt_bin.data); + break; + case KNOT_ENOENT: + printf("-- no salt --\n"); + ret = KNOT_EOK; + break; + } + return ret; +} + +int keymgr_nsec3_salt_set(kdnssec_ctx_t *ctx, const char *new_salt) +{ + assert(new_salt); + + dnssec_binary_t salt_bin = { 0 }; + if (strcmp(new_salt, "-") != 0 && + hex_to_bin(new_salt, &salt_bin) != DNSSEC_EOK) { + return KNOT_EMALF; + } + if (salt_bin.size != ctx->policy->nsec3_salt_length) { + printf("Warning: specified salt doesn't match configured " + "salt length (%d).\n", + (int)ctx->policy->nsec3_salt_length); + } + int ret = kasp_db_store_nsec3salt(*ctx->kasp_db, ctx->zone->dname, + &salt_bin, knot_time()); + if (salt_bin.size > 0) { + free(salt_bin.data); + } + return ret; +} + +static void print_tsig(dnssec_tsig_algorithm_t mac, const char *name, + const dnssec_binary_t *secret) +{ + assert(name); + assert(secret); + + const char *mac_name = dnssec_tsig_algorithm_to_name(mac); + assert(mac_name); + + // client format (as a comment) + printf("# %s:%s:%.*s\n", mac_name, name, (int)secret->size, secret->data); + + // server format + printf("key:\n"); + printf(" - id: %s\n", name); + printf(" algorithm: %s\n", mac_name); + printf(" secret: %.*s\n", (int)secret->size, secret->data); +} + +int keymgr_generate_tsig(const char *tsig_name, const char *alg_name, int bits) +{ + dnssec_tsig_algorithm_t alg = dnssec_tsig_algorithm_from_name(alg_name); + if (alg == DNSSEC_TSIG_UNKNOWN) { + return KNOT_INVALID_KEY_ALGORITHM; + } + + int optimal_bits = dnssec_tsig_optimal_key_size(alg); + if (bits == 0) { + bits = optimal_bits; // TODO review + } + + // round up bits to bytes + bits = (bits + CHAR_BIT - 1) / CHAR_BIT * CHAR_BIT; + + if (bits != optimal_bits) { + printf("Notice: Optimal key size for %s is %d bits.", + dnssec_tsig_algorithm_to_name(alg), optimal_bits); + } + assert(bits % CHAR_BIT == 0); + + _cleanup_binary_ dnssec_binary_t key = { 0 }; + int r = dnssec_binary_alloc(&key, bits / CHAR_BIT); + if (r != DNSSEC_EOK) { + printf("Failed to allocate memory."); + return knot_error_from_libdnssec(r); + } + + r = gnutls_rnd(GNUTLS_RND_KEY, key.data, key.size); + if (r != 0) { + printf("Failed to generate secret the key."); + return knot_error_from_libdnssec(r); + } + + _cleanup_binary_ dnssec_binary_t key_b64 = { 0 }; + r = dnssec_binary_to_base64(&key, &key_b64); + if (r != DNSSEC_EOK) { + printf("Failed to convert the key to Base64."); + return knot_error_from_libdnssec(r); + } + + print_tsig(alg, tsig_name, &key_b64); + + return KNOT_EOK; +} + +static long is_uint32(const char *string) +{ + if (*string == '\0') { + return -1; + } + for (const char *p = string; *p != '\0'; p++) { + if (!is_digit(*p)) { + return -1; + } + } + long res = atol(string); + return (res <= UINT32_MAX ? res : -1); +} + +static bool is_hex(const char *string) +{ + for (const char *p = string; *p != '\0'; p++) { + if (!is_xdigit(*p)) { + return false; + } + } + return (*string != '\0'); +} + +int keymgr_get_key(kdnssec_ctx_t *ctx, const char *key_spec, knot_kasp_key_t **key) +{ + long spec_tag = is_uint32(key_spec), spec_len = strlen(key_spec); + if (spec_tag < 0 && !is_hex(key_spec)) { + printf("Error in key specification.\n"); + return KNOT_EINVAL; + } + + *key = NULL; + for (size_t i = 0; i < ctx->zone->num_keys; i++) { + knot_kasp_key_t *candidate = &ctx->zone->keys[i]; + if ((spec_tag >= 0 && dnssec_key_get_keytag(candidate->key) == spec_tag) || + (spec_tag < 0 && strncmp(candidate->id, key_spec, spec_len) == 0)) { + if (*key == NULL) { + *key = candidate; + } + else { + printf("Key is not specified uniquely.\n"); + return KNOT_ELIMIT; + } + } + } + if (*key == NULL) { + printf("Key not found.\n"); + return KNOT_ENOENT; + } + return KNOT_EOK; +} + +int keymgr_foreign_key_id(char *argv[], knot_dname_t **key_zone, char **key_id) +{ + *key_zone = knot_dname_from_str_alloc(argv[0]); + if (*key_zone == NULL) { + return KNOT_ENOMEM; + } + knot_dname_to_lower(*key_zone); + + kdnssec_ctx_t kctx = { 0 }; + int ret = kdnssec_ctx_init(conf(), &kctx, *key_zone, NULL); + if (ret != KNOT_EOK) { + printf("Failed to initialize zone %s (%s)\n", argv[0], knot_strerror(ret)); + free(*key_zone); + *key_zone = NULL; + return KNOT_ENOZONE; + } + knot_kasp_key_t *key; + ret = keymgr_get_key(&kctx, argv[2], &key); + if (ret == KNOT_EOK) { + *key_id = strdup(key->id); + if (*key_id == NULL) { + ret = KNOT_ENOMEM; + } + } + kdnssec_ctx_deinit(&kctx); + return ret; +} + +int keymgr_set_timing(knot_kasp_key_t *key, int argc, char *argv[]) +{ + knot_kasp_key_timing_t temp = key->timing; + kdnssec_generate_flags_t flags = ((key->is_ksk ? DNSKEY_GENERATE_KSK : 0) | (key->is_zsk ? DNSKEY_GENERATE_ZSK : 0)); + + if (genkeyargs(argc, argv, true, &flags, NULL, NULL, &temp, NULL)) { + int ret = check_timers(&temp); + if (ret != KNOT_EOK) { + return ret; + } + key->timing = temp; + key->is_ksk = (flags & DNSKEY_GENERATE_KSK); + key->is_zsk = (flags & DNSKEY_GENERATE_ZSK); + return KNOT_EOK; + } + return KNOT_EINVAL; +} + +static void print_timer(const char *name, knot_time_t t, knot_time_print_t format, + char separator) +{ + static char buff[100]; + if (knot_time_print(format, t, buff, sizeof(buff)) < 0) { + printf("%s=(error)%c", name, separator); // shall not happen + } else { + printf("%s=%s%c", name, buff, separator); + } +} + +int keymgr_list_keys(kdnssec_ctx_t *ctx, knot_time_print_t format) +{ + for (size_t i = 0; i < ctx->zone->num_keys; i++) { + knot_kasp_key_t *key = &ctx->zone->keys[i]; + printf("%s ksk=%s zsk=%s tag=%05d algorithm=%-2d size=%-4u public-only=%s ", key->id, + (key->is_ksk ? "yes" : "no "), (key->is_zsk ? "yes" : "no "), + dnssec_key_get_keytag(key->key), (int)dnssec_key_get_algorithm(key->key), + dnssec_key_get_size(key->key), (key->is_pub_only ? "yes" : "no ")); + print_timer("created", key->timing.created, format, ' '); + print_timer("pre-active", key->timing.pre_active, format, ' '); + print_timer("publish", key->timing.publish, format, ' '); + print_timer("ready", key->timing.ready, format, ' '); + print_timer("active", key->timing.active, format, ' '); + print_timer("retire-active", key->timing.retire_active, format, ' '); + print_timer("retire", key->timing.retire, format, ' '); + print_timer("post-active", key->timing.post_active, format, ' '); + print_timer("remove", key->timing.remove, format, '\n'); + } + return KNOT_EOK; +} + +static int print_ds(const knot_dname_t *dname, const dnssec_binary_t *rdata) +{ + wire_ctx_t ctx = wire_ctx_init(rdata->data, rdata->size); + if (wire_ctx_available(&ctx) < 4) { + return KNOT_EMALF; + } + + char *name = knot_dname_to_str_alloc(dname); + if (!name) { + return KNOT_ENOMEM; + } + + uint16_t keytag = wire_ctx_read_u16(&ctx); + uint8_t algorithm = wire_ctx_read_u8(&ctx); + uint8_t digest_type = wire_ctx_read_u8(&ctx); + + size_t digest_size = wire_ctx_available(&ctx); + + printf("%s DS %d %d %d ", name, keytag, algorithm, digest_type); + for (size_t i = 0; i < digest_size; i++) { + printf("%02x", ctx.position[i]); + } + printf("\n"); + + free(name); + return KNOT_EOK; +} + +static int create_and_print_ds(const knot_dname_t *zone_name, + const dnssec_key_t *key, dnssec_key_digest_t digest) +{ + _cleanup_binary_ dnssec_binary_t rdata = { 0 }; + int r = dnssec_key_create_ds(key, digest, &rdata); + if (r != DNSSEC_EOK) { + return knot_error_from_libdnssec(r); + } + + return print_ds(zone_name, &rdata); +} + +int keymgr_generate_ds(const knot_dname_t *dname, const knot_kasp_key_t *key) +{ + static const dnssec_key_digest_t digests[] = { + DNSSEC_KEY_DIGEST_SHA1, + DNSSEC_KEY_DIGEST_SHA256, + DNSSEC_KEY_DIGEST_SHA384, + 0 + }; + + int ret = KNOT_EOK; + for (int i = 0; digests[i] != 0 && ret == KNOT_EOK; i++) { + ret = create_and_print_ds(dname, key->key, digests[i]); + } + + return ret; +} + +int keymgr_generate_dnskey(const knot_dname_t *dname, const knot_kasp_key_t *key) +{ + const dnssec_key_t *dnskey = key->key; + + char *name = knot_dname_to_str_alloc(dname); + if (!name) { + return KNOT_ENOMEM; + } + + uint16_t flags = dnssec_key_get_flags(dnskey); + uint8_t algorithm = dnssec_key_get_algorithm(dnskey); + + dnssec_binary_t pubkey = { 0 }; + int ret = dnssec_key_get_pubkey(dnskey, &pubkey); + if (ret != DNSSEC_EOK) { + free(name); + return knot_error_from_libdnssec(ret); + } + + uint8_t *base64_output = NULL; + int len = base64_encode_alloc(pubkey.data, pubkey.size, &base64_output); + if (len < 0) { + free(name); + return len; + } + + printf("%s DNSKEY %u 3 %u %.*s\n", name, flags, algorithm, len, base64_output); + + free(base64_output); + free(name); + return KNOT_EOK; +} diff --git a/src/utils/keymgr/functions.h b/src/utils/keymgr/functions.h new file mode 100644 index 0000000..0dcf9cc --- /dev/null +++ b/src/utils/keymgr/functions.h @@ -0,0 +1,45 @@ +/* Copyright (C) 2018 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 "knot/dnssec/context.h" + +int keymgr_generate_key(kdnssec_ctx_t *ctx, int argc, char *argv[]); + +int keymgr_import_bind(kdnssec_ctx_t *ctx, const char *import_file, bool pub_only); + +int keymgr_import_pem(kdnssec_ctx_t *ctx, const char *import_file, int argc, char *argv[]); + +int keymgr_import_pkcs11(kdnssec_ctx_t *ctx, const char *key_id, int argc, char *argv[]); + +int keymgr_nsec3_salt_print(kdnssec_ctx_t *ctx); + +int keymgr_nsec3_salt_set(kdnssec_ctx_t *ctx, const char *new_salt); + +int keymgr_generate_tsig(const char *tsig_name, const char *alg_name, int bits); + +int keymgr_get_key(kdnssec_ctx_t *ctx, const char *key_spec, knot_kasp_key_t **key); + +int keymgr_foreign_key_id(char *argv[], knot_dname_t **key_zone, char **key_id); + +int keymgr_set_timing(knot_kasp_key_t *key, int argc, char *argv[]); + +int keymgr_list_keys(kdnssec_ctx_t *ctx, knot_time_print_t format); + +int keymgr_generate_ds(const knot_dname_t *dname, const knot_kasp_key_t *key); + +int keymgr_generate_dnskey(const knot_dname_t *dname, const knot_kasp_key_t *key); diff --git a/src/utils/keymgr/main.c b/src/utils/keymgr/main.c new file mode 100644 index 0000000..0ed388f --- /dev/null +++ b/src/utils/keymgr/main.c @@ -0,0 +1,373 @@ +/* Copyright (C) 2018 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 <getopt.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include "contrib/string.h" +#include "knot/conf/conf.h" +#include "knot/dnssec/zone-keys.h" +#include "libknot/libknot.h" +#include "utils/common/params.h" +#include "utils/keymgr/functions.h" + +#define PROGRAM_NAME "keymgr" + +static void print_help(void) +{ + printf("Usage:\n" + " %s -h | -V\n" + " %s -t <tsig_name> [<algorithm>] [<bits>]\n" + " %s [-c | -C | -d <path>] <zone> <command> [<argument>...]\n" + "\n" + "Parameters:\n" + " -c, --config <file> Use a textual configuration file.\n" + " (default %s)\n" + " -C, --confdb <dir> Use a binary configuration database directory.\n" + " (default %s)\n" + " -d, --dir <path> Use specified KASP database path and default configuration.\n" + " -t, --tsig <name> [alg] Generate a TSIG key.\n" + " -h, --help Print the program help.\n" + " -V, --version Print the program version.\n" + "\n" + "Commands:\n" + " list List all zone's DNSSEC keys.\n" + " generate Generate new DNSSEC key.\n" + " (syntax: generate <attribute_name>=<value>...)\n" + " import-bind Import BIND-style key file pair (.key + .private).\n" + " (syntax: import-bind <key_file_name>)\n" + " import-pub Import public-only key to be published in the zone (in BIND .key format).\n" + " (syntax: import-pub <key_file_name>)\n" + " import-pem Import key in PEM format. Specify its parameters manually.\n" + " (syntax: import-pem <pem_file_path> <attribute_name>=<value>...)\n" + " import-pkcs11 Import key stored in PKCS11 storage. Specify its parameters manually.\n" + " (syntax: import-pkcs11 <key_id> <attribute_name>=<value>...)\n" + " nsec3-salt Print current NSEC3 salt. If parameter is specified, set new salt.\n" + " (syntax: nsec3salt [<new_salt>])\n" + " ds Generate DS record(s) for specified key.\n" + " (syntax: ds <key_spec>)\n" + " dnskey Generate DNSKEY record for specified key.\n" + " (syntax: dnskey <key_spec>)\n" + " share Share an existing key of another zone with the specified zone.\n" + " (syntax: share <full_key_ID>\n" + " delete Remove the specified key from zone.\n" + " (syntax: delete <key_spec>)\n" + " set Set existing key's timing attribute.\n" + " (syntax: set <key_spec> <attribute_name>=<value>...)\n" + "\n" + "Key specification:\n" + " either the key tag (number) or [a prefix of] key ID.\n" + "\n" + "Key attributes:\n" + " algorithm The key cryptographic algorithm: either name (e.g. RSASHA256) or\n" + " number.\n" + " size The key size in bits.\n" + " ksk Whether the generated/imported key shall be Key Signing Key.\n" + " created/publish/ready/active/retire/remove The timestamp of the key\n" + " lifetime event (e.g. published=+1d active=1499770874)\n", + PROGRAM_NAME, PROGRAM_NAME, PROGRAM_NAME, CONF_DEFAULT_FILE, CONF_DEFAULT_DBDIR); +} + +static int key_command(int argc, char *argv[], int optind) +{ + if (argc < optind + 2) { + printf("Zone name and/or command not specified\n"); + print_help(); + return KNOT_EINVAL; + } + argc -= optind; + argv += optind; + + knot_dname_t *zone_name = knot_dname_from_str_alloc(argv[0]); + if (zone_name == NULL) { + return KNOT_ENOMEM; + } + knot_dname_to_lower(zone_name); + + kdnssec_ctx_t kctx = { 0 }; + + conf_val_t mapsize = conf_default_get(conf(), C_MAX_KASP_DB_SIZE); + char *kasp_dir = conf_kaspdir(conf()); + int ret = kasp_db_init(kaspdb(), kasp_dir, conf_int(&mapsize)); + free(kasp_dir); + if (ret != KNOT_EOK) { + printf("Failed to initialize KASP db (%s)\n", knot_strerror(ret)); + goto main_end; + } + + ret = kdnssec_ctx_init(conf(), &kctx, zone_name, NULL); + if (ret != KNOT_EOK) { + printf("Failed to initialize KASP (%s)\n", knot_strerror(ret)); + goto main_end; + } + +#define CHECK_MISSING_ARG(msg) \ + if (argc < 3) { \ + printf("%s\n", (msg)); \ + ret = KNOT_EINVAL; \ + goto main_end; \ + } + + bool print_ok_on_succes = true; + if (strcmp(argv[1], "generate") == 0) { + ret = keymgr_generate_key(&kctx, argc - 2, argv + 2); + print_ok_on_succes = false; + } else if (strcmp(argv[1], "import-bind") == 0) { + CHECK_MISSING_ARG("BIND-style key to import not specified"); + ret = keymgr_import_bind(&kctx, argv[2], false); + } else if (strcmp(argv[1], "import-pub") == 0) { + CHECK_MISSING_ARG("BIND-style key to import not specified"); + ret = keymgr_import_bind(&kctx, argv[2], true); + } else if (strcmp(argv[1], "import-pem") == 0) { + CHECK_MISSING_ARG("PEM file to import not specified"); + ret = keymgr_import_pem(&kctx, argv[2], argc - 3, argv + 3); + } else if (strcmp(argv[1], "import-pkcs11") == 0) { + CHECK_MISSING_ARG("Key ID to import not specified"); + ret = keymgr_import_pkcs11(&kctx, argv[2], argc - 3, argv + 3); + } else if (strcmp(argv[1], "nsec3-salt") == 0) { + if (argc > 2) { + ret = keymgr_nsec3_salt_set(&kctx, argv[2]); + } else { + ret = keymgr_nsec3_salt_print(&kctx); + print_ok_on_succes = false; + } + } else if (strcmp(argv[1], "set") == 0) { + CHECK_MISSING_ARG("Key is not specified"); + knot_kasp_key_t *key2set; + ret = keymgr_get_key(&kctx, argv[2], &key2set); + if (ret == KNOT_EOK) { + ret = keymgr_set_timing(key2set, argc - 3, argv + 3); + if (ret == KNOT_EOK) { + ret = kdnssec_ctx_commit(&kctx); + } + } + } else if (strcmp(argv[1], "list") == 0) { + knot_time_print_t format = TIME_PRINT_UNIX; + if (argc > 2 && strcmp(argv[2], "human") == 0) { + format = TIME_PRINT_HUMAN_MIXED; + } else if (argc > 2 && strcmp(argv[2], "iso") == 0) { + format = TIME_PRINT_ISO8601; + } + ret = keymgr_list_keys(&kctx, format); + print_ok_on_succes = false; + } else if (strcmp(argv[1], "ds") == 0 || strcmp(argv[1], "dnskey") == 0) { + int (*generate_rr)(const knot_dname_t *, const knot_kasp_key_t *) = keymgr_generate_dnskey; + if (strcmp(argv[1], "ds") == 0) { + generate_rr = keymgr_generate_ds; + } + if (argc < 3) { + for (int i = 0; i < kctx.zone->num_keys && ret == KNOT_EOK; i++) { + if (kctx.zone->keys[i].is_ksk) { + ret = generate_rr(zone_name, &kctx.zone->keys[i]); + } + } + } else { + knot_kasp_key_t *key2rr; + ret = keymgr_get_key(&kctx, argv[2], &key2rr); + if (ret == KNOT_EOK) { + ret = generate_rr(zone_name, key2rr); + } + } + print_ok_on_succes = false; + } else if (strcmp(argv[1], "share") == 0) { + CHECK_MISSING_ARG("Key to be shared is not specified"); + knot_dname_t *other_zone = NULL; + char *key_to_share = NULL; + ret = keymgr_foreign_key_id(argv, &other_zone, &key_to_share); + if (ret == KNOT_EOK) { + ret = kasp_db_share_key(*kctx.kasp_db, other_zone, kctx.zone->dname, key_to_share); + } + free(other_zone); + free(key_to_share); + } else if (strcmp(argv[1], "delete") == 0) { + CHECK_MISSING_ARG("Key is not specified"); + knot_kasp_key_t *key2del; + ret = keymgr_get_key(&kctx, argv[2], &key2del); + if (ret == KNOT_EOK) { + ret = kdnssec_delete_key(&kctx, key2del); + } + } else { + printf("Wrong zone-key command: %s\n", argv[1]); + goto main_end; + } + +#undef CHECK_MISSING_ARG + + if (ret == KNOT_EOK) { + printf("%s", print_ok_on_succes ? "OK\n" : ""); + } else { + printf("Error (%s)\n", knot_strerror(ret)); + } + +main_end: + kdnssec_ctx_deinit(&kctx); + kasp_db_close(kaspdb()); + free(zone_name); + + return ret; +} + +static bool init_conf(const char *confdb) +{ + size_t max_conf_size = (size_t)CONF_MAPSIZE * 1024 * 1024; + + conf_flag_t flags = CONF_FNOHOSTNAME | CONF_FOPTMODULES; + if (confdb != NULL) { + flags |= CONF_FREADONLY; + } + + conf_t *new_conf = NULL; + int ret = conf_new(&new_conf, conf_schema, confdb, max_conf_size, flags); + if (ret != KNOT_EOK) { + printf("Failed opening configuration database %s (%s)\n", + (confdb == NULL ? "" : confdb), knot_strerror(ret)); + return false; + } + conf_update(new_conf, CONF_UPD_FNONE); + return true; +} + +static bool init_confile(const char *confile) +{ + int ret = conf_import(conf(), confile, true); + if (ret != KNOT_EOK) { + printf("Failed opening configuration file %s (%s)\n", + confile, knot_strerror(ret)); + return false; + } + return true; +} + +static bool init_conf_blank(const char *kasp_dir) +{ + char *confstr = sprintf_alloc("template:\n""" + " - id: default\n" + " storage: .\n" + " kasp-db: %s\n", kasp_dir); + int ret = conf_import(conf(), confstr, false); + free(confstr); + if (ret != KNOT_EOK) { + printf("Failed creating fake configuration (%s)\n", + knot_strerror(ret)); + return false; + } + return true; +} + +static void update_privileges(void) +{ + int uid, gid; + if (conf_user(conf(), &uid, &gid) != KNOT_EOK) { + return; + } + + // Just try to alter process privileges if different from configured. + int unused __attribute__((unused)); + if ((gid_t)gid != getgid()) { + unused = setregid(gid, gid); + } + if ((uid_t)uid != getuid()) { + unused = setreuid(uid, uid); + } +} + +static bool conf_initialized = false; // This is a singleton as well as conf() is. + +#define CHECK_CONF_UNINIT \ + if ((conf_initialized = !conf_initialized) == false) { \ + printf("Error: multiple arguments attempting configuration initializatioin.\n"); \ + return EXIT_FAILURE; \ + } + +int main(int argc, char *argv[]) +{ + int ret; + + struct option opts[] = { + { "config", required_argument, NULL, 'c' }, + { "confdb", required_argument, NULL, 'C' }, + { "dir", required_argument, NULL, 'd' }, + { "tsig", required_argument, NULL, 't' }, + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, 'V' }, + { NULL } + }; + + int opt = 0; + while ((opt = getopt_long(argc, argv, "hVd:c:C:t:", opts, NULL)) != -1) { + switch (opt) { + case 'h': + print_help(); + return EXIT_SUCCESS; + case 'V': + print_version(PROGRAM_NAME); + return EXIT_SUCCESS; + case 'd': + CHECK_CONF_UNINIT + if (!init_conf(NULL) || !init_conf_blank(optarg)) { + return EXIT_FAILURE; + } + break; + case 'c': + CHECK_CONF_UNINIT + if (!init_conf(NULL) || !init_confile(optarg)) { + return EXIT_FAILURE; + } + break; + case 'C': + CHECK_CONF_UNINIT + if (!init_conf(argv[2])) { + return EXIT_FAILURE; + } + break; + case 't': + ret = keymgr_generate_tsig(optarg, (argc > optind ? argv[optind] : "hmac-sha256"), + (argc > optind + 1 ? atol(argv[optind + 1]) : 0)); + if (ret != KNOT_EOK) { + printf("Failed to generate TSIG (%s)\n", knot_strerror(ret)); + } + return (ret == KNOT_EOK ? EXIT_SUCCESS : EXIT_FAILURE); + default: + print_help(); + return EXIT_FAILURE; + } + } + + if (!conf_initialized) { + struct stat st; + if (stat(CONF_DEFAULT_DBDIR, &st) == 0 && init_conf(CONF_DEFAULT_DBDIR)) { + // initialized conf from default DB location + } else if (stat(CONF_DEFAULT_FILE, &st) == 0 && + init_conf(NULL) && init_confile(CONF_DEFAULT_FILE)) { + // initialized conf from default confile + } else { + printf("Couldn't initialize configuration, please provide -c, -C, or -d option\n"); + return EXIT_FAILURE; + } + } + + update_privileges(); + + ret = key_command(argc, argv, optind); + + conf_free(conf()); + + return (ret == KNOT_EOK ? EXIT_SUCCESS : EXIT_FAILURE); +} |