diff options
Diffstat (limited to '')
-rw-r--r-- | src/libdnssec/key.h | 309 | ||||
-rw-r--r-- | src/libdnssec/key/algorithm.c | 180 | ||||
-rw-r--r-- | src/libdnssec/key/algorithm.h | 30 | ||||
-rw-r--r-- | src/libdnssec/key/convert.c | 375 | ||||
-rw-r--r-- | src/libdnssec/key/convert.h | 44 | ||||
-rw-r--r-- | src/libdnssec/key/dnskey.c | 91 | ||||
-rw-r--r-- | src/libdnssec/key/dnskey.h | 46 | ||||
-rw-r--r-- | src/libdnssec/key/ds.c | 128 | ||||
-rw-r--r-- | src/libdnssec/key/internal.h | 35 | ||||
-rw-r--r-- | src/libdnssec/key/key.c | 439 | ||||
-rw-r--r-- | src/libdnssec/key/keytag.c | 88 | ||||
-rw-r--r-- | src/libdnssec/key/privkey.c | 140 | ||||
-rw-r--r-- | src/libdnssec/key/privkey.h | 35 | ||||
-rw-r--r-- | src/libdnssec/key/simple.c | 55 | ||||
-rw-r--r-- | src/libdnssec/keyid.c | 87 | ||||
-rw-r--r-- | src/libdnssec/keyid.h | 64 | ||||
-rw-r--r-- | src/libdnssec/keystore.h | 155 | ||||
-rw-r--r-- | src/libdnssec/keystore/internal.h | 50 | ||||
-rw-r--r-- | src/libdnssec/keystore/keystore.c | 186 | ||||
-rw-r--r-- | src/libdnssec/keystore/pkcs11.c | 397 | ||||
-rw-r--r-- | src/libdnssec/keystore/pkcs8.c | 497 | ||||
-rw-r--r-- | src/libdnssec/keytag.h | 44 |
22 files changed, 3475 insertions, 0 deletions
diff --git a/src/libdnssec/key.h b/src/libdnssec/key.h new file mode 100644 index 0000000..2a69d37 --- /dev/null +++ b/src/libdnssec/key.h @@ -0,0 +1,309 @@ +/* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + */ + +/*! + * \file + * + * \addtogroup key + * + * \brief DNSSEC public and private key manipulation. + * + * The dnssec_key_t is an abstraction for a DNSSEC key pair. If the key + * key is initialized with a public key data only, it can be used only for + * signature verification. In order to use the key for signing, private key + * has to be loaded. If only a private key is loaded into the structure, + * the public key is automatically constructed. + * + * The module interface provides various functions to retrieve information + * about the key. But the key is mostly used by other modules of the library. + * + * @{ + */ + +#pragma once + +#include <stdbool.h> +#include <stdint.h> + +#include <libdnssec/binary.h> + +/*! + * DNSKEY algorithm numbers. + * + * \see https://www.iana.org/assignments/dns-sec-alg-numbers/dns-sec-alg-numbers.xhtml + */ +typedef enum dnssec_key_algorithm { + DNSSEC_KEY_ALGORITHM_DELETE = 0, + DNSSEC_KEY_ALGORITHM_RSA_MD5 = 1, /*!< Unsupported */ + DNSSEC_KEY_ALGORITHM_DH = 2, /*!< Unsupported */ + DNSSEC_KEY_ALGORITHM_DSA = 3, /*!< Unsupported */ + + DNSSEC_KEY_ALGORITHM_RSA_SHA1 = 5, + DNSSEC_KEY_ALGORITHM_DSA_NSEC3_SHA1 = 6, /*!< Unsupported */ + DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3 = 7, + DNSSEC_KEY_ALGORITHM_RSA_SHA256 = 8, + DNSSEC_KEY_ALGORITHM_RSA_SHA512 = 10, + DNSSEC_KEY_ALGORITHM_ECC_GOST = 12, /*!< Unsupported */ + DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256 = 13, + DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384 = 14, + DNSSEC_KEY_ALGORITHM_ED25519 = 15, + DNSSEC_KEY_ALGORITHM_ED448 = 16, + + DNSSEC_KEY_ALGORITHM_INDIRECT = 252, + DNSSEC_KEY_ALGORITHM_PRIVATEDNS = 253, + DNSSEC_KEY_ALGORITHM_PRIVATEOID = 254, +} dnssec_key_algorithm_t; + +/*! + * DS algorithm numbers. + * + * \see https://www.iana.org/assignments/ds-rr-types/ds-rr-types.xhtml + */ +typedef enum dnssec_key_digest { + DNSSEC_KEY_DIGEST_INVALID = 0, + DNSSEC_KEY_DIGEST_SHA1 = 1, + DNSSEC_KEY_DIGEST_SHA256 = 2, + DNSSEC_KEY_DIGEST_SHA384 = 4, +} dnssec_key_digest_t; + +/*! + * DS algorithm digest lengths in bytes. + */ +typedef enum dnssec_key_digest_len { + DNSSEC_KEY_DIGEST_LEN_SHA1 = 20, /*!< RFC 3658 */ + DNSSEC_KEY_DIGEST_LEN_SHA256 = 32, /*!< RFC 4509 */ + DNSSEC_KEY_DIGEST_LEN_SHA384 = 48, /*!< RFC 6605 */ +} dnssec_key_digest_len_t; + +struct dnssec_key; + +/*! + * DNSSEC key. + */ +typedef struct dnssec_key dnssec_key_t; + +/*! + * Check whether a DNSKEY algorithm is supported. + * + * @note: less secure algorithms may go unsupported on purpose. + * - if IETF RFCs deprecate algorithm even for validation (see RFC 8624) + * - if (local) GnuTLS policy considers an algorithm insecure + */ +bool dnssec_algorithm_key_support(dnssec_key_algorithm_t algorithm); + +/*! + * Check if the algorithm allows deterministic signing. + */ +bool dnssec_algorithm_reproducible(dnssec_key_algorithm_t algorithm, bool enabled); + +/*! + * Allocate new DNSSEC key. + * + * The protocol field of the key is set to 3 (DNSSEC). + * The flags field of the key is set to 256 (zone key, no SEP). + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_key_new(dnssec_key_t **key); + +/*! + * Clear the DNSSEC key. + * + * Has the same effect as calling \ref dnssec_key_free and \ref dnssec_key_new. + */ +void dnssec_key_clear(dnssec_key_t *key); + +/*! + * Free the key allocated by \ref dnssec_key_new. + */ +void dnssec_key_free(dnssec_key_t *key); + +/*! + * Create a copy of a DNSSEC key. + * + * Only a public part of the key is copied. + */ +dnssec_key_t *dnssec_key_dup(const dnssec_key_t *key); + +/*! + * Get the key tag of the DNSSEC key. + */ +uint16_t dnssec_key_get_keytag(const dnssec_key_t *key); + +/*! + * Get the domain name of the DNSSEC key. + */ +const uint8_t *dnssec_key_get_dname(const dnssec_key_t *key); + +/*! + * Set the domain name of the DNSSEC key. + */ +int dnssec_key_set_dname(dnssec_key_t *key, const uint8_t *dname); + +/*! + * Get the flags field of the DNSSEC key. + */ +uint16_t dnssec_key_get_flags(const dnssec_key_t *key); + +/*! + * Set the flags field of the DNSSEC key. + */ +int dnssec_key_set_flags(dnssec_key_t *key, uint16_t flags); + +/*! + * Get the protocol field of the DNSSEC key. + */ +uint8_t dnssec_key_get_protocol(const dnssec_key_t *key); + +/*! + * Get the protocol field of the DNSSEC key. + */ +int dnssec_key_set_protocol(dnssec_key_t *key, uint8_t protocol); + +/*! + * Get the algorithm field of the DNSSEC key. + */ +uint8_t dnssec_key_get_algorithm(const dnssec_key_t *key); + +/*! + * Set the algorithm field of the DNSSEC key. + * + * The function will fail if the algorithm is incompatible with the + * loaded key. This means, that the function can be used to set the initial + * algorithm and later, only the hashing algorithm can be changed. + */ +int dnssec_key_set_algorithm(dnssec_key_t *key, uint8_t algorithm); + +/*! + * Get the public key field of the DNSSEC key. + * + * The returned content must not be modified by the caller. A reference + * to internally allocated structure is returned. + */ +int dnssec_key_get_pubkey(const dnssec_key_t *key, dnssec_binary_t *pubkey); + +/*! + * Set the public key field of the DNSSEC key. + * + * A valid algorithm has to be set prior to calling this function. + * + * The function will fail if the key is already loaded in the structure. + */ +int dnssec_key_set_pubkey(dnssec_key_t *key, const dnssec_binary_t *pubkey); + +/*! + * Get the bit size of the cryptographic key used with the DNSSEC key. + */ +unsigned dnssec_key_get_size(const dnssec_key_t *key); + +/*! + * \brief Compute key ID from public key. + * + * \param key Key structure holding the public key. + * \param id Output: key ID in hex. + * + * \return DNSSEC_E* + */ +int dnssec_key_get_keyid(const dnssec_key_t *key, char **id); + +/*! + * Get the RDATA of the DNSSEC key. + * + * The returned content must not be modified by the caller. A reference + * to internally allocated structure is returned. + */ +int dnssec_key_get_rdata(const dnssec_key_t *key, dnssec_binary_t *rdata); + +/*! + * Set the RDATA of the DNSSEC key. + * + * Calling this function has the same effect as setting the individual + * fields of the key step-by-step. The same limitations apply. + */ +int dnssec_key_set_rdata(dnssec_key_t *key, const dnssec_binary_t *rdata); + +/*! + * Load PKCS #8 private key in the unencrypted PEM format. + * + * At least an algorithm must be set prior to calling this function. + * + * The function will create public key, unless it was already set (using + * \ref dnssec_key_set_pubkey or \ref dnssec_key_set_rdata). If the public key + * was set, the function will prevent loading of non-matching private key. + */ +int dnssec_key_load_pkcs8(dnssec_key_t *key, const dnssec_binary_t *pem); + +/*! + * Check if the key can be used for signing. + */ +bool dnssec_key_can_sign(const dnssec_key_t *key); + +/*! + * Check if the key can be used for verification. + */ +bool dnssec_key_can_verify(const dnssec_key_t *key); + +/*! + * Get private key size range for a DNSSEC algorithm. + * + * \param[in] algorithm DNSKEY algorithm. + * \param[out] min Minimal size of the private key (can be NULL). + * \param[out] max Maximal size of the private key (can be NULL). + * + * \return DNSSEC_EOK for valid parameters. + */ +int dnssec_algorithm_key_size_range(dnssec_key_algorithm_t algorithm, + unsigned *min, unsigned *max); + +/*! + * Check if the private key size matches DNSKEY constraints. + * + * \param algorithm DNSKEY algorithm. + * \param bits Private key size. + * + * \return DNSKEY algorithm matches the key size constraints. + */ +bool dnssec_algorithm_key_size_check(dnssec_key_algorithm_t algorithm, + unsigned bits); + +/*! + * Get default key size for given algorithm. + * + * The default size is balance between security and response lengths with + * respect to use in DNS. + */ +int dnssec_algorithm_key_size_default(dnssec_key_algorithm_t algorithm); + +/*! + * Check whether a DS algorithm is supported. + * + * @note: see note at dnssec_algorithm_key_support(). + */ +bool dnssec_algorithm_digest_support(dnssec_key_digest_t algorithm); + +/*! + * Create DS (Delegation Signer) RDATA from DNSSEC key. + * + * \param[in] key DNSSEC key. + * \param[in] digest Digest algorithm to be used. + * \param[out] rdata Allocated DS RDATA. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_key_create_ds(const dnssec_key_t *key, dnssec_key_digest_t digest, + dnssec_binary_t *rdata); + +/*! @} */ diff --git a/src/libdnssec/key/algorithm.c b/src/libdnssec/key/algorithm.c new file mode 100644 index 0000000..a9bc3ee --- /dev/null +++ b/src/libdnssec/key/algorithm.c @@ -0,0 +1,180 @@ +/* Copyright (C) 2020 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 <https://www.gnu.org/licenses/>. + */ + +#include <gnutls/gnutls.h> + +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/key/algorithm.h" +#include "libdnssec/shared/shared.h" + +/* -- internal ------------------------------------------------------------- */ + +struct limits { + unsigned min; + unsigned max; + unsigned def; + bool (*validate)(unsigned bits); +}; + +static const struct limits *get_limits(dnssec_key_algorithm_t algorithm) +{ + static const struct limits RSA = { + .min = 1024, + .max = 4096, + .def = 2048, + }; + + static const struct limits EC256 = { + .min = 256, + .max = 256, + .def = 256, + }; + + static const struct limits EC384 = { + .min = 384, + .max = 384, + .def = 384, + }; + + static const struct limits ED25519 = { + .min = 256, + .max = 256, + .def = 256, + }; + + static const struct limits ED448 = { + .min = 456, + .max = 456, + .def = 456, + }; + + 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; + case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: + return &EC256; + case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: + return &EC384; + case DNSSEC_KEY_ALGORITHM_ED25519: + return &ED25519; + case DNSSEC_KEY_ALGORITHM_ED448: + return &ED448; + default: + return NULL; + } +} + +/* -- internal API --------------------------------------------------------- */ + +gnutls_pk_algorithm_t algorithm_to_gnutls(dnssec_key_algorithm_t dnssec) +{ + switch (dnssec) { + 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 GNUTLS_PK_RSA; + case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: + case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: + return GNUTLS_PK_EC; +#ifdef HAVE_ED25519 + case DNSSEC_KEY_ALGORITHM_ED25519: + return GNUTLS_PK_EDDSA_ED25519; +#endif +#ifdef HAVE_ED448 + case DNSSEC_KEY_ALGORITHM_ED448: + return GNUTLS_PK_EDDSA_ED448; +#endif + default: + return GNUTLS_PK_UNKNOWN; + } +} + +/* -- public API ----------------------------------------------------------- */ + +_public_ +bool dnssec_algorithm_reproducible(dnssec_key_algorithm_t algorithm, bool enabled) +{ + (void)enabled; + switch (algorithm) { + case DNSSEC_KEY_ALGORITHM_ED25519: + case DNSSEC_KEY_ALGORITHM_ED448: + return true; // those are always reproducible + case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: + case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: +#ifdef HAVE_GNUTLS_REPRODUCIBLE + return enabled; // Reproducible only if GnuTLS supports && enabled +#else + return false; +#endif + default: + return false; + } +} + +_public_ +int dnssec_algorithm_key_size_range(dnssec_key_algorithm_t algorithm, + unsigned *min_ptr, unsigned *max_ptr) +{ + if (!min_ptr && !max_ptr) { + return DNSSEC_EINVAL; + } + + const struct limits *limits = get_limits(algorithm); + if (!limits) { + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + if (min_ptr) { + *min_ptr = limits->min; + } + if (max_ptr) { + *max_ptr = limits->max; + } + + return DNSSEC_EOK; +} + +_public_ +bool dnssec_algorithm_key_size_check(dnssec_key_algorithm_t algorithm, + unsigned bits) +{ + const struct limits *limits = get_limits(algorithm); + if (!limits) { + return false; + } + + if (bits < limits->min || bits > limits->max) { + return false; + } + + if (limits->validate && !limits->validate(bits)) { + return false; + } + + return true; +} + +_public_ +int dnssec_algorithm_key_size_default(dnssec_key_algorithm_t algorithm) +{ + const struct limits *limits = get_limits(algorithm); + return limits ? limits->def : 0; +} diff --git a/src/libdnssec/key/algorithm.h b/src/libdnssec/key/algorithm.h new file mode 100644 index 0000000..4906675 --- /dev/null +++ b/src/libdnssec/key/algorithm.h @@ -0,0 +1,30 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gnutls/gnutls.h> + +#include "libdnssec/key.h" + +/*! + * Convert DNSKEY algorithm identifier to GnuTLS identifier. + * + * \param dnssec DNSSEC DNSKEY algorithm identifier. + * + * \return GnuTLS private key algorithm identifier, GNUTLS_PK_UNKNOWN on error. + */ +gnutls_pk_algorithm_t algorithm_to_gnutls(dnssec_key_algorithm_t dnssec); diff --git a/src/libdnssec/key/convert.c b/src/libdnssec/key/convert.c new file mode 100644 index 0000000..56168f7 --- /dev/null +++ b/src/libdnssec/key/convert.c @@ -0,0 +1,375 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <gnutls/abstract.h> +#include <gnutls/gnutls.h> +#include <stdbool.h> +#include <stdint.h> +#include <string.h> + +#include "libdnssec/shared/bignum.h" +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/key/algorithm.h" +#include "libdnssec/key/dnskey.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/shared/binary_wire.h" + +/* -- wrappers for GnuTLS types -------------------------------------------- */ + +static size_t bignum_size_u_datum(const gnutls_datum_t *_bignum) +{ + const dnssec_binary_t bignum = binary_from_datum(_bignum); + return bignum_size_u(&bignum); +} + +static void wire_write_bignum_datum(wire_ctx_t *ctx, size_t width, + const gnutls_datum_t *_bignum) +{ + const dnssec_binary_t bignum = binary_from_datum(_bignum); + bignum_write(ctx, width, &bignum); +} + +static gnutls_datum_t wire_take_datum(wire_ctx_t *ctx, size_t count) +{ + gnutls_datum_t result = { .data = ctx->position, .size = count }; + ctx->position += count; + + return result; +} + +/* -- DNSSEC to crypto ------------------------------------------------------*/ + +/*! + * Convert RSA public key to DNSSEC format. + */ +static int rsa_pubkey_to_rdata(gnutls_pubkey_t key, dnssec_binary_t *rdata) +{ + assert(key); + assert(rdata); + + _cleanup_datum_ gnutls_datum_t modulus = { 0 }; + _cleanup_datum_ gnutls_datum_t exponent = { 0 }; + + int result = gnutls_pubkey_get_pk_rsa_raw(key, &modulus, &exponent); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_EXPORT_ERROR; + } + + size_t exponent_size = bignum_size_u_datum(&exponent); + if (exponent_size > UINT8_MAX) { + return DNSSEC_KEY_EXPORT_ERROR; + } + + size_t modulus_size = bignum_size_u_datum(&modulus); + + result = dnssec_binary_alloc(rdata, 1 + exponent_size + modulus_size); + if (result != DNSSEC_EOK) { + return result; + } + + wire_ctx_t wire = binary_init(rdata); + wire_ctx_write_u8(&wire, exponent_size); + wire_write_bignum_datum(&wire, exponent_size, &exponent); + wire_write_bignum_datum(&wire, modulus_size, &modulus); + assert(wire_ctx_offset(&wire) == rdata->size); + + return DNSSEC_EOK; +} + +/*! + * Get point size for an ECDSA curve. + */ +static size_t ecdsa_curve_point_size(gnutls_ecc_curve_t curve) +{ + switch (curve) { + case GNUTLS_ECC_CURVE_SECP256R1: return 32; + case GNUTLS_ECC_CURVE_SECP384R1: return 48; + default: return 0; + } +} + +#if defined(HAVE_ED25519) || defined(HAVE_ED448) +static size_t eddsa_curve_point_size(gnutls_ecc_curve_t curve) +{ + switch (curve) { +#ifdef HAVE_ED25519 + case GNUTLS_ECC_CURVE_ED25519: return 32; +#endif +#ifdef HAVE_ED448 + case GNUTLS_ECC_CURVE_ED448: return 57; +#endif + default: return 0; + } +} +#endif + +/*! + * Convert ECDSA public key to DNSSEC format. + */ +static int ecdsa_pubkey_to_rdata(gnutls_pubkey_t key, dnssec_binary_t *rdata) +{ + assert(key); + assert(rdata); + + _cleanup_datum_ gnutls_datum_t point_x = { 0 }; + _cleanup_datum_ gnutls_datum_t point_y = { 0 }; + gnutls_ecc_curve_t curve = { 0 }; + + int result = gnutls_pubkey_get_pk_ecc_raw(key, &curve, &point_x, &point_y); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_EXPORT_ERROR; + } + + size_t point_size = ecdsa_curve_point_size(curve); + if (point_size == 0) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + result = dnssec_binary_alloc(rdata, 2 * point_size); + if (result != DNSSEC_EOK) { + return result; + } + + wire_ctx_t wire = binary_init(rdata); + wire_write_bignum_datum(&wire, point_size, &point_x); + wire_write_bignum_datum(&wire, point_size, &point_y); + assert(wire_ctx_offset(&wire) == rdata->size); + + return DNSSEC_EOK; +} + +/*! + * Convert EDDSA public key to DNSSEC format. + */ +#if defined(HAVE_ED25519) || defined(HAVE_ED448) +static int eddsa_pubkey_to_rdata(gnutls_pubkey_t key, dnssec_binary_t *rdata) +{ + assert(key); + assert(rdata); + + _cleanup_datum_ gnutls_datum_t point_x = { 0 }; + gnutls_ecc_curve_t curve = { 0 }; + + int result = gnutls_pubkey_get_pk_ecc_raw(key, &curve, &point_x, NULL); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_EXPORT_ERROR; + } + + size_t point_size = eddsa_curve_point_size(curve); + if (point_size == 0) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + result = dnssec_binary_alloc(rdata, point_size); + if (result != DNSSEC_EOK) { + return result; + } + + wire_ctx_t wire = binary_init(rdata); + wire_write_bignum_datum(&wire, point_size, &point_x); + assert(wire_ctx_offset(&wire) == rdata->size); + + return DNSSEC_EOK; +} +#endif + +/* -- crypto to DNSSEC ------------------------------------------------------*/ + +/*! + * Convert RSA key in DNSSEC format to crypto key. + */ +static int rsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t key) +{ + assert(rdata); + assert(key); + + if (rdata->size == 0) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + wire_ctx_t ctx = binary_init(rdata); + + // parse public exponent + + uint8_t exponent_size = wire_ctx_read_u8(&ctx); + if (exponent_size == 0 || wire_ctx_available(&ctx) < exponent_size) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + gnutls_datum_t exponent = wire_take_datum(&ctx, exponent_size); + + // parse modulus + + size_t modulus_size = wire_ctx_available(&ctx); + if (modulus_size == 0) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + gnutls_datum_t modulus = wire_take_datum(&ctx, modulus_size); + + assert(wire_ctx_offset(&ctx) == rdata->size); + + int result = gnutls_pubkey_import_rsa_raw(key, &modulus, &exponent); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_IMPORT_ERROR; + } + + return DNSSEC_EOK; +} + +/*! + * Get ECDSA curve based on DNSKEY RDATA size. + */ +static gnutls_ecc_curve_t ecdsa_curve_from_rdata_size(size_t rdata_size) +{ + switch (rdata_size) { + case 64: return GNUTLS_ECC_CURVE_SECP256R1; + case 96: return GNUTLS_ECC_CURVE_SECP384R1; + default: return GNUTLS_ECC_CURVE_INVALID; + } +} + +/*! + * Get EDDSA curve based on DNSKEY RDATA size. + */ +#if defined(HAVE_ED25519) || defined(HAVE_ED448) +static gnutls_ecc_curve_t eddsa_curve_from_rdata_size(size_t rdata_size) +{ + switch (rdata_size) { +#ifdef HAVE_ED25519 + case 32: return GNUTLS_ECC_CURVE_ED25519; +#endif +#ifdef HAVE_ED448 + case 57: return GNUTLS_ECC_CURVE_ED448; +#endif + default: return GNUTLS_ECC_CURVE_INVALID; + } +} +#endif + +/*! + * Convert ECDSA key in DNSSEC format to crypto key. + */ +static int ecdsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t key) +{ + assert(rdata); + assert(key); + + gnutls_ecc_curve_t curve = ecdsa_curve_from_rdata_size(rdata->size); + if (curve == GNUTLS_ECC_CURVE_INVALID) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + // parse points + + wire_ctx_t ctx = binary_init(rdata); + + size_t point_size = wire_ctx_available(&ctx) / 2; + gnutls_datum_t point_x = wire_take_datum(&ctx, point_size); + gnutls_datum_t point_y = wire_take_datum(&ctx, point_size); + assert(wire_ctx_offset(&ctx) == rdata->size); + + int result = gnutls_pubkey_import_ecc_raw(key, curve, &point_x, &point_y); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_IMPORT_ERROR; + } + + return DNSSEC_EOK; +} + +/*! + * Convert EDDSA key in DNSSEC format to crypto key. + */ +#if defined(HAVE_ED25519) || defined(HAVE_ED448) +static int eddsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t key) +{ + assert(rdata); + assert(key); + + gnutls_ecc_curve_t curve = eddsa_curve_from_rdata_size(rdata->size); + if (curve == GNUTLS_ECC_CURVE_INVALID) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + wire_ctx_t ctx = binary_init(rdata); + + size_t point_size = wire_ctx_available(&ctx); + gnutls_datum_t point_x = wire_take_datum(&ctx, point_size); + assert(wire_ctx_offset(&ctx) == rdata->size); + + int result = gnutls_pubkey_import_ecc_raw(key, curve, &point_x, NULL); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_IMPORT_ERROR; + } + + return DNSSEC_EOK; +} +#endif + +/* -- internal API --------------------------------------------------------- */ + +/*! + * Encode public key to the format used in DNSKEY RDATA. + */ +int convert_pubkey_to_dnskey(gnutls_pubkey_t key, dnssec_binary_t *rdata) +{ + assert(key); + assert(rdata); + + int algorithm = gnutls_pubkey_get_pk_algorithm(key, NULL); + if (algorithm < 0) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + switch ((gnutls_pk_algorithm_t)algorithm) { + case GNUTLS_PK_RSA: return rsa_pubkey_to_rdata(key, rdata); + case GNUTLS_PK_EC: return ecdsa_pubkey_to_rdata(key, rdata); +#ifdef HAVE_ED25519 + case GNUTLS_PK_EDDSA_ED25519: return eddsa_pubkey_to_rdata(key, rdata); +#endif +#ifdef HAVE_ED448 + case GNUTLS_PK_EDDSA_ED448: return eddsa_pubkey_to_rdata(key, rdata); +#endif + default: return DNSSEC_INVALID_KEY_ALGORITHM; + } +} + +/*! + * Create public key from the format encoded in DNSKEY RDATA. + */ +int convert_dnskey_to_pubkey(uint8_t algorithm, const dnssec_binary_t *rdata, + gnutls_pubkey_t key) +{ + assert(rdata); + assert(key); + + gnutls_pk_algorithm_t gnutls_alg = algorithm_to_gnutls(algorithm); + + switch(gnutls_alg) { + case GNUTLS_PK_RSA: return rsa_rdata_to_pubkey(rdata, key); + case GNUTLS_PK_EC: return ecdsa_rdata_to_pubkey(rdata, key); +#ifdef HAVE_ED25519 + case GNUTLS_PK_EDDSA_ED25519: return eddsa_rdata_to_pubkey(rdata, key); +#endif +#ifdef HAVE_ED448 + case GNUTLS_PK_EDDSA_ED448: return eddsa_rdata_to_pubkey(rdata, key); +#endif + default: return DNSSEC_INVALID_KEY_ALGORITHM; + } +} diff --git a/src/libdnssec/key/convert.h b/src/libdnssec/key/convert.h new file mode 100644 index 0000000..079a5f6 --- /dev/null +++ b/src/libdnssec/key/convert.h @@ -0,0 +1,44 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gnutls/abstract.h> + +#include "libdnssec/binary.h" +#include "libdnssec/key.h" + +/*! + * Encode public key into the format used in DNSKEY RDATA. + * + * \param[in] key Public key to be encoded. + * \param[out] rdata Encoded key (allocated). + * + * \return Error code, DNSSEC_EOK if successful. + */ +int convert_pubkey_to_dnskey(gnutls_pubkey_t key, dnssec_binary_t *rdata); + +/*! + * Create public key from the format encoded in DNSKEY RDATA. + * + * \param[in] algorithm DNSSEC algorithm identification. + * \param[in] rdata Public key in DNSKEY RDATA format. + * \param[out] key GnuTLS public key (initialized). + * + * \return Error code, DNSSEC_EOK if successful. + */ +int convert_dnskey_to_pubkey(uint8_t algorithm, const dnssec_binary_t *rdata, + gnutls_pubkey_t key); diff --git a/src/libdnssec/key/dnskey.c b/src/libdnssec/key/dnskey.c new file mode 100644 index 0000000..6360700 --- /dev/null +++ b/src/libdnssec/key/dnskey.c @@ -0,0 +1,91 @@ +/* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + */ + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key/dnskey.h" +#include "libdnssec/key/convert.h" +#include "libdnssec/shared/binary_wire.h" + +/* -- internal API --------------------------------------------------------- */ + +/*! + * Update 'Public key' field of DNSKEY RDATA. + */ +int dnskey_rdata_set_pubkey(dnssec_binary_t *rdata, const dnssec_binary_t *pubkey) +{ + assert(rdata); + assert(pubkey); + + size_t new_size = DNSKEY_RDATA_OFFSET_PUBKEY + pubkey->size; + int result = dnssec_binary_resize(rdata, new_size); + if (result != DNSSEC_EOK) { + return result; + } + + wire_ctx_t wire = binary_init(rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PUBKEY); + binary_write(&wire, pubkey); + assert(wire_ctx_offset(&wire) == rdata->size); + + return DNSSEC_EOK; +} + +/*! + * Create a GnuTLS public key from DNSKEY RDATA. + * + * \param rdata DNSKEY RDATA. + * \param key_ptr Resulting public key. + */ +int dnskey_rdata_to_crypto_key(const dnssec_binary_t *rdata, gnutls_pubkey_t *key_ptr) +{ + assert(rdata); + assert(key_ptr); + + uint8_t algorithm = 0, protocol = 0, flags_hi = 0; + dnssec_binary_t rdata_pubkey = { 0 }; + + wire_ctx_t wire = binary_init(rdata); + + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_FLAGS); + flags_hi = wire_ctx_read_u8(&wire); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PROTOCOL); + protocol = wire_ctx_read_u8(&wire); + if (flags_hi != 0x1 || protocol != 0x3) { + return DNSSEC_INVALID_PUBLIC_KEY; + } + + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_ALGORITHM); + algorithm = wire_ctx_read_u8(&wire); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PUBKEY); + binary_available(&wire, &rdata_pubkey); + + gnutls_pubkey_t key = NULL; + int result = gnutls_pubkey_init(&key); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_ENOMEM; + } + + result = convert_dnskey_to_pubkey(algorithm, &rdata_pubkey, key); + if (result != DNSSEC_EOK) { + gnutls_pubkey_deinit(key); + return result; + } + + *key_ptr = key; + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/key/dnskey.h b/src/libdnssec/key/dnskey.h new file mode 100644 index 0000000..e765046 --- /dev/null +++ b/src/libdnssec/key/dnskey.h @@ -0,0 +1,46 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gnutls/abstract.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" + +/*! + * DNSKEY RDATA fields offsets. + * + * \see RFC 4034 (section 2.1) + */ +enum dnskey_rdata_offsets { + DNSKEY_RDATA_OFFSET_FLAGS = 0, + DNSKEY_RDATA_OFFSET_PROTOCOL = 2, + DNSKEY_RDATA_OFFSET_ALGORITHM = 3, + DNSKEY_RDATA_OFFSET_PUBKEY = 4, +}; + +/*! + * Update 'Public key' field of DNSKEY RDATA. + */ +int dnskey_rdata_set_pubkey(dnssec_binary_t *rdata, + const dnssec_binary_t *pubkey); + +/*! + * Create a GnuTLS public key from DNSKEY RDATA. + */ +int dnskey_rdata_to_crypto_key(const dnssec_binary_t *rdata, + gnutls_pubkey_t *key_ptr); diff --git a/src/libdnssec/key/ds.c b/src/libdnssec/key/ds.c new file mode 100644 index 0000000..ad580ad --- /dev/null +++ b/src/libdnssec/key/ds.c @@ -0,0 +1,128 @@ +/* Copyright (C) 2020 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 <https://www.gnu.org/licenses/>. + */ + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/key/internal.h" +#include "libdnssec/shared/dname.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/shared/binary_wire.h" + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +/*! + * Convert DNSSEC DS digest algorithm to GnuTLS digest algorithm. + */ +static gnutls_digest_algorithm_t lookup_algorithm(dnssec_key_digest_t algorithm) +{ + switch (algorithm) { + case DNSSEC_KEY_DIGEST_SHA1: return GNUTLS_DIG_SHA1; + case DNSSEC_KEY_DIGEST_SHA256: return GNUTLS_DIG_SHA256; + case DNSSEC_KEY_DIGEST_SHA384: return GNUTLS_DIG_SHA384; + default: + return GNUTLS_DIG_UNKNOWN; + }; +} + +_public_ +bool dnssec_algorithm_digest_support(dnssec_key_digest_t algorithm) +{ + /* GnuTLS docs: + * > It is not possible to query for insecure hash algorithms directly + * > (only indirectly through the signature API). + * So let's query combining the hash with RSA. + */ + gnutls_sign_algorithm_t rsa; + switch (algorithm) { + case DNSSEC_KEY_DIGEST_SHA1: rsa = GNUTLS_SIGN_RSA_SHA1; break; + case DNSSEC_KEY_DIGEST_SHA256: rsa = GNUTLS_SIGN_RSA_SHA256; break; + case DNSSEC_KEY_DIGEST_SHA384: rsa = GNUTLS_SIGN_RSA_SHA384; break; + default: + return false; + }; + return gnutls_sign_is_secure(rsa); +} + +static void wire_write_digest(wire_ctx_t *wire, + gnutls_hash_hd_t digest, int digest_size) +{ + assert(wire_ctx_available(wire) >= digest_size); + gnutls_hash_output(digest, wire->position); + wire->position += digest_size; +} + +_public_ +int dnssec_key_create_ds(const dnssec_key_t *key, + dnssec_key_digest_t ds_algorithm, + dnssec_binary_t *rdata_ptr) +{ + if (!key || !rdata_ptr) { + return DNSSEC_EINVAL; + } + + if (!key->dname) { + return DNSSEC_INVALID_KEY_NAME; + } + + if (!key->public_key){ + return DNSSEC_INVALID_PUBLIC_KEY; + } + + gnutls_digest_algorithm_t algorithm = lookup_algorithm(ds_algorithm); + if (algorithm == GNUTLS_DIG_UNKNOWN) { + return DNSSEC_INVALID_DS_ALGORITHM; + } + + // compute DS hash + + _cleanup_hash_ gnutls_hash_hd_t digest = NULL; + int r = gnutls_hash_init(&digest, algorithm); + if (r < 0) { + return DNSSEC_DS_HASHING_ERROR; + } + + if (gnutls_hash(digest, key->dname, dname_length(key->dname)) != 0 || + gnutls_hash(digest, key->rdata.data, key->rdata.size) != 0 + ) { + return DNSSEC_DS_HASHING_ERROR; + } + + // build DS RDATA + + int digest_size = gnutls_hash_get_len(algorithm); + if (digest_size == 0) { + return DNSSEC_DS_HASHING_ERROR; + } + + dnssec_binary_t rdata = { 0 }; + r = dnssec_binary_alloc(&rdata, 4 + digest_size); + if (r != DNSSEC_EOK) { + return r; + } + + wire_ctx_t wire = binary_init(&rdata); + wire_ctx_write_u16(&wire, dnssec_key_get_keytag(key)); + wire_ctx_write_u8(&wire, dnssec_key_get_algorithm(key)); + wire_ctx_write_u8(&wire, ds_algorithm); + wire_write_digest(&wire, digest, digest_size); + assert(wire_ctx_offset(&wire) == wire.size); + + *rdata_ptr = rdata; + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/key/internal.h b/src/libdnssec/key/internal.h new file mode 100644 index 0000000..550e454 --- /dev/null +++ b/src/libdnssec/key/internal.h @@ -0,0 +1,35 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gnutls/abstract.h> +#include <stdint.h> + +#include "libdnssec/key.h" +#include "libdnssec/shared/dname.h" + +/*! + * DNSSEC key. + */ +struct dnssec_key { + uint8_t *dname; + dnssec_binary_t rdata; + + gnutls_pubkey_t public_key; + gnutls_privkey_t private_key; + unsigned bits; +}; diff --git a/src/libdnssec/key/key.c b/src/libdnssec/key/key.c new file mode 100644 index 0000000..f363167 --- /dev/null +++ b/src/libdnssec/key/key.c @@ -0,0 +1,439 @@ +/* Copyright (C) 2019 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 <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <gnutls/abstract.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/key/algorithm.h" +#include "libdnssec/key/convert.h" +#include "libdnssec/key/dnskey.h" +#include "libdnssec/key/internal.h" +#include "libdnssec/shared/keyid_gnutls.h" +#include "libdnssec/keystore.h" +#include "libdnssec/keytag.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/shared/binary_wire.h" +#include "contrib/wire_ctx.h" + +/*! + * Minimal size of DNSKEY RDATA. + */ +#define DNSKEY_RDATA_MIN_SIZE DNSKEY_RDATA_OFFSET_PUBKEY + +/*! + * RDATA template for newly allocated keys. + */ +static const dnssec_binary_t DNSKEY_RDATA_TEMPLATE = { + .size = 4, + .data = (uint8_t []) { 0x01, 0x00, 0x03, 0x00 } +}; + +/* -- key allocation ------------------------------------------------------- */ + +_public_ +int dnssec_key_new(dnssec_key_t **key_ptr) +{ + if (!key_ptr) { + return DNSSEC_EINVAL; + } + + dnssec_key_t *key = calloc(1, sizeof(*key)); + if (!key) { + return DNSSEC_ENOMEM; + } + + int r = dnssec_binary_dup(&DNSKEY_RDATA_TEMPLATE, &key->rdata); + if (r != DNSSEC_EOK) { + free(key); + return DNSSEC_ENOMEM; + } + + *key_ptr = key; + + return DNSSEC_EOK; +} + +/*! + * Clear allocated fields inside the key structure, except RDATA. + */ +static void key_free_internals(dnssec_key_t *key) +{ + assert(key); + + free(key->dname); + key->dname = NULL; + + gnutls_privkey_deinit(key->private_key); + key->private_key = NULL; + + gnutls_pubkey_deinit(key->public_key); + key->public_key = NULL; +} + +_public_ +void dnssec_key_clear(dnssec_key_t *key) +{ + if (!key) { + return; + } + + // reuse RDATA + dnssec_binary_t rdata = key->rdata; + + // clear the structure + key_free_internals(key); + clear_struct(key); + + // restore template RDATA (downsize, no need to realloc) + assert(rdata.size >= DNSKEY_RDATA_MIN_SIZE); + rdata.size = DNSKEY_RDATA_MIN_SIZE; + memmove(rdata.data, DNSKEY_RDATA_TEMPLATE.data, rdata.size); + + key->rdata = rdata; +} + +_public_ +void dnssec_key_free(dnssec_key_t *key) +{ + if (!key) { + return; + } + + key_free_internals(key); + dnssec_binary_free(&key->rdata); + + free(key); +} + +_public_ +dnssec_key_t *dnssec_key_dup(const dnssec_key_t *key) +{ + if (!key) { + return NULL; + } + + dnssec_key_t *dup = NULL; + + if (dnssec_key_new(&dup) != DNSSEC_EOK || + dnssec_key_set_dname(dup, key->dname) != DNSSEC_EOK || + dnssec_key_set_rdata(dup, &key->rdata) != DNSSEC_EOK + ) { + dnssec_key_free(dup); + return NULL; + } + + return dup; +} + +/* -- freely modifiable attributes ----------------------------------------- */ + +_public_ +const uint8_t *dnssec_key_get_dname(const dnssec_key_t *key) +{ + if (!key) { + return NULL; + } + + return key->dname; +} + +_public_ +int dnssec_key_set_dname(dnssec_key_t *key, const uint8_t *dname) +{ + if (!key) { + return DNSSEC_EINVAL; + } + + uint8_t *copy = NULL; + if (dname) { + copy = dname_copy(dname); + if (!copy) { + return DNSSEC_ENOMEM; + } + + dname_normalize(copy); + } + + free(key->dname); + key->dname = copy; + + return DNSSEC_EOK; +} + +_public_ +uint16_t dnssec_key_get_flags(const dnssec_key_t *key) +{ + if (!key) { + return 0; + } + + wire_ctx_t wire = binary_init(&key->rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_FLAGS); + return wire_ctx_read_u16(&wire); +} + +_public_ +int dnssec_key_set_flags(dnssec_key_t *key, uint16_t flags) +{ + if (!key) { + return DNSSEC_EINVAL; + } + + wire_ctx_t wire = binary_init(&key->rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_FLAGS); + wire_ctx_write_u16(&wire, flags); + + return DNSSEC_EOK; +} + +_public_ +uint8_t dnssec_key_get_protocol(const dnssec_key_t *key) +{ + if (!key) { + return 0; + } + + wire_ctx_t wire = binary_init(&key->rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PROTOCOL); + return wire_ctx_read_u8(&wire); +} + +_public_ +int dnssec_key_set_protocol(dnssec_key_t *key, uint8_t protocol) +{ + if (!key) { + return DNSSEC_EINVAL; + } + + wire_ctx_t wire = binary_init(&key->rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PROTOCOL); + wire_ctx_write_u8(&wire, protocol); + + return DNSSEC_EOK; +} + +/* -- restricted attributes ------------------------------------------------ */ + +_public_ +uint16_t dnssec_key_get_keytag(const dnssec_key_t *key) +{ + uint16_t keytag = 0; + if (dnssec_key_can_verify(key)) { + dnssec_keytag(&key->rdata, &keytag); + } + + return keytag; +} + +/*! + * Check if current public key algorithm matches with the new algorithm. + */ +static bool can_change_algorithm(dnssec_key_t *key, uint8_t algorithm) +{ + assert(key); + + if (!key->public_key) { + return true; + } + + gnutls_pk_algorithm_t update = algorithm_to_gnutls(algorithm); + if (update == GNUTLS_PK_UNKNOWN) { + return false; + } + + int current = gnutls_pubkey_get_pk_algorithm(key->public_key, NULL); + assert(current >= 0); + + return current == update; +} + +_public_ +uint8_t dnssec_key_get_algorithm(const dnssec_key_t *key) +{ + if (!key) { + return 0; + } + + wire_ctx_t wire = binary_init(&key->rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_ALGORITHM); + return wire_ctx_read_u8(&wire); +} + +_public_ +int dnssec_key_set_algorithm(dnssec_key_t *key, uint8_t algorithm) +{ + if (!key) { + return DNSSEC_EINVAL; + } + + if (!can_change_algorithm(key, algorithm)) { + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + wire_ctx_t wire = binary_init(&key->rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_ALGORITHM); + wire_ctx_write_u8(&wire, algorithm); + + return DNSSEC_EOK; +} + +_public_ +int dnssec_key_get_pubkey(const dnssec_key_t *key, dnssec_binary_t *pubkey) +{ + if (!key || !pubkey) { + return DNSSEC_EINVAL; + } + + wire_ctx_t wire = binary_init(&key->rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PUBKEY); + binary_available(&wire, pubkey); + + return DNSSEC_EOK; +} + +_public_ +int dnssec_key_set_pubkey(dnssec_key_t *key, const dnssec_binary_t *pubkey) +{ + if (!key || !pubkey || !pubkey->data) { + return DNSSEC_EINVAL; + } + + if (key->public_key) { + return DNSSEC_KEY_ALREADY_PRESENT; + } + + if (dnssec_key_get_algorithm(key) == 0) { + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + int result = dnskey_rdata_set_pubkey(&key->rdata, pubkey); + if (result != DNSSEC_EOK) { + return result; + } + + result = dnskey_rdata_to_crypto_key(&key->rdata, &key->public_key); + if (result != DNSSEC_EOK) { + key->rdata.size = DNSKEY_RDATA_OFFSET_PUBKEY; // downsize + return result; + } + + return DNSSEC_EOK; +} + +_public_ +unsigned dnssec_key_get_size(const dnssec_key_t *key) +{ + if (!key || !key->public_key) { + return 0; + } + + unsigned bits = 0; + uint8_t algorithm = dnssec_key_get_algorithm(key); + switch (algorithm) { + case 13: + bits = 256; + break; + case 14: + bits = 384; + break; + case 15: + bits = 256; + break; + case 16: + bits = 456; + break; + default: + gnutls_pubkey_get_pk_algorithm(key->public_key, &bits); + } + + return bits; +} + +_public_ +int dnssec_key_get_keyid(const dnssec_key_t *key, char **id) +{ + if (!key || !id) { + return DNSSEC_EINVAL; + } + + return keyid_pubkey_hex(key->public_key, id); +} + +_public_ +int dnssec_key_get_rdata(const dnssec_key_t *key, dnssec_binary_t *rdata) +{ + if (!key || !rdata) { + return DNSSEC_EINVAL; + } + + *rdata = key->rdata; + + return DNSSEC_EOK; +} + +_public_ +int dnssec_key_set_rdata(dnssec_key_t *key, const dnssec_binary_t *rdata) +{ + if (!key || !rdata || !rdata->data) { + return DNSSEC_EINVAL; + } + + if (rdata->size < DNSKEY_RDATA_MIN_SIZE) { + return DNSSEC_MALFORMED_DATA; + } + + if (key->public_key) { + return DNSSEC_KEY_ALREADY_PRESENT; + } + + gnutls_pubkey_t new_pubkey = NULL; + int result = dnskey_rdata_to_crypto_key(rdata, &new_pubkey); + if (result != DNSSEC_EOK) { + return result; + } + + result = dnssec_binary_resize(&key->rdata, rdata->size); + if (result != DNSSEC_EOK) { + gnutls_pubkey_deinit(new_pubkey); + return result; + } + + // commit result + memmove(key->rdata.data, rdata->data, rdata->size); + key->public_key = new_pubkey; + + return DNSSEC_EOK; +} + +/* -- key presence checking ------------------------------------------------ */ + +_public_ +bool dnssec_key_can_sign(const dnssec_key_t *key) +{ + return key && key->private_key; +} + +_public_ +bool dnssec_key_can_verify(const dnssec_key_t *key) +{ + return key && key->public_key; +} diff --git a/src/libdnssec/key/keytag.c b/src/libdnssec/key/keytag.c new file mode 100644 index 0000000..a14353e --- /dev/null +++ b/src/libdnssec/key/keytag.c @@ -0,0 +1,88 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdint.h> +#include <string.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key/dnskey.h" +#include "libdnssec/shared/shared.h" + +/*! + * Compute keytag for RSA/MD5 key. + * + * \see RFC 2537 (section 2), RFC 4034 (appendix B.1) + */ +static uint16_t keytag_compat(const dnssec_binary_t *rdata) +{ + assert(rdata); + assert(rdata->data); + + if (rdata->size < 9) { // in fact, the condition could be stricter + return 0; + } + + uint8_t msb = rdata->data[rdata->size - 3]; + uint8_t lsb = rdata->data[rdata->size - 2]; + + return (msb << 8) + lsb; +} + +/*! + * Compute keytag for other than RSA/MD5 key. + * + * \see RFC 4034 (appendix B) + */ +static uint16_t keytag_current(const dnssec_binary_t *rdata) +{ + assert(rdata); + assert(rdata->data); + + uint32_t ac = 0; + for (int i = 0; i < rdata->size; i++) { + ac += (i & 1) ? rdata->data[i] : rdata->data[i] << 8; + } + + return (ac >> 16) + ac; +} + +/* -- public API ----------------------------------------------------------- */ + +/*! + * Compute keytag for a DNSSEC key. + */ +_public_ +int dnssec_keytag(const dnssec_binary_t *rdata, uint16_t *keytag) +{ + if (!rdata || !keytag) { + return DNSSEC_EINVAL; + } + + if (!rdata->data || rdata->size < DNSKEY_RDATA_OFFSET_PUBKEY) { + return DNSSEC_MALFORMED_DATA; + } + + uint8_t algorithm = rdata->data[DNSKEY_RDATA_OFFSET_ALGORITHM]; + if (algorithm == 1) { + *keytag = keytag_compat(rdata); + } else { + *keytag = keytag_current(rdata); + } + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/key/privkey.c b/src/libdnssec/key/privkey.c new file mode 100644 index 0000000..abe968a --- /dev/null +++ b/src/libdnssec/key/privkey.c @@ -0,0 +1,140 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#include <gnutls/abstract.h> +#include <gnutls/gnutls.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key/algorithm.h" +#include "libdnssec/key/convert.h" +#include "libdnssec/key/dnskey.h" +#include "libdnssec/key/internal.h" +#include "libdnssec/key/privkey.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/shared/binary_wire.h" + +/* -- internal functions --------------------------------------------------- */ + +/*! + * Check if the algorithm number is valid for given DNSKEY. + */ +static bool valid_algorithm(dnssec_key_t *key, gnutls_privkey_t privkey) +{ + uint8_t current = dnssec_key_get_algorithm(key); + int gnu_algorithm = gnutls_privkey_get_pk_algorithm(privkey, NULL); + + return (gnu_algorithm == algorithm_to_gnutls(current)); +} + +/*! + * Create GnuTLS public key from private key. + */ +static int public_from_private(gnutls_privkey_t privkey, gnutls_pubkey_t *pubkey) +{ + assert(privkey); + assert(pubkey); + + gnutls_pubkey_t new_key = NULL; + int result = gnutls_pubkey_init(&new_key); + if (result != GNUTLS_E_SUCCESS) { + return DNSSEC_ENOMEM; + } + + result = gnutls_pubkey_import_privkey(new_key, privkey, 0, 0); + if (result != GNUTLS_E_SUCCESS) { + gnutls_pubkey_deinit(new_key); + return DNSSEC_KEY_IMPORT_ERROR; + } + + *pubkey = new_key; + + return DNSSEC_EOK; +} + +/*! + * Create public key (GnuTLS and DNSKEY RDATA) from a private key. + */ +static int create_public_key(gnutls_privkey_t privkey, + gnutls_pubkey_t *pubkey_ptr, + dnssec_binary_t *rdata) +{ + assert(privkey); + assert(pubkey_ptr); + assert(rdata); + + // crypto public key + + gnutls_pubkey_t pubkey = NULL; + int result = public_from_private(privkey, &pubkey); + if (result != DNSSEC_EOK) { + return result; + } + + // dnssec public key + + _cleanup_binary_ dnssec_binary_t rdata_pubkey = { 0 }; + result = convert_pubkey_to_dnskey(pubkey, &rdata_pubkey); + if (result != DNSSEC_EOK) { + gnutls_pubkey_deinit(pubkey); + return result; + } + + size_t rdata_size = DNSKEY_RDATA_OFFSET_PUBKEY + rdata_pubkey.size; + result = dnssec_binary_resize(rdata, rdata_size); + if (result != DNSSEC_EOK) { + gnutls_pubkey_deinit(pubkey); + return result; + } + + // updated RDATA + + wire_ctx_t wire = binary_init(rdata); + wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PUBKEY); + binary_write(&wire, &rdata_pubkey); + assert(wire_ctx_offset(&wire) == rdata->size); + + *pubkey_ptr = pubkey; + + return DNSSEC_EOK; +} + +/* -- internal API --------------------------------------------------------- */ + +/*! + * Load a private key into a DNSSEC key, create a public part if necessary. + */ +int key_set_private_key(dnssec_key_t *key, gnutls_privkey_t privkey) +{ + assert(key); + assert(privkey); + assert(key->private_key == NULL); + + if (!valid_algorithm(key, privkey)) { + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + if (!key->public_key) { + int r = create_public_key(privkey, &key->public_key, &key->rdata); + if (r != DNSSEC_EOK) { + return r; + } + } + + key->private_key = privkey; + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/key/privkey.h b/src/libdnssec/key/privkey.h new file mode 100644 index 0000000..8afe7c9 --- /dev/null +++ b/src/libdnssec/key/privkey.h @@ -0,0 +1,35 @@ +/* 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 <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gnutls/abstract.h> + +#include "libdnssec/key.h" + +/*! + * Load a private key into a DNSSEC key, create a public part if necessary. + * + * If the public key is not loaded, at least an algorithm must be set. + * + * Updates private key, public key, RDATA, and key identifiers. + * + * \param key DNSSEC key to be updated. + * \param privkey Private key to be set. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int key_set_private_key(dnssec_key_t *key, gnutls_privkey_t privkey); diff --git a/src/libdnssec/key/simple.c b/src/libdnssec/key/simple.c new file mode 100644 index 0000000..10126cc --- /dev/null +++ b/src/libdnssec/key/simple.c @@ -0,0 +1,55 @@ +/* Copyright (C) 2019 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 <https://www.gnu.org/licenses/>. + */ + +#include <gnutls/abstract.h> +#include <gnutls/gnutls.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/key/dnskey.h" +#include "libdnssec/key/internal.h" +#include "libdnssec/key/privkey.h" +#include "libdnssec/pem.h" +#include "libdnssec/shared/shared.h" + +/* -- public API ----------------------------------------------------------- */ + +_public_ +int dnssec_key_load_pkcs8(dnssec_key_t *key, const dnssec_binary_t *pem) +{ + if (!key || !pem || !pem->data) { + return DNSSEC_EINVAL; + } + + if (dnssec_key_get_algorithm(key) == 0) { + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + gnutls_privkey_t privkey = NULL; + int r = dnssec_pem_to_privkey(pem, &privkey); + if (r != DNSSEC_EOK) { + return r; + } + + r = key_set_private_key(key, privkey); + if (r != DNSSEC_EOK) { + gnutls_privkey_deinit(privkey); + return r; + } + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/keyid.c b/src/libdnssec/keyid.c new file mode 100644 index 0000000..5a6f29d --- /dev/null +++ b/src/libdnssec/keyid.c @@ -0,0 +1,87 @@ +/* Copyright (C) 2019 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 <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <string.h> + +#include "libdnssec/error.h" +#include "libdnssec/keyid.h" +#include "libdnssec/shared/shared.h" + +#include "contrib/ctype.h" +#include "contrib/tolower.h" + +/* -- public API ----------------------------------------------------------- */ + +_public_ +bool dnssec_keyid_is_valid(const char *id) +{ + if (!id) { + return false; + } + + if (strlen(id) % 2 != 0) { + return false; + } + + for (int i = 0; id[i] != '\0'; i++) { + if (!is_xdigit(id[i])) { + return false; + } + } + + return true; +} + +_public_ +void dnssec_keyid_normalize(char *id) +{ + if (!id) { + return; + } + + for (size_t i = 0; id[i] != '\0'; i++) { + assert(id[i] != '\0' && is_xdigit(id[i])); + id[i] = knot_tolower(id[i]); + } +} + +_public_ +char *dnssec_keyid_copy(const char *id) +{ + if (!id) { + return NULL; + } + + char *copy = strdup(id); + if (!copy) { + return NULL; + } + + dnssec_keyid_normalize(copy); + + return copy; +} + +_public_ +bool dnssec_keyid_equal(const char *one, const char *two) +{ + if (!one || !two) { + return NULL; + } + + return (strcasecmp(one, two) == 0); +} diff --git a/src/libdnssec/keyid.h b/src/libdnssec/keyid.h new file mode 100644 index 0000000..c90bb1c --- /dev/null +++ b/src/libdnssec/keyid.h @@ -0,0 +1,64 @@ +/* Copyright (C) 2020 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 <https://www.gnu.org/licenses/>. + */ + +/*! + * \file + * + * \addtogroup keyid + * + * \brief DNSSEC key ID manipulation. + * + * The module contains auxiliary functions for manipulation with key IDs. + * + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <stdbool.h> + +/*! + * Length of the key ID in presentation form (ASCII). + */ +#define DNSSEC_KEYID_SIZE 40 + +/*! + * Length of the key ID in internal form (binary). + */ +#define DNSSEC_KEYID_BINARY_SIZE 20 + +/*! + * Check if a provided string is a valid key ID string. + */ +bool dnssec_keyid_is_valid(const char *id); + +/*! + * Normalize the key ID string. + */ +void dnssec_keyid_normalize(char *id); + +/*! + * Create a normalized copy if the key ID. + */ +char *dnssec_keyid_copy(const char *id); + +/*! + * Check if two key IDs are equal. + */ +bool dnssec_keyid_equal(const char *one, const char *two); + +/*! @} */ diff --git a/src/libdnssec/keystore.h b/src/libdnssec/keystore.h new file mode 100644 index 0000000..8697935 --- /dev/null +++ b/src/libdnssec/keystore.h @@ -0,0 +1,155 @@ +/* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + */ + +/*! + * \file + * + * \addtogroup keystore + * + * \brief Private key store access. + * + * The module provides abstraction for private key store. Basically, PKCS #8 + * and PKCS #11 interfaces are supported. + * + * PKCS #8 uses unencrypted PEM. + * + * PKCS #11 provides access Hardware Security Modules. + * + * @{ + */ + +#pragma once + +#include <libdnssec/binary.h> +#include <libdnssec/key.h> + +struct dnssec_keystore; + +/*! + * DNSSEC private keys store. + */ +typedef struct dnssec_keystore dnssec_keystore_t; + +/*! + * Create default PKCS #8 private key store context. + * + * The default store maintains the private keys in one directory on the file + * system. The private keys are stored in unencrypted PEM format, named + * key-id.pem. The configuration string is a path to the directory. + * + * \param[out] store Opened key store. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_init_pkcs8(dnssec_keystore_t **store); + +/*! + * Crate new PKCS #11 private key store context. + * + * \param[out] store Opened key store. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_init_pkcs11(dnssec_keystore_t **store); + +/*! + * Deinitialize private key store context. + * + * \param store Key store to be deinitialized. + */ +int dnssec_keystore_deinit(dnssec_keystore_t *store); + +/*! + * Initialize new private key store. + */ +int dnssec_keystore_init(dnssec_keystore_t *store, const char *config); + +/*! + * Open private key store. + */ +int dnssec_keystore_open(dnssec_keystore_t *store, const char *config); + +/*! + * Close private key store. + * + * \param store Key store to be closed. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_close(dnssec_keystore_t *store); + +/*! + * Generate a new key in the key store. + * + * \param[in] store Key store. + * \param[in] algorithm Algorithm. + * \param[in] bits Bit length of the key to be generated. + * \param[in] label Optional key label for PKCS #11. + * \param[out] id_ptr ID of the generated key. Must be freed by the caller. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_generate(dnssec_keystore_t *store, + dnssec_key_algorithm_t algorithm, + unsigned bits, const char *label, char **id_ptr); + +/*! + * Import an existing key into the key store. + * + * \param[in] store Key store. + * \param[in] pem Private key material in PEM format. + * \param[out] id_ptr ID of the imported key. Must be freed by the caller. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_import(dnssec_keystore_t *store, const dnssec_binary_t *pem, + char **id_ptr); + +/*! + * Remove a private key from the key store. + * + * \param store Key store. + * \param id ID of the private key to be deleted. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_remove(dnssec_keystore_t *store, const char *id); + +/*! + * Export private key from the key store into a DNSSEC key. + * + * The key algorithm has to be set before calling this function. + * + * \param store Private key store. + * \param id ID of the key. + * \param key DNSSEC key to be initialized. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_get_private(dnssec_keystore_t *store, const char *id, + dnssec_key_t *key); + +/*! + * Import a DNSSEC private key into key store. + * + * \param store Key store. + * \param key DNSSEC key with a private key. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_keystore_set_private(dnssec_keystore_t *store, dnssec_key_t *key); + +/*! @} */ diff --git a/src/libdnssec/keystore/internal.h b/src/libdnssec/keystore/internal.h new file mode 100644 index 0000000..5afa8ce --- /dev/null +++ b/src/libdnssec/keystore/internal.h @@ -0,0 +1,50 @@ +/* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + */ + +#pragma once + +#include <gnutls/gnutls.h> +#include <gnutls/abstract.h> + +#include "libdnssec/binary.h" +#include "libdnssec/key.h" +#include "libdnssec/keystore.h" + +typedef struct keystore_functions { + // construction of internal context + int (*ctx_new)(void **ctx_ptr); + void (*ctx_free)(void *ctx); + // keystore init/open/close + int (*init)(void *ctx, const char *config); + int (*open)(void *ctx, const char *config); + int (*close)(void *ctx); + // keystore access + int (*generate_key)(void *ctx, gnutls_pk_algorithm_t algorithm, + unsigned bits, const char *label, char **id_ptr); + int (*import_key)(void *ctx, const dnssec_binary_t *pem, char **id_ptr); + int (*remove_key)(void *ctx, const char *id); + // private key access + int (*get_private)(void *ctx, const char *id, gnutls_privkey_t *key_ptr); + int (*set_private)(void *ctx, gnutls_privkey_t key); +} keystore_functions_t; + +struct dnssec_keystore { + const keystore_functions_t *functions; + void *ctx; +}; + +int keystore_create(dnssec_keystore_t **store_ptr, + const keystore_functions_t *functions); diff --git a/src/libdnssec/keystore/keystore.c b/src/libdnssec/keystore/keystore.c new file mode 100644 index 0000000..f88dd14 --- /dev/null +++ b/src/libdnssec/keystore/keystore.c @@ -0,0 +1,186 @@ +/* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <stdlib.h> + +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/key/algorithm.h" +#include "libdnssec/key/dnskey.h" +#include "libdnssec/key/internal.h" +#include "libdnssec/key/privkey.h" +#include "libdnssec/keyid.h" +#include "libdnssec/keystore.h" +#include "libdnssec/keystore/internal.h" +#include "libdnssec/shared/shared.h" + +/* -- internal API --------------------------------------------------------- */ + +int keystore_create(dnssec_keystore_t **store_ptr, + const keystore_functions_t *functions) +{ + assert(store_ptr); + assert(functions); + + dnssec_keystore_t *store = calloc(1, sizeof(*store)); + if (!store) { + return DNSSEC_ENOMEM; + } + + store->functions = functions; + + int result = functions->ctx_new(&store->ctx); + if (result != DNSSEC_EOK) { + free(store); + return DNSSEC_ENOMEM; + } + + *store_ptr = store; + return DNSSEC_EOK; +} + +/* -- public API ----------------------------------------------------------- */ + +_public_ +int dnssec_keystore_deinit(dnssec_keystore_t *store) +{ + if (!store) { + return DNSSEC_EINVAL; + } + + dnssec_keystore_close(store); + store->functions->ctx_free(store->ctx); + + free(store); + + return DNSSEC_EOK; +} + +_public_ +int dnssec_keystore_init(dnssec_keystore_t *store, const char *config) +{ + if (!store) { + return DNSSEC_EINVAL; + } + + return store->functions->init(store->ctx, config); +} + +_public_ +int dnssec_keystore_open(dnssec_keystore_t *store, const char *config) +{ + if (!store) { + return DNSSEC_EINVAL; + } + + return store->functions->open(store->ctx, config); +} + +_public_ +int dnssec_keystore_close(dnssec_keystore_t *store) +{ + if (!store) { + return DNSSEC_EINVAL; + } + + return store->functions->close(store->ctx); +} + +_public_ +int dnssec_keystore_generate(dnssec_keystore_t *store, + dnssec_key_algorithm_t _algorithm, + unsigned bits, const char *label, char **id_ptr) +{ + if (!store || !_algorithm || !id_ptr) { + return DNSSEC_EINVAL; + } + + // prepare parameters + + gnutls_pk_algorithm_t algorithm = algorithm_to_gnutls(_algorithm); + if (algorithm == GNUTLS_PK_UNKNOWN) { + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + if (!dnssec_algorithm_key_size_check(_algorithm, bits)) { + return DNSSEC_INVALID_KEY_SIZE; + } + + return store->functions->generate_key(store->ctx, algorithm, bits, label, id_ptr); +} + +_public_ +int dnssec_keystore_import(dnssec_keystore_t *store, const dnssec_binary_t *pem, + char **id_ptr) +{ + if (!store || !pem || !id_ptr) { + return DNSSEC_EINVAL; + } + + return store->functions->import_key(store->ctx, pem, id_ptr); +} + +_public_ +int dnssec_keystore_remove(dnssec_keystore_t *store, const char *id) +{ + if (!store || !id) { + return DNSSEC_EINVAL; + } + + return store->functions->remove_key(store->ctx, id); +} + +_public_ +int dnssec_keystore_get_private(dnssec_keystore_t *store, const char *id, + dnssec_key_t *key) +{ + if (!store || !id || dnssec_key_get_algorithm(key) == 0 || !key) { + return DNSSEC_EINVAL; + } + + if (key->private_key) { + return DNSSEC_KEY_ALREADY_PRESENT; + } + + gnutls_privkey_t privkey = NULL; + int r = store->functions->get_private(store->ctx, id, &privkey); + if (r != DNSSEC_EOK) { + return r; + } + + r = key_set_private_key(key, privkey); + if (r != DNSSEC_EOK) { + gnutls_privkey_deinit(privkey); + return r; + } + + return DNSSEC_EOK; +} + +_public_ +int dnssec_keystore_set_private(dnssec_keystore_t *store, dnssec_key_t *key) +{ + if (!store || !key) { + return DNSSEC_EINVAL; + } + + if (!key->private_key) { + return DNSSEC_NO_PRIVATE_KEY; + } + + return store->functions->set_private(store->ctx, key->private_key); +} diff --git a/src/libdnssec/keystore/pkcs11.c b/src/libdnssec/keystore/pkcs11.c new file mode 100644 index 0000000..2b8b6d5 --- /dev/null +++ b/src/libdnssec/keystore/pkcs11.c @@ -0,0 +1,397 @@ +/* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + */ + +#include <gnutls/gnutls.h> +#include <pthread.h> + +#include "contrib/string.h" +#include "libdnssec/error.h" +#include "libdnssec/keyid.h" +#include "libdnssec/shared/keyid_gnutls.h" +#include "libdnssec/keystore.h" +#include "libdnssec/keystore/internal.h" +#include "libdnssec/p11/p11.h" +#include "libdnssec/pem.h" +#include "libdnssec/shared/shared.h" + +#ifdef ENABLE_PKCS11 + +struct pkcs11_ctx { + char *url; +}; + +typedef struct pkcs11_ctx pkcs11_ctx_t; + +/*! + * Flags used when generating/import key into the token. + */ +static const int TOKEN_ADD_FLAGS = GNUTLS_PKCS11_OBJ_FLAG_MARK_SENSITIVE + | GNUTLS_PKCS11_OBJ_FLAG_MARK_PRIVATE; + +static int key_url(const char *token_uri, const char *key_id, char **url_ptr) +{ + assert(token_uri); + assert(key_id); + assert(url_ptr); + + if (!dnssec_keyid_is_valid(key_id)) { + return DNSSEC_INVALID_KEY_ID; + } + + size_t token_len = strlen(token_uri); + size_t id_len = strlen(key_id); + + // url: <token-url>;id=%aa%bb%cc.. + + size_t len = token_len + 4 + (id_len / 2 * 3); + char *url = malloc(len + 1); + if (!url) { + return DNSSEC_ENOMEM; + } + + size_t prefix = snprintf(url, len, "%s;id=", token_uri); + if (prefix != token_len + 4) { + free(url); + return DNSSEC_ENOMEM; + } + + assert(id_len % 2 == 0); + char *pos = url + prefix; + for (int i = 0; i < id_len; i += 2, pos += 3) { + pos[0] = '%'; + pos[1] = key_id[i]; + pos[2] = key_id[i+1]; + } + assert(url + len == pos); + url[len] = '\0'; + + *url_ptr = url; + return DNSSEC_EOK; +} + +/*! + * Parse configuration string. Accepted format: "<pkcs11-url> <module-path>" + */ +static int parse_config(const char *config, char **uri_ptr, char **module_ptr) +{ + const char *space = strchr(config, ' '); + if (!space) { + return DNSSEC_KEYSTORE_INVALID_CONFIG; + } + + char *url = strndup(config, space - config); + char *module = strdup(space + 1); + + if (!url || !module) { + free(url); + free(module); + return DNSSEC_ENOMEM; + } + + *uri_ptr = url; + *module_ptr = module; + + return DNSSEC_EOK; +} + +/*! + * Load PKCS #11 module and check if the token is available. + */ +static int safe_open(const char *config, char **url_ptr) +{ + char *url = NULL; + char *module = NULL; + + int r = parse_config(config, &url, &module); + if (r != DNSSEC_EOK) { + return r; + } + + r = p11_load_module(module); + free(module); + if (r != GNUTLS_E_SUCCESS) { + free(url); + return DNSSEC_P11_FAILED_TO_LOAD_MODULE; + } + + unsigned int flags = 0; + r = gnutls_pkcs11_token_get_flags(url, &flags); + if (r != GNUTLS_E_SUCCESS) { + free(url); + return DNSSEC_P11_TOKEN_NOT_AVAILABLE; + } + + *url_ptr = url; + + return DNSSEC_EOK; +} + +/* -- internal API --------------------------------------------------------- */ + +static void disable_pkcs11_callbacks(void) +{ + gnutls_pkcs11_set_pin_function(NULL, NULL); + gnutls_pkcs11_set_token_function(NULL, NULL); +} + +static int pkcs11_ctx_new(void **ctx_ptr) +{ + static pthread_once_t once = PTHREAD_ONCE_INIT; + pthread_once(&once, disable_pkcs11_callbacks); + + pkcs11_ctx_t *ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + return DNSSEC_ENOMEM; + } + + *ctx_ptr = ctx; + + return DNSSEC_EOK; +} + +static void pkcs11_ctx_free(void *ctx) +{ + free(ctx); +} + +static int pkcs11_init(void *ctx, const char *config) +{ + /* + * Current keystore initialization is idempotent. We don't really + * initialize the token because don't want to wipe the data. We just + * check that the token is available the same way pkcs11_open() does. + */ + + _cleanup_free_ char *url = NULL; + + return safe_open(config, &url); +} + +static int pkcs11_open(void *_ctx, const char *config) +{ + pkcs11_ctx_t *ctx = _ctx; + + return safe_open(config, &ctx->url); +} + +static int pkcs11_close(void *_ctx) +{ + pkcs11_ctx_t *ctx = _ctx; + + free(ctx->url); + clear_struct(ctx); + + return DNSSEC_EOK; +} + +static int pkcs11_generate_key(void *_ctx, gnutls_pk_algorithm_t algorithm, + unsigned bits, const char *label, char **id_ptr) +{ + pkcs11_ctx_t *ctx = _ctx; + + uint8_t buf[20] = { 0 }; + gnutls_rnd(GNUTLS_RND_RANDOM, buf, sizeof(buf)); + dnssec_binary_t cka_id = { .data = buf, .size = sizeof(buf) }; + + int flags = TOKEN_ADD_FLAGS | GNUTLS_PKCS11_OBJ_FLAG_LOGIN; + gnutls_datum_t gt_cka_id = binary_to_datum(&cka_id); + int r = gnutls_pkcs11_privkey_generate3(ctx->url, algorithm, bits, label, + >_cka_id, 0, NULL, 0, flags); + if (r != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_GENERATE_ERROR; + } + + char *id = bin_to_hex(cka_id.data, cka_id.size, false); + if (id == NULL) { + return DNSSEC_ENOMEM; + } + + *id_ptr = id; + + return DNSSEC_EOK; +} + +static int import_pem(const dnssec_binary_t *pem, + gnutls_x509_privkey_t *key_ptr, + gnutls_pubkey_t *pubkey_ptr) +{ + gnutls_x509_privkey_t x509_key = NULL; + gnutls_privkey_t key = NULL; + gnutls_pubkey_t pubkey = NULL; + + int r = dnssec_pem_to_x509(pem, &x509_key); + if (r != DNSSEC_EOK) { + goto fail; + } + + if (gnutls_privkey_init(&key) != GNUTLS_E_SUCCESS || + gnutls_pubkey_init(&pubkey) != GNUTLS_E_SUCCESS + ) { + r = DNSSEC_ENOMEM; + goto fail; + } + + if (gnutls_privkey_import_x509(key, x509_key, 0) != GNUTLS_E_SUCCESS || + gnutls_pubkey_import_privkey(pubkey, key, 0, 0) != GNUTLS_E_SUCCESS + ) { + r = DNSSEC_KEY_IMPORT_ERROR; + goto fail; + } + +fail: + gnutls_privkey_deinit(key); + + if (r == DNSSEC_EOK) { + *key_ptr = x509_key; + *pubkey_ptr = pubkey; + } else { + gnutls_x509_privkey_deinit(x509_key); + gnutls_pubkey_deinit(pubkey); + } + + return r; +} + +static int pkcs11_import_key(void *_ctx, const dnssec_binary_t *pem, char **id_ptr) +{ + pkcs11_ctx_t *ctx = _ctx; + + _cleanup_x509_privkey_ gnutls_x509_privkey_t key = NULL; + _cleanup_pubkey_ gnutls_pubkey_t pubkey = NULL; + + int r = import_pem(pem, &key, &pubkey); + if (r != DNSSEC_EOK) { + return r; + } + + _cleanup_binary_ dnssec_binary_t id = { 0 }; + r = keyid_x509(key, &id); + if (r != DNSSEC_EOK) { + return r; + } + + int flags = TOKEN_ADD_FLAGS | GNUTLS_PKCS11_OBJ_FLAG_LOGIN; + gnutls_datum_t gid = binary_to_datum(&id); + + r = gnutls_pkcs11_copy_x509_privkey2(ctx->url, key, NULL, &gid, 0, flags); + if (r != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_IMPORT_ERROR; + } + + r = gnutls_pkcs11_copy_pubkey(ctx->url, pubkey, NULL, &gid, 0, flags); + if (r != GNUTLS_E_SUCCESS) { + // note, we result with dangling private key in the token + return DNSSEC_KEY_IMPORT_ERROR; + } + + *id_ptr = bin_to_hex(id.data, id.size, false); + if (*id_ptr == NULL) { + return DNSSEC_ENOMEM; + } + + return DNSSEC_EOK; +} + +static int pkcs11_remove_key(void *_ctx, const char *id) +{ + pkcs11_ctx_t *ctx = _ctx; + + _cleanup_free_ char *url = NULL; + int r = key_url(ctx->url, id, &url); + if (r != DNSSEC_EOK) { + return r; + } + + r = gnutls_pkcs11_delete_url(url, GNUTLS_PKCS11_OBJ_FLAG_LOGIN); + if (r < 0) { + return DNSSEC_ERROR; + } else if (r == 0) { + return DNSSEC_ENOENT; + } + + return DNSSEC_EOK; +} + +static int pkcs11_get_private(void *_ctx, const char *id, gnutls_privkey_t *key_ptr) +{ + pkcs11_ctx_t *ctx = _ctx; + + _cleanup_free_ char *url = NULL; + int r = key_url(ctx->url, id, &url); + if (r != DNSSEC_EOK) { + return r; + } + + gnutls_privkey_t key = NULL; + r = gnutls_privkey_init(&key); + if (r != GNUTLS_E_SUCCESS) { + return DNSSEC_ENOMEM; + } + + r = gnutls_privkey_import_pkcs11_url(key, url); + if (r != GNUTLS_E_SUCCESS) { + gnutls_privkey_deinit(key); + return DNSSEC_NOT_FOUND; + } + + *key_ptr = key; + + return DNSSEC_EOK; +} + +static int pkcs11_set_private(void *ctx, gnutls_privkey_t key) +{ + _cleanup_binary_ dnssec_binary_t pem = { 0 }; + int r = dnssec_pem_from_privkey(key, &pem); + if (r != DNSSEC_EOK) { + return r; + } + + _cleanup_free_ char *keyid = NULL; + + return pkcs11_import_key(ctx, &pem, &keyid); +} + +/* -- public API ----------------------------------------------------------- */ + +_public_ +int dnssec_keystore_init_pkcs11(dnssec_keystore_t **store_ptr) +{ + static const keystore_functions_t IMPLEMENTATION = { + .ctx_new = pkcs11_ctx_new, + .ctx_free = pkcs11_ctx_free, + .init = pkcs11_init, + .open = pkcs11_open, + .close = pkcs11_close, + .generate_key = pkcs11_generate_key, + .import_key = pkcs11_import_key, + .remove_key = pkcs11_remove_key, + .get_private = pkcs11_get_private, + .set_private = pkcs11_set_private, + }; + + return keystore_create(store_ptr, &IMPLEMENTATION); +} + +#else // !ENABLE_PKCS11 + +_public_ +int dnssec_keystore_init_pkcs11(dnssec_keystore_t **store_ptr) +{ + return DNSSEC_NOT_IMPLEMENTED_ERROR; +} + +#endif diff --git a/src/libdnssec/keystore/pkcs8.c b/src/libdnssec/keystore/pkcs8.c new file mode 100644 index 0000000..047bde8 --- /dev/null +++ b/src/libdnssec/keystore/pkcs8.c @@ -0,0 +1,497 @@ +/* Copyright (C) 2022 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 <https://www.gnu.org/licenses/>. + */ + +#include <assert.h> +#include <fcntl.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <sys/stat.h> +#include <sys/types.h> + +#include "contrib/files.h" +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/keystore.h" +#include "libdnssec/keystore/internal.h" +#include "libdnssec/pem.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/shared/keyid_gnutls.h" + +#define DIR_INIT_MODE 0750 + +/*! + * Context for PKCS #8 key directory. + */ +typedef struct { + char *dir_name; +} pkcs8_dir_handle_t; + +/* -- internal functions --------------------------------------------------- */ + +/*! + * Get path to a private key in PKCS #8 PEM format. + */ +static char *key_path(const char *dir, const char *id) +{ + char *strp = NULL; + + int ret = asprintf(&strp, "%s/%s.pem", dir, id); + if (ret < 0) { + return NULL; + } + return strp; +} + +/*! + * Get size of the file and reset the position to the beginning of the file. + */ +static int file_size(int fd, size_t *size) +{ + off_t offset = lseek(fd, 0, SEEK_END); + if (offset == -1) { + return dnssec_errno_to_error(errno); + } + + if (lseek(fd, 0, SEEK_SET) == -1) { + return dnssec_errno_to_error(errno); + } + + assert(offset >= 0); + *size = offset; + + return DNSSEC_EOK; +} + +/*! + * Open a key file and get the descriptor. + */ +static int key_open(const char *dir_name, const char *id, int flags, + mode_t mode, int *fd_ptr) +{ + assert(dir_name); + assert(id); + assert(fd_ptr); + + _cleanup_free_ char *filename = key_path(dir_name, id); + if (!filename) { + return DNSSEC_ENOMEM; + } + + int fd = open(filename, flags, mode); + if (fd == -1) { + return dnssec_errno_to_error(errno); + } + + *fd_ptr = fd; + + return DNSSEC_EOK; +} + +static int key_open_read(const char *dir_name, const char *id, int *fd_ptr) +{ + return key_open(dir_name, id, O_RDONLY, 0, fd_ptr); +} + +static int key_open_write(const char *dir_name, const char *id, int *fd_ptr) +{ + return key_open(dir_name, id, O_WRONLY|O_CREAT|O_EXCL, + S_IRUSR|S_IWUSR|S_IRGRP, fd_ptr); +} + +static int pkcs8_dir_read(pkcs8_dir_handle_t *handle, const char *id, dnssec_binary_t *pem) +{ + if (!handle || !id || !pem) { + return DNSSEC_EINVAL; + } + + // open file and get it's size + + _cleanup_close_ int file = -1; + int result = key_open_read(handle->dir_name, id, &file); + if (result != DNSSEC_EOK) { + return result; + } + + size_t size = 0; + result = file_size(file, &size); + if (result != DNSSEC_EOK) { + return result; + } + + if (size == 0) { + return DNSSEC_MALFORMED_DATA; + } + + // read the stored data + + dnssec_binary_t read_pem = { 0 }; + result = dnssec_binary_alloc(&read_pem, size); + if (result != DNSSEC_EOK) { + return result; + } + + ssize_t read_count = read(file, read_pem.data, read_pem.size); + if (read_count == -1) { + dnssec_binary_free(&read_pem); + return dnssec_errno_to_error(errno); + } + + assert(read_count == read_pem.size); + *pem = read_pem; + + return DNSSEC_EOK; +} + +static bool key_is_duplicate(int open_error, pkcs8_dir_handle_t *handle, + const char *id, const dnssec_binary_t *pem) +{ + assert(handle); + assert(id); + assert(pem); + + if (open_error != dnssec_errno_to_error(EEXIST)) { + return false; + } + + _cleanup_binary_ dnssec_binary_t old = { 0 }; + int r = pkcs8_dir_read(handle, id, &old); + if (r != DNSSEC_EOK) { + return false; + } + + return dnssec_binary_cmp(&old, pem) == 0; +} + +static int pem_generate(gnutls_pk_algorithm_t algorithm, unsigned bits, + dnssec_binary_t *pem, char **id) +{ + assert(pem); + assert(id); + + // generate key + + _cleanup_x509_privkey_ gnutls_x509_privkey_t key = NULL; + int r = gnutls_x509_privkey_init(&key); + if (r != GNUTLS_E_SUCCESS) { + return DNSSEC_ENOMEM; + } + + r = gnutls_x509_privkey_generate(key, algorithm, bits, 0); + if (r != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_GENERATE_ERROR; + } + + // convert to PEM and export the ID + + dnssec_binary_t _pem = { 0 }; + r = dnssec_pem_from_x509(key, &_pem); + if (r != DNSSEC_EOK) { + return r; + } + + // export key ID + + char *_id = NULL; + r = keyid_x509_hex(key, &_id); + if (r != DNSSEC_EOK) { + dnssec_binary_free(&_pem); + return r; + } + + *id = _id; + *pem = _pem; + + return DNSSEC_EOK; +} + +/* -- internal API --------------------------------------------------------- */ + +static int pkcs8_ctx_new(void **ctx_ptr) +{ + if (!ctx_ptr) { + return DNSSEC_EINVAL; + } + + pkcs8_dir_handle_t *ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + return DNSSEC_ENOMEM; + } + + *ctx_ptr = ctx; + + return DNSSEC_EOK; +} + +static void pkcs8_ctx_free(void *ctx) +{ + free(ctx); +} + +static int pkcs8_init(void *ctx, const char *config) +{ + if (!ctx || !config) { + return DNSSEC_EINVAL; + } + + return make_dir(config, DIR_INIT_MODE, true); +} + +static int pkcs8_open(void *ctx, const char *config) +{ + if (!ctx || !config) { + return DNSSEC_EINVAL; + } + + pkcs8_dir_handle_t *handle = ctx; + + char *path = realpath(config, NULL); + if (!path) { + return DNSSEC_NOT_FOUND; + } + + handle->dir_name = path; + + return DNSSEC_EOK; +} + +static int pkcs8_close(void *ctx) +{ + if (!ctx) { + return DNSSEC_EINVAL; + } + + pkcs8_dir_handle_t *handle = ctx; + + free(handle->dir_name); + memset(handle, 0, sizeof(*handle)); + + return DNSSEC_EOK; +} + +static int pkcs8_generate_key(void *ctx, gnutls_pk_algorithm_t algorithm, + unsigned bits, const char *label, char **id_ptr) +{ + if (!ctx || !id_ptr) { + return DNSSEC_EINVAL; + } + + (void)label; + + pkcs8_dir_handle_t *handle = ctx; + + // generate key + + char *id = NULL; + _cleanup_binary_ dnssec_binary_t pem = { 0 }; + int r = pem_generate(algorithm, bits, &pem, &id); + if (r != DNSSEC_EOK) { + return r; + } + + // create the file + + _cleanup_close_ int file = -1; + r = key_open_write(handle->dir_name, id, &file); + if (r != DNSSEC_EOK) { + if (key_is_duplicate(r, handle, id, &pem)) { + return DNSSEC_EOK; + } + return r; + } + + // write the data + + ssize_t wrote_count = write(file, pem.data, pem.size); + if (wrote_count == -1) { + return dnssec_errno_to_error(errno); + } + + assert(wrote_count == pem.size); + + // finish + + *id_ptr = id; + + return DNSSEC_EOK; +} + +static int pkcs8_import_key(void *ctx, const dnssec_binary_t *pem, char **id_ptr) +{ + if (!ctx || !pem || !id_ptr) { + return DNSSEC_EINVAL; + } + + pkcs8_dir_handle_t *handle = ctx; + + // retrieve key ID + + char *id = NULL; + _cleanup_x509_privkey_ gnutls_x509_privkey_t key = NULL; + int r = dnssec_pem_to_x509(pem, &key); + if (r != DNSSEC_EOK) { + return r; + } + + r = keyid_x509_hex(key, &id); + if (r != DNSSEC_EOK) { + return r; + } + + // create the file + + _cleanup_close_ int file = -1; + r = key_open_write(handle->dir_name, id, &file); + if (r != DNSSEC_EOK) { + if (key_is_duplicate(r, handle, id, pem)) { + *id_ptr = id; + return DNSSEC_EOK; + } + free(id); + return r; + } + + // write the data + + ssize_t wrote_count = write(file, pem->data, pem->size); + if (wrote_count == -1) { + free(id); + return dnssec_errno_to_error(errno); + } + + assert(wrote_count == pem->size); + + // finish + + *id_ptr = id; + + return DNSSEC_EOK; +} + +static int pkcs8_remove_key(void *ctx, const char *id) +{ + if (!ctx || !id) { + return DNSSEC_EINVAL; + } + + pkcs8_dir_handle_t *handle = ctx; + + _cleanup_free_ char *filename = key_path(handle->dir_name, id); + if (!filename) { + return DNSSEC_ENOMEM; + } + + if (unlink(filename) == -1) { + return dnssec_errno_to_error(errno); + } + + return DNSSEC_EOK; +} + +static int pkcs8_get_private(void *ctx, const char *id, gnutls_privkey_t *key_ptr) +{ + if (!ctx || !id || !key_ptr) { + return DNSSEC_EINVAL; + } + + pkcs8_dir_handle_t *handle = ctx; + + // load private key data + + _cleanup_close_ int file = -1; + int r = key_open_read(handle->dir_name, id, &file); + if (r != DNSSEC_EOK) { + return r; + } + + size_t size = 0; + r = file_size(file, &size); + if (r != DNSSEC_EOK) { + return r; + } + + if (size == 0) { + return DNSSEC_MALFORMED_DATA; + } + + // read the stored data + + _cleanup_binary_ dnssec_binary_t pem = { 0 }; + r = dnssec_binary_alloc(&pem, size); + if (r != DNSSEC_EOK) { + return r; + } + + ssize_t read_count = read(file, pem.data, pem.size); + if (read_count == -1) { + dnssec_binary_free(&pem); + return dnssec_errno_to_error(errno); + } + + assert(read_count == pem.size); + + // construct the key + + gnutls_privkey_t key = NULL; + r = dnssec_pem_to_privkey(&pem, &key); + if (r != DNSSEC_EOK) { + return r; + } + + // finish + + *key_ptr = key; + + return DNSSEC_EOK; +} + +static int pkcs8_set_private(void *ctx, gnutls_privkey_t key) +{ + if (!ctx) { + return DNSSEC_EINVAL; + } + + _cleanup_binary_ dnssec_binary_t pem = { 0 }; + int r = dnssec_pem_from_privkey(key, &pem); + if (r != DNSSEC_EOK) { + return r; + } + + _cleanup_free_ char *keyid = NULL; + + return pkcs8_import_key(ctx, &pem, &keyid); +} + +/* -- public API ----------------------------------------------------------- */ + +_public_ +int dnssec_keystore_init_pkcs8(dnssec_keystore_t **store_ptr) +{ + static const keystore_functions_t IMPLEMENTATION = { + .ctx_new = pkcs8_ctx_new, + .ctx_free = pkcs8_ctx_free, + .init = pkcs8_init, + .open = pkcs8_open, + .close = pkcs8_close, + .generate_key = pkcs8_generate_key, + .import_key = pkcs8_import_key, + .remove_key = pkcs8_remove_key, + .get_private = pkcs8_get_private, + .set_private = pkcs8_set_private, + }; + + return keystore_create(store_ptr, &IMPLEMENTATION); +} diff --git a/src/libdnssec/keytag.h b/src/libdnssec/keytag.h new file mode 100644 index 0000000..7915970 --- /dev/null +++ b/src/libdnssec/keytag.h @@ -0,0 +1,44 @@ +/* Copyright (C) 2020 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 <https://www.gnu.org/licenses/>. + */ + +/*! + * \file + * + * \addtogroup keytag + * + * \brief Low-level key tag computation API. + * + * The module provides simple interface for DNSKEY key id computation. + * + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <libdnssec/binary.h> + +/*! + * Compute a key tag for a DNSSEC key. + * + * \param[in] rdata DNSKEY RDATA. + * \param[out] keytag Computed keytag. + * + * \return Error code, DNSSEC_EOK of successful. + */ +int dnssec_keytag(const dnssec_binary_t *rdata, uint16_t *keytag); + +/*! @} */ |