diff options
Diffstat (limited to 'src/libdnssec')
58 files changed, 7352 insertions, 0 deletions
diff --git a/src/libdnssec/Makefile.inc b/src/libdnssec/Makefile.inc new file mode 100644 index 0000000..981d841 --- /dev/null +++ b/src/libdnssec/Makefile.inc @@ -0,0 +1,70 @@ +lib_LTLIBRARIES += libdnssec.la +pkgconfig_DATA += libdnssec.pc + +libdnssec_la_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY) $(gnutls_CFLAGS) +libdnssec_la_LDFLAGS = $(AM_LDFLAGS) $(libdnssec_VERSION_INFO) $(LDFLAG_EXCLUDE_LIBS) +libdnssec_la_LIBADD = $(libcontrib_LIBS) $(gnutls_LIBS) +if ENABLE_PKCS11 +libdnssec_la_LIBADD += $(pthread_LIBS) +endif + +include_libdnssecdir = $(includedir)/libdnssec +include_libdnssec_HEADERS = \ + libdnssec/binary.h \ + libdnssec/crypto.h \ + libdnssec/digest.h \ + libdnssec/dnssec.h \ + libdnssec/error.h \ + libdnssec/key.h \ + libdnssec/keyid.h \ + libdnssec/keystore.h \ + libdnssec/keytag.h \ + libdnssec/nsec.h \ + libdnssec/pem.h \ + libdnssec/random.h \ + libdnssec/sign.h \ + libdnssec/tsig.h \ + libdnssec/version.h + +libdnssec_la_SOURCES = \ + libdnssec/binary.c \ + libdnssec/crypto.c \ + libdnssec/digest.c \ + libdnssec/error.c \ + libdnssec/key/algorithm.c \ + libdnssec/key/algorithm.h \ + libdnssec/key/convert.c \ + libdnssec/key/convert.h \ + libdnssec/key/dnskey.c \ + libdnssec/key/dnskey.h \ + libdnssec/key/ds.c \ + libdnssec/key/internal.h \ + libdnssec/key/key.c \ + libdnssec/key/keytag.c \ + libdnssec/key/privkey.c \ + libdnssec/key/privkey.h \ + libdnssec/key/simple.c \ + libdnssec/keyid.c \ + libdnssec/keystore/internal.h \ + libdnssec/keystore/keystore.c \ + libdnssec/keystore/pkcs11.c \ + libdnssec/keystore/pkcs8.c \ + libdnssec/nsec/bitmap.c \ + libdnssec/nsec/hash.c \ + libdnssec/nsec/nsec.c \ + libdnssec/p11/p11.c \ + libdnssec/p11/p11.h \ + libdnssec/pem.c \ + libdnssec/random.c \ + libdnssec/shared/bignum.c \ + libdnssec/shared/bignum.h \ + libdnssec/shared/binary_wire.h \ + libdnssec/shared/dname.c \ + libdnssec/shared/dname.h \ + libdnssec/shared/keyid_gnutls.c \ + libdnssec/shared/keyid_gnutls.h \ + libdnssec/shared/shared.h \ + libdnssec/sign/der.c \ + libdnssec/sign/der.h \ + libdnssec/sign/sign.c \ + libdnssec/tsig.c diff --git a/src/libdnssec/binary.c b/src/libdnssec/binary.c new file mode 100644 index 0000000..59153f3 --- /dev/null +++ b/src/libdnssec/binary.c @@ -0,0 +1,163 @@ +/* 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 <assert.h> +#include <string.h> + +#include "contrib/base64.h" +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/shared/shared.h" + +/* -- public API ----------------------------------------------------------- */ + +_public_ +int dnssec_binary_alloc(dnssec_binary_t *data, size_t size) +{ + if (!data || size == 0) { + return DNSSEC_EINVAL; + } + + uint8_t *new_data = calloc(1, size); + if (!new_data) { + return DNSSEC_ENOMEM; + } + + data->data = new_data; + data->size = size; + + return DNSSEC_EOK; +} + +_public_ +void dnssec_binary_free(dnssec_binary_t *binary) +{ + if (!binary) { + return; + } + + free(binary->data); + clear_struct(binary); +} + +_public_ +int dnssec_binary_dup(const dnssec_binary_t *from, dnssec_binary_t *to) +{ + if (!from || !to) { + return DNSSEC_EINVAL; + } + + uint8_t *copy = malloc(from->size); + if (copy == NULL) { + return DNSSEC_ENOMEM; + } + + memmove(copy, from->data, from->size); + + to->size = from->size; + to->data = copy; + + return DNSSEC_EOK; +} + +_public_ +int dnssec_binary_resize(dnssec_binary_t *data, size_t new_size) +{ + if (!data) { + return DNSSEC_EINVAL; + } + + uint8_t *new_data = realloc(data->data, new_size); + if (new_size > 0 && new_data == NULL) { + return DNSSEC_ENOMEM; + } + + data->data = new_data; + data->size = new_size; + + return DNSSEC_EOK; +} + +_public_ +int dnssec_binary_cmp(const dnssec_binary_t *one, const dnssec_binary_t *two) +{ + if (one == two) { + return 0; + } + + uint8_t *data_one = one && one->size > 0 ? one->data : NULL; + uint8_t *data_two = two && two->size > 0 ? two->data : NULL; + + if (data_one == data_two) { + return 0; + } else if (data_one == NULL) { + return -1; + } else if (data_two == NULL) { + return +1; + } + + size_t min_size = one->size <= two->size ? one->size : two->size; + int cmp = memcmp(data_one, data_two, min_size); + if (cmp != 0) { + return cmp; + } else if (one->size == two->size) { + return 0; + } else if (one->size < two->size) { + return -1; + } else { + return +1; + } +} + +_public_ +int dnssec_binary_from_base64(const dnssec_binary_t *base64, + dnssec_binary_t *binary) +{ + if (!base64 || !binary) { + return DNSSEC_EINVAL; + } + + uint8_t *data; + int32_t size = knot_base64_decode_alloc(base64->data, base64->size, &data); + if (size < 0) { + return DNSSEC_EINVAL; + } + + binary->data = data; + binary->size = size; + + return DNSSEC_EOK; +} + +_public_ +int dnssec_binary_to_base64(const dnssec_binary_t *binary, + dnssec_binary_t *base64) +{ + if (!binary || !base64) { + return DNSSEC_EINVAL; + } + + uint8_t *data; + int32_t size = knot_base64_encode_alloc(binary->data, binary->size, &data); + if (size < 0) { + return DNSSEC_EINVAL; + } + + base64->data = data; + base64->size = size; + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/binary.h b/src/libdnssec/binary.h new file mode 100644 index 0000000..8ff4174 --- /dev/null +++ b/src/libdnssec/binary.h @@ -0,0 +1,116 @@ +/* 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 binary + * + * \brief Universal binary data container. + * + * The module provides universal binary data container extensively used by + * a lot of functions provided by the library. + * + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <stdlib.h> + +/*! + * Universal structure to hold binary data. + */ +typedef struct dnssec_binary { + size_t size; /*!< Size of the binary data. */ + uint8_t *data; /*!< Stored data. */ +} dnssec_binary_t; + +/*! + * Allocate new binary data structure. + * + * \param[out] data Binary to be allocated. + * \param[in] size Requested size of the binary. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_binary_alloc(dnssec_binary_t *data, size_t size); + +/*! + * Free content of binary structure. + * + * \param binary Binary structure to be freed. + */ +void dnssec_binary_free(dnssec_binary_t *binary); + +/*! + * Create a copy of a binary structure. + * + * \param[in] from Source of the copy. + * \param[out] to Target of the copy. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_binary_dup(const dnssec_binary_t *from, dnssec_binary_t *to); + +/*! + * Resize binary structure to a new size. + * + * Internally uses realloc, which means that this function can be also used + * as a malloc or free. + * + * \param data Binary to be resized. + * \param new_size New size. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_binary_resize(dnssec_binary_t *data, size_t new_size); + +/*! + * Compare two binary structures (equivalent of memcmp). + * + * \note NULL sorts before data. + * + * \param one First binary. + * \param two Second binary. + * + * \return 0 if one equals two, <0 if one sorts before two, >0 otherwise. + */ +int dnssec_binary_cmp(const dnssec_binary_t *one, const dnssec_binary_t *two); + +/*! + * Allocate binary from Base64 encoded string. + * + * \param[in] base64 Base64 encoded data. + * \param[out] binary Decoded binary data. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_binary_from_base64(const dnssec_binary_t *base64, + dnssec_binary_t *binary); + +/*! + * Create Base64 encoded string from binary data. + * + * \param[in] binary Binary data. + * \param[out] base64 Base64 encode data. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_binary_to_base64(const dnssec_binary_t *binary, + dnssec_binary_t *base64); +/*! @} */ diff --git a/src/libdnssec/crypto.c b/src/libdnssec/crypto.c new file mode 100644 index 0000000..c42bed5 --- /dev/null +++ b/src/libdnssec/crypto.c @@ -0,0 +1,42 @@ +/* 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/gnutls.h> +#include <gnutls/pkcs11.h> + +#include "libdnssec/crypto.h" +#include "libdnssec/p11/p11.h" +#include "libdnssec/shared/shared.h" + +_public_ +void dnssec_crypto_init(void) +{ + p11_init(); + gnutls_global_init(); +} + +_public_ +void dnssec_crypto_cleanup(void) +{ + gnutls_global_deinit(); + p11_cleanup(); +} + +_public_ +void dnssec_crypto_reinit(void) +{ + p11_reinit(); +} diff --git a/src/libdnssec/crypto.h b/src/libdnssec/crypto.h new file mode 100644 index 0000000..9129084 --- /dev/null +++ b/src/libdnssec/crypto.h @@ -0,0 +1,52 @@ +/* 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 crypto + * + * \brief Cryptographic backend initialization. + * + * For most cryptographic operations, the library requires global + * initialization. Also, if the application creates a subprocess, the + * library has to be reinitialized in the child process after \c fork(). + * + * @{ + */ + +#pragma once + +/*! + * Initialize cryptographic backend. + */ +void dnssec_crypto_init(void); + +/*! + * Reinitialize cryptographic backend. + * + * Must be called after fork() by the child. + */ +void dnssec_crypto_reinit(void); + +/*! + * Deinitialize cryptographic backend. + * + * Should be called when terminating the application. + */ +void dnssec_crypto_cleanup(void); + +/*! @} */ diff --git a/src/libdnssec/digest.c b/src/libdnssec/digest.c new file mode 100644 index 0000000..83a4fcb --- /dev/null +++ b/src/libdnssec/digest.c @@ -0,0 +1,105 @@ +/* Copyright (C) 2021 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/digest.h" + +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#include "libdnssec/shared/shared.h" + +struct dnssec_digest_ctx { + gnutls_hash_hd_t gtctx; + unsigned size; +}; + +static gnutls_digest_algorithm_t lookup_algorithm(dnssec_digest_t algorithm) +{ + switch (algorithm) { + case DNSSEC_DIGEST_SHA384: return GNUTLS_DIG_SHA384; + case DNSSEC_DIGEST_SHA512: return GNUTLS_DIG_SHA512; + default: + return GNUTLS_DIG_UNKNOWN; + }; +} + +_public_ +int dnssec_digest_init(dnssec_digest_t algorithm, dnssec_digest_ctx_t **out_ctx) +{ + if (out_ctx == NULL) { + return DNSSEC_EINVAL; + } + + gnutls_digest_algorithm_t gtalg = lookup_algorithm(algorithm); + if (gtalg == GNUTLS_DIG_UNKNOWN) { + return DNSSEC_INVALID_DIGEST_ALGORITHM; + } + + dnssec_digest_ctx_t *res = malloc(sizeof(*res)); + if (res == NULL) { + return DNSSEC_ENOMEM; + } + + res->size = gnutls_hash_get_len(gtalg); + if (res->size == 0 || gnutls_hash_init(&res->gtctx, gtalg) < 0) { + free(res); + return DNSSEC_DIGEST_ERROR; + } + + *out_ctx = res; + return DNSSEC_EOK; +} + +static void digest_ctx_free(dnssec_digest_ctx_t *ctx) +{ + free_gnutls_hash_ptr(&ctx->gtctx); + free(ctx); +} + +_public_ +int dnssec_digest(dnssec_digest_ctx_t *ctx, dnssec_binary_t *data) +{ + if (ctx == NULL || data == NULL) { + return DNSSEC_EINVAL; + } + + int r = gnutls_hash(ctx->gtctx, data->data, data->size); + if (r != 0) { + digest_ctx_free(ctx); + return DNSSEC_DIGEST_ERROR; + } + return DNSSEC_EOK; +} + +_public_ +int dnssec_digest_finish(dnssec_digest_ctx_t *ctx, dnssec_binary_t *out) +{ + if (ctx == NULL || out == NULL) { + return DNSSEC_EINVAL; + } + + int r = dnssec_binary_resize(out, ctx->size); + if (r < 0) { + dnssec_binary_free(out); + digest_ctx_free(ctx); + return r; + } + + gnutls_hash_output(ctx->gtctx, out->data); + + digest_ctx_free(ctx); + return DNSSEC_EOK; +} diff --git a/src/libdnssec/digest.h b/src/libdnssec/digest.h new file mode 100644 index 0000000..76709aa --- /dev/null +++ b/src/libdnssec/digest.h @@ -0,0 +1,76 @@ +/* Copyright (C) 2021 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 digest + * + * \brief Data hashing operations. + * + * @{ + */ + +#pragma once + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" + +typedef enum { + DNSSEC_DIGEST_INVALID = 0, + DNSSEC_DIGEST_SHA384 = 1, + DNSSEC_DIGEST_SHA512 = 2, +} dnssec_digest_t; + +struct dnssec_digest_ctx; +typedef struct dnssec_digest_ctx dnssec_digest_ctx_t; + +/*! + * \brief Initialize digest context. + * + * \param algorithm Hasing algorithm to be used. + * \param out_ctx Output: context structure to be initialized. + * + * \return DNSSEC_E* + */ +int dnssec_digest_init(dnssec_digest_t algorithm, dnssec_digest_ctx_t **out_ctx); + +/*! + * \brief Digest data. + * + * \param ctx Digest context. + * \param data Data to be hashed. + * + * \note This function may be invoked repeatedly for single digest context, + * hashing all data as concatenated. + * + * \return DNSSEC_E* + * + * \note If error is returned, the digest context is automatically disposed. + */ +int dnssec_digest(dnssec_digest_ctx_t *ctx, dnssec_binary_t *data); + +/*! + * \brief Finalize digest, dispose digest context and return the hash. + * + * \param ctx Digest context. + * \param out Output: computed hash. + * + * \return DNSSEC_E* + */ +int dnssec_digest_finish(dnssec_digest_ctx_t *ctx, dnssec_binary_t *out); + +/*! @} */ diff --git a/src/libdnssec/dnssec.h b/src/libdnssec/dnssec.h new file mode 100644 index 0000000..ebef953 --- /dev/null +++ b/src/libdnssec/dnssec.h @@ -0,0 +1,36 @@ +/* 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/>. + */ + +/*! + * \file + * + * Convenient header to include all library modules. + */ + +#pragma once + +#include <libdnssec/binary.h> +#include <libdnssec/crypto.h> +#include <libdnssec/error.h> +#include <libdnssec/key.h> +#include <libdnssec/keyid.h> +#include <libdnssec/keystore.h> +#include <libdnssec/keytag.h> +#include <libdnssec/nsec.h> +#include <libdnssec/pem.h> +#include <libdnssec/random.h> +#include <libdnssec/sign.h> +#include <libdnssec/tsig.h> diff --git a/src/libdnssec/error.c b/src/libdnssec/error.c new file mode 100644 index 0000000..d4f9a81 --- /dev/null +++ b/src/libdnssec/error.c @@ -0,0 +1,87 @@ +/* Copyright (C) 2021 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 <string.h> + +#include "libdnssec/error.h" +#include "libdnssec/shared/shared.h" + +typedef struct error_message_t { + int code; + const char *text; +} error_message_t; + +static const error_message_t ERROR_MESSAGES[] = { + { DNSSEC_EOK, "no error" }, + + { DNSSEC_ENOMEM, "not enough memory" }, + { DNSSEC_EINVAL, "invalid argument" }, + { DNSSEC_ENOENT, "no such file or directory" }, + + { DNSSEC_ERROR, "unspecified error" }, + { DNSSEC_NOT_IMPLEMENTED_ERROR, "not implemented" }, + { DNSSEC_MALFORMED_DATA, "malformed data" }, + { DNSSEC_NOT_FOUND, "not found" }, + + { DNSSEC_PKCS8_IMPORT_ERROR, "PKCS #8 import error" }, + { DNSSEC_KEY_EXPORT_ERROR, "key export error" }, + { DNSSEC_KEY_IMPORT_ERROR, "key import error" }, + { DNSSEC_KEY_GENERATE_ERROR, "key generation error" }, + + { DNSSEC_INVALID_PUBLIC_KEY, "invalid public key" }, + { DNSSEC_INVALID_PRIVATE_KEY, "invalid private key" }, + { DNSSEC_INVALID_KEY_ALGORITHM, "invalid key algorithm" }, + { DNSSEC_INVALID_KEY_SIZE, "invalid key size" }, + { DNSSEC_INVALID_KEY_ID, "invalid key ID" }, + { DNSSEC_INVALID_KEY_NAME, "invalid key name" }, + + { DNSSEC_NO_PUBLIC_KEY, "no public key" }, + { DNSSEC_NO_PRIVATE_KEY, "no private key" }, + { DNSSEC_KEY_ALREADY_PRESENT, "key already present" }, + + { DNSSEC_SIGN_INIT_ERROR, "signing initialization error" }, + { DNSSEC_SIGN_ERROR, "signing error" }, + { DNSSEC_INVALID_SIGNATURE, "invalid signature" }, + + { DNSSEC_INVALID_NSEC3_ALGORITHM, "invalid NSEC3 algorithm" }, + { DNSSEC_NSEC3_HASHING_ERROR, "NSEC3 hashing error" }, + + { DNSSEC_INVALID_DS_ALGORITHM, "invalid DS algorithm" }, + { DNSSEC_DS_HASHING_ERROR, "DS hashing error" }, + + { DNSSEC_KEYSTORE_INVALID_CONFIG, "invalid KASP keystore configuration" }, + + { DNSSEC_P11_FAILED_TO_LOAD_MODULE, "failed to load PKCS #11 module" }, + { DNSSEC_P11_TOO_MANY_MODULES, "too many PKCS #11 modules loaded" }, + { DNSSEC_P11_TOKEN_NOT_AVAILABLE, "PKCS #11 token not available" }, + + { DNSSEC_INVALID_DIGEST_ALGORITHM, "invalid digest algorithm" }, + { DNSSEC_DIGEST_ERROR, "digest error" }, + + { 0 } +}; + +_public_ +const char *dnssec_strerror(int error) +{ + for (const error_message_t *m = ERROR_MESSAGES; m->text; m++) { + if (m->code == error) { + return m->text; + } + } + + return NULL; +} diff --git a/src/libdnssec/error.h b/src/libdnssec/error.h new file mode 100644 index 0000000..aee87cb --- /dev/null +++ b/src/libdnssec/error.h @@ -0,0 +1,106 @@ +/* Copyright (C) 2021 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 error + * + * \brief Error codes and error reporting. + * + * The module defines all error codes used in the library, and functions + * to convert the error codes to sensible error strings. + * + * @{ + */ + +#pragma once + +#include <errno.h> + +/*! + * Library error codes. + */ +enum dnssec_error { + DNSSEC_EOK = 0, + + DNSSEC_ENOMEM = -ENOMEM, + DNSSEC_EINVAL = -EINVAL, + DNSSEC_ENOENT = -ENOENT, + + DNSSEC_ERROR_MIN = -1500, + + DNSSEC_ERROR = DNSSEC_ERROR_MIN, + DNSSEC_NOT_IMPLEMENTED_ERROR, + DNSSEC_MALFORMED_DATA, + DNSSEC_NOT_FOUND, + + DNSSEC_PKCS8_IMPORT_ERROR, + DNSSEC_KEY_EXPORT_ERROR, + DNSSEC_KEY_IMPORT_ERROR, + DNSSEC_KEY_GENERATE_ERROR, + + DNSSEC_INVALID_PUBLIC_KEY, + DNSSEC_INVALID_PRIVATE_KEY, + DNSSEC_INVALID_KEY_ALGORITHM, + DNSSEC_INVALID_KEY_SIZE, + DNSSEC_INVALID_KEY_ID, + DNSSEC_INVALID_KEY_NAME, + + DNSSEC_NO_PUBLIC_KEY, + DNSSEC_NO_PRIVATE_KEY, + DNSSEC_KEY_ALREADY_PRESENT, + + DNSSEC_SIGN_INIT_ERROR, + DNSSEC_SIGN_ERROR, + DNSSEC_INVALID_SIGNATURE, + + DNSSEC_INVALID_NSEC3_ALGORITHM, + DNSSEC_NSEC3_HASHING_ERROR, + + DNSSEC_INVALID_DS_ALGORITHM, + DNSSEC_DS_HASHING_ERROR, + + DNSSEC_KEYSTORE_INVALID_CONFIG, + + DNSSEC_P11_FAILED_TO_LOAD_MODULE, + DNSSEC_P11_TOO_MANY_MODULES, + DNSSEC_P11_TOKEN_NOT_AVAILABLE, + + DNSSEC_INVALID_DIGEST_ALGORITHM, + DNSSEC_DIGEST_ERROR, + + DNSSEC_ERROR_MAX = -1001 +}; + +/*! + * Translate error code to error message. + * + * \param error Error code. + * + * \return Statically allocated error message string or NULL if unknown. + */ +const char *dnssec_strerror(int error); + +/*! + * Convert errno value to DNSSEC error code. + */ +static inline int dnssec_errno_to_error(int ecode) +{ + return -ecode; +} + +/*! @} */ diff --git a/src/libdnssec/key.h b/src/libdnssec/key.h new file mode 100644 index 0000000..aa8002b --- /dev/null +++ b/src/libdnssec/key.h @@ -0,0 +1,309 @@ +/* Copyright (C) 2023 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. + * + * Public key isn't duplicated. + */ +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..4574bbe --- /dev/null +++ b/src/libdnssec/key/key.c @@ -0,0 +1,461 @@ +/* Copyright (C) 2023 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; + } + + if (key->private_key != NULL) { + gnutls_privkey_init(&dup->private_key); + + gnutls_privkey_type_t type = gnutls_privkey_get_type(key->private_key); + if (type == GNUTLS_PRIVKEY_PKCS11) { +#ifdef ENABLE_PKCS11 + gnutls_pkcs11_privkey_t tmp; + gnutls_privkey_export_pkcs11(key->private_key, &tmp); + gnutls_privkey_import_pkcs11(dup->private_key, tmp, + GNUTLS_PRIVKEY_IMPORT_AUTO_RELEASE); +#else + assert(0); +#endif // ENABLE_PKCS11 + } else { + assert(type == GNUTLS_PRIVKEY_X509); + gnutls_x509_privkey_t tmp; + gnutls_privkey_export_x509(key->private_key, &tmp); + gnutls_privkey_import_x509(dup->private_key, tmp, + GNUTLS_PRIVKEY_IMPORT_AUTO_RELEASE); + } + } + + 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..aee77d5 --- /dev/null +++ b/src/libdnssec/keystore/pkcs8.c @@ -0,0 +1,497 @@ +/* Copyright (C) 2023 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); + +/*! @} */ diff --git a/src/libdnssec/nsec.h b/src/libdnssec/nsec.h new file mode 100644 index 0000000..19808b0 --- /dev/null +++ b/src/libdnssec/nsec.h @@ -0,0 +1,155 @@ +/* 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 nsec + * + * \brief NSEC bitmap and NSEC3 hash computation API. + * + * The module provides interface for computation of NSEC3 hashes and for + * construction of bit maps used in NSEC and NSEC3 records. + * + * @{ + */ + +#pragma once + +#include <stddef.h> +#include <stdint.h> +#include <stdbool.h> + +#include <libdnssec/binary.h> + +/*! + * DNSSEC NSEC3 algorithm numbers. + */ +typedef enum dnssec_nsec_algorithm { + DNSSEC_NSEC3_ALGORITHM_UNKNOWN = 0, + DNSSEC_NSEC3_ALGORITHM_SHA1 = 1, +} dnssec_nsec3_algorithm_t; + +/*! + * DNSSEC NSEC3 parameters. + */ +typedef struct dnssec_nsec3_params { + dnssec_nsec3_algorithm_t algorithm; /*!< NSEC3 algorithm. */ + uint8_t flags; /*!< NSEC3 flags. */ + uint16_t iterations; /*!< NSEC3 iterations count. */ + dnssec_binary_t salt; /*!< NSEC3 salt. */ +} dnssec_nsec3_params_t; + +/*! + * Free NSEC3 parameters. + */ +void dnssec_nsec3_params_free(dnssec_nsec3_params_t *params); + +/*! + * Parse NSEC3 parameters from NSEC3PARAM RDATA. + * + * \param params Output parameters. + * \param rdata NSEC3PARAM RDATA. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_nsec3_params_from_rdata(dnssec_nsec3_params_t *params, + const dnssec_binary_t *rdata); + +/*! + * Check if NSEC3 parameters match. + * + * \param params1 NSEC3 parameters 1. + * \param params2 NSEC3 parameters 2. + * + * \return True if match or if both NULL. + */ +bool dnssec_nsec3_params_match(const dnssec_nsec3_params_t *params1, + const dnssec_nsec3_params_t *params2); + +/*! + * Check whether a given NSEC bitmap contains a given RR type. + * + * \param bitmap Bitmap of an NSEC record. + * \param size Size of the bitmap. + * \param type RR type to check for. + * + * \return true if bitmap contains type, false otherwise. + */ +bool dnssec_nsec_bitmap_contains(const uint8_t *bitmap, uint16_t size, uint16_t type); + +/*! + * Compute NSEC3 hash for given data. + * + * \todo Input data must be converted to lowercase! + * + * \param[in] data Data to be hashed (usually domain name). + * \param[in] params NSEC3 parameters. + * \param[out] hash Computed hash (will be allocated or resized). + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_nsec3_hash(const dnssec_binary_t *data, + const dnssec_nsec3_params_t *params, + dnssec_binary_t *hash); + +/*! + * Get length of raw NSEC3 hash for a given algorithm. + * + * \param algorithm NSEC3 algorithm number. + * + * \return Length of raw NSEC3 hash, zero on error. + */ +size_t dnssec_nsec3_hash_length(dnssec_nsec3_algorithm_t algorithm); + +struct dnssec_nsec_bitmap; + +/*! + * Context for encoding of RR types bitmap used in NSEC/NSEC3. + */ +typedef struct dnssec_nsec_bitmap dnssec_nsec_bitmap_t; + +/*! + * Allocate new bit map encoding context. + */ +dnssec_nsec_bitmap_t *dnssec_nsec_bitmap_new(void); + +/*! + * Clear existing bit map encoding context. + */ +void dnssec_nsec_bitmap_clear(dnssec_nsec_bitmap_t *bitmap); + +/*! + * Free bit map encoding context. + */ +void dnssec_nsec_bitmap_free(dnssec_nsec_bitmap_t *bitmap); + +/*! + * Add one RR type into the bitmap. + */ +void dnssec_nsec_bitmap_add(dnssec_nsec_bitmap_t *bitmap, uint16_t type); + +/*! + * Compute the size of the encoded bitmap. + */ +size_t dnssec_nsec_bitmap_size(const dnssec_nsec_bitmap_t *bitmap); + +/*! + * Write encoded bitmap into the given buffer. + */ +void dnssec_nsec_bitmap_write(const dnssec_nsec_bitmap_t *bitmap, uint8_t *output); + +/*! @} */ diff --git a/src/libdnssec/nsec/bitmap.c b/src/libdnssec/nsec/bitmap.c new file mode 100644 index 0000000..05f8cfa --- /dev/null +++ b/src/libdnssec/nsec/bitmap.c @@ -0,0 +1,142 @@ +/* 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 <limits.h> +#include <stdint.h> +#include <string.h> + +#include "libdnssec/nsec.h" +#include "libdnssec/shared/shared.h" + +#define BITMAP_WINDOW_SIZE 256 +#define BITMAP_WINDOW_BYTES (BITMAP_WINDOW_SIZE/CHAR_BIT) +#define BITMAP_WINDOW_COUNT 256 + +/*! + * One window of an NSEC bitmap. + */ +typedef struct window { + uint8_t used; + uint8_t data[BITMAP_WINDOW_BYTES]; +} window_t; + +struct dnssec_nsec_bitmap { + int used; + window_t windows[BITMAP_WINDOW_COUNT]; +}; + +/* -- public API ----------------------------------------------------------- */ + +/*! + * Allocate new bit map encoding context. + */ +_public_ +dnssec_nsec_bitmap_t *dnssec_nsec_bitmap_new(void) +{ + dnssec_nsec_bitmap_t *bitmap = malloc(sizeof(*bitmap)); + if (!bitmap) { + return NULL; + } + + dnssec_nsec_bitmap_clear(bitmap); + + return bitmap; +} + +/*! + * Clear existing bit map encoding context. + */ +_public_ +void dnssec_nsec_bitmap_clear(dnssec_nsec_bitmap_t *bitmap) +{ + clear_struct(bitmap); +} + +/*! + * Free bit map encoding context. + */ +_public_ +void dnssec_nsec_bitmap_free(dnssec_nsec_bitmap_t *bitmap) +{ + free(bitmap); +} + +/*! + * Add one RR type into the bitmap. + */ +_public_ +void dnssec_nsec_bitmap_add(dnssec_nsec_bitmap_t *bitmap, uint16_t type) +{ + int win = type / BITMAP_WINDOW_SIZE; + int bit = type % BITMAP_WINDOW_SIZE; + + if (bitmap->used <= win) { + bitmap->used = win + 1; + } + + int win_byte = bit / CHAR_BIT; + int win_bit = bit % CHAR_BIT; + + window_t *window = &bitmap->windows[win]; + window->data[win_byte] |= 0x80 >> win_bit; + if (window->used <= win_byte) { + window->used = win_byte + 1; + } +} + +/*! + * Compute the size of the encoded bitmap. + */ +_public_ +size_t dnssec_nsec_bitmap_size(const dnssec_nsec_bitmap_t *bitmap) +{ + size_t result = 0; + + for (int i = 0; i < bitmap->used; i++) { + int used = bitmap->windows[i].used; + if (used == 0) { + continue; + } + + result += 2 + used; // windows number, window size, data + } + + return result; +} + +/*! + * Write encoded bitmap into the given buffer. + */ +_public_ +void dnssec_nsec_bitmap_write(const dnssec_nsec_bitmap_t *bitmap, uint8_t *output) +{ + uint8_t *write_ptr = output; + for (int win = 0; win < bitmap->used; win++) { + int used = bitmap->windows[win].used; + if (used == 0) { + continue; + } + + *write_ptr = (uint8_t)win; + write_ptr += 1; + + *write_ptr = (uint8_t)used; + write_ptr += 1; + + memmove(write_ptr, bitmap->windows[win].data, used); + write_ptr += used; + } +} diff --git a/src/libdnssec/nsec/hash.c b/src/libdnssec/nsec/hash.c new file mode 100644 index 0000000..b5d46ab --- /dev/null +++ b/src/libdnssec/nsec/hash.c @@ -0,0 +1,125 @@ +/* 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/gnutls.h> +#include <gnutls/crypto.h> +#include <string.h> + +#include "libdnssec/error.h" +#include "libdnssec/nsec.h" +#include "libdnssec/shared/shared.h" + +/*! + * Compute NSEC3 hash for given data and algorithm. + * + * \see RFC 5155 + * + * \todo Input data should be converted to lowercase. + */ +static int nsec3_hash(gnutls_digest_algorithm_t algorithm, int iterations, + const dnssec_binary_t *salt, const dnssec_binary_t *data, + dnssec_binary_t *hash) +{ + assert(salt); + assert(data); + assert(hash); + + int hash_size = gnutls_hash_get_len(algorithm); + if (hash_size <= 0) { + return DNSSEC_NSEC3_HASHING_ERROR; + } + + int result = dnssec_binary_resize(hash, hash_size); + if (result != DNSSEC_EOK) { + return result; + } + + _cleanup_hash_ gnutls_hash_hd_t digest = NULL; + result = gnutls_hash_init(&digest, algorithm); + if (result < 0) { + return DNSSEC_NSEC3_HASHING_ERROR; + } + + const uint8_t *in = data->data; + size_t in_size = data->size; + + for (int i = 0; i <= iterations; i++) { + result = gnutls_hash(digest, in, in_size); + if (result < 0) { + return DNSSEC_NSEC3_HASHING_ERROR; + } + + result = gnutls_hash(digest, salt->data, salt->size); + if (result < 0) { + return DNSSEC_NSEC3_HASHING_ERROR; + } + + gnutls_hash_output(digest, hash->data); + + in = hash->data; + in_size = hash->size; + } + + return DNSSEC_EOK; +} + +/*! + * Get GnuTLS digest algorithm from DNSSEC algorithm number. + */ +static gnutls_digest_algorithm_t algorithm_d2g(dnssec_nsec3_algorithm_t dnssec) +{ + switch (dnssec) { + case DNSSEC_NSEC3_ALGORITHM_SHA1: return GNUTLS_DIG_SHA1; + default: return GNUTLS_DIG_UNKNOWN; + } +} + +/* -- public API ----------------------------------------------------------- */ + +/*! + * Compute NSEC3 hash for given data. + */ +_public_ +int dnssec_nsec3_hash(const dnssec_binary_t *data, + const dnssec_nsec3_params_t *params, + dnssec_binary_t *hash) +{ + if (!data || !params || !hash) { + return DNSSEC_EINVAL; + } + + gnutls_digest_algorithm_t algorithm = algorithm_d2g(params->algorithm); + if (algorithm == GNUTLS_DIG_UNKNOWN) { + return DNSSEC_INVALID_NSEC3_ALGORITHM; + } + + return nsec3_hash(algorithm, params->iterations, ¶ms->salt, data, hash); +} + +/*! + * Get length of raw NSEC3 hash for a given algorithm. + */ +_public_ +size_t dnssec_nsec3_hash_length(dnssec_nsec3_algorithm_t algorithm) +{ + gnutls_digest_algorithm_t gnutls = algorithm_d2g(algorithm); + if (gnutls == GNUTLS_DIG_UNKNOWN) { + return 0; + } + + return gnutls_hash_get_len(gnutls); +} diff --git a/src/libdnssec/nsec/nsec.c b/src/libdnssec/nsec/nsec.c new file mode 100644 index 0000000..2e71598 --- /dev/null +++ b/src/libdnssec/nsec/nsec.c @@ -0,0 +1,130 @@ +/* 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 "libdnssec/nsec.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/shared/binary_wire.h" + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" + +/*! + * Free NSEC3 parameters. + */ +_public_ +void dnssec_nsec3_params_free(dnssec_nsec3_params_t *params) +{ + if (!params) { + return; + } + + dnssec_binary_free(¶ms->salt); + clear_struct(params); +} + +/*! + * Parse NSEC3 parameters from NSEC3PARAM RDATA. + * + * \see RFC 5155 (section 4.2) + */ +_public_ +int dnssec_nsec3_params_from_rdata(dnssec_nsec3_params_t *params, + const dnssec_binary_t *rdata) +{ + if (!params || !rdata || !rdata->data) { + return DNSSEC_EINVAL; + } + + dnssec_nsec3_params_t new_params = { 0 }; + + wire_ctx_t wire = binary_init(rdata); + + if (wire_ctx_available(&wire) < 5) { + return DNSSEC_MALFORMED_DATA; + } + + new_params.algorithm = wire_ctx_read_u8(&wire); + new_params.flags = wire_ctx_read_u8(&wire); + new_params.iterations = wire_ctx_read_u16(&wire); + new_params.salt.size = wire_ctx_read_u8(&wire); + + if (wire_ctx_available(&wire) != new_params.salt.size) { + return DNSSEC_MALFORMED_DATA; + } + + new_params.salt.data = malloc(new_params.salt.size); + if (new_params.salt.data == NULL) { + return DNSSEC_ENOMEM; + } + + binary_read(&wire, &new_params.salt); + assert(wire_ctx_offset(&wire) == rdata->size); + + *params = new_params; + + return DNSSEC_EOK; +} + +_public_ +bool dnssec_nsec_bitmap_contains(const uint8_t *bitmap, uint16_t size, uint16_t type) +{ + if (!bitmap || size == 0) { + return false; + } + + const uint8_t type_hi = (type >> 8); // Which window block contains type. + const uint8_t type_lo = (type & 0xff); + const uint8_t bitmap_idx = (type_lo >> 3); // Which byte in the window block contains type. + const uint8_t bit_mask = 1 << (7 - (type_lo & 0x07)); // Which bit in the byte represents type. + + size_t bitmap_pos = 0; + while (bitmap_pos + 3 <= size) { + uint8_t block_idx = bitmap[bitmap_pos++]; // Skip window block No. + uint8_t block_size = bitmap[bitmap_pos++]; // Skip window block size. + + // Size checks. + if (block_size == 0 || bitmap_pos + block_size > size) { + return false; + } + + // Check whether we found the correct window block. + if (block_idx == type_hi) { + if (bitmap_idx < block_size) { + // Check if the bit for type is set. + return bitmap[bitmap_pos + bitmap_idx] & bit_mask; + } + return false; + } else { + bitmap_pos += block_size; + } + } + + return false; +} + +_public_ +bool dnssec_nsec3_params_match(const dnssec_nsec3_params_t *params1, + const dnssec_nsec3_params_t *params2) +{ + if (params1 != NULL && params2 != NULL) { + return (params1->algorithm == params2->algorithm && + params1->flags == params2->flags && + params1->iterations == params2->iterations && + dnssec_binary_cmp(¶ms1->salt, ¶ms2->salt) == 0); + } else { + return (params1 == NULL && params2 == NULL); + } +} diff --git a/src/libdnssec/p11/p11.c b/src/libdnssec/p11/p11.c new file mode 100644 index 0000000..c3af544 --- /dev/null +++ b/src/libdnssec/p11/p11.c @@ -0,0 +1,113 @@ +/* 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/pkcs11.h> +#include <stdlib.h> +#include <string.h> + +#include "libdnssec/p11/p11.h" +#include "libdnssec/error.h" + +#ifdef ENABLE_PKCS11 + +#define PKCS11_MODULES_MAX 16 + +static char *pkcs11_modules[PKCS11_MODULES_MAX] = { 0 }; +static int pkcs11_modules_count = 0; + +static int map_result(int gnutls_result) +{ + return gnutls_result == GNUTLS_E_SUCCESS ? DNSSEC_EOK : DNSSEC_ERROR; +} + +int p11_init(void) +{ + int r = gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL); + return map_result(r); +} + +int p11_reinit(void) +{ + int r = gnutls_pkcs11_reinit(); + return map_result(r); +} + +int p11_load_module(const char *module) +{ + for (int i = 0; i < pkcs11_modules_count; i++) { + if (strcmp(pkcs11_modules[i], module) == 0) { + return DNSSEC_EOK; + } + } + + assert(pkcs11_modules_count <= PKCS11_MODULES_MAX); + if (pkcs11_modules_count == PKCS11_MODULES_MAX) { + return DNSSEC_P11_TOO_MANY_MODULES; + } + + char *copy = strdup(module); + if (!copy) { + return DNSSEC_ENOMEM; + } + + int r = gnutls_pkcs11_add_provider(module, NULL); + if (r != GNUTLS_E_SUCCESS) { + free(copy); + return DNSSEC_P11_FAILED_TO_LOAD_MODULE; + } + + pkcs11_modules[pkcs11_modules_count] = copy; + pkcs11_modules_count += 1; + + return DNSSEC_EOK; +} + +void p11_cleanup(void) +{ + for (int i = 0; i < pkcs11_modules_count; i++) { + free(pkcs11_modules[i]); + pkcs11_modules[i] = NULL; + } + + pkcs11_modules_count = 0; + + gnutls_pkcs11_deinit(); +} + +#else + +int p11_init(void) +{ + return DNSSEC_EOK; +} + +int p11_reinit(void) +{ + return DNSSEC_EOK; +} + +int p11_load_module(const char *module) +{ + return DNSSEC_NOT_IMPLEMENTED_ERROR; +} + +void p11_cleanup(void) +{ + // this function intentionally left blank +} + +#endif diff --git a/src/libdnssec/p11/p11.h b/src/libdnssec/p11/p11.h new file mode 100644 index 0000000..5642a55 --- /dev/null +++ b/src/libdnssec/p11/p11.h @@ -0,0 +1,41 @@ +/* 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 + +/*! + * Initialize PKCS11 global context. + */ +int p11_init(void); + +/*! + * Reinitialize PKCS11 global context after fork(). + */ +int p11_reinit(void); + +/*! + * Load PKCS11 module unless the module was already loaded. + * + * Duplicates are detected based on the module path. + */ +int p11_load_module(const char *name); + +/*! + * Cleanup PKCS11 global context. + * + * Should be called when the library is deinitialized to prevent memory leaks. + */ +void p11_cleanup(void); diff --git a/src/libdnssec/pem.c b/src/libdnssec/pem.c new file mode 100644 index 0000000..fa463f6 --- /dev/null +++ b/src/libdnssec/pem.c @@ -0,0 +1,182 @@ +/* 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 <assert.h> +#include <gnutls/abstract.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/pem.h" +#include "libdnssec/shared/shared.h" + +_public_ +int dnssec_pem_to_x509(const dnssec_binary_t *pem, gnutls_x509_privkey_t *key) +{ + if (!pem || !key) { + return DNSSEC_EINVAL; + } + + gnutls_datum_t data = binary_to_datum(pem); + + gnutls_x509_privkey_t _key = NULL; + int r = gnutls_x509_privkey_init(&_key); + if (r != GNUTLS_E_SUCCESS) { + return DNSSEC_ENOMEM; + } + + int format = GNUTLS_X509_FMT_PEM; + char *password = NULL; + int flags = GNUTLS_PKCS_PLAIN; + r = gnutls_x509_privkey_import_pkcs8(_key, &data, format, password, flags); + if (r != GNUTLS_E_SUCCESS) { + gnutls_x509_privkey_deinit(_key); + return DNSSEC_PKCS8_IMPORT_ERROR; + } + + *key = _key; + + return DNSSEC_EOK; +} + +_public_ +int dnssec_pem_to_privkey(const dnssec_binary_t *pem, gnutls_privkey_t *key) +{ + if (!pem || !key) { + return DNSSEC_EINVAL; + } + + gnutls_x509_privkey_t key_x509 = NULL; + int r = dnssec_pem_to_x509(pem, &key_x509); + if (r != DNSSEC_EOK) { + return r; + } + + gnutls_privkey_t key_abs = NULL; + r = gnutls_privkey_init(&key_abs); + if (r != GNUTLS_E_SUCCESS) { + gnutls_x509_privkey_deinit(key_x509); + return DNSSEC_ENOMEM; + } + + int flags = GNUTLS_PRIVKEY_IMPORT_AUTO_RELEASE; + r = gnutls_privkey_import_x509(key_abs, key_x509, flags); + if (r != GNUTLS_E_SUCCESS) { + gnutls_x509_privkey_deinit(key_x509); + gnutls_privkey_deinit(key_abs); + return DNSSEC_ENOMEM; + } + + *key = key_abs; + + return DNSSEC_EOK; +} + +static int try_export_pem(gnutls_x509_privkey_t key, dnssec_binary_t *pem) +{ + assert(key); + + gnutls_x509_crt_fmt_t format = GNUTLS_X509_FMT_PEM; + char *password = NULL; + int flags = GNUTLS_PKCS_PLAIN; + + return gnutls_x509_privkey_export_pkcs8(key, format, password, flags, + pem->data, &pem->size); +} + +_public_ +int dnssec_pem_from_x509(gnutls_x509_privkey_t key, dnssec_binary_t *pem) +{ + if (!key || !pem) { + return DNSSEC_EINVAL; + } + + dnssec_binary_t _pem = { 0 }; + int r = try_export_pem(key, &_pem); + if (r != GNUTLS_E_SHORT_MEMORY_BUFFER || _pem.size == 0) { + return DNSSEC_KEY_EXPORT_ERROR; + } + + r = dnssec_binary_alloc(&_pem, _pem.size); + if (r != DNSSEC_EOK) { + return r; + } + + r = try_export_pem(key, &_pem); + if (r != GNUTLS_E_SUCCESS) { + dnssec_binary_free(&_pem); + return DNSSEC_KEY_EXPORT_ERROR; + } + + *pem = _pem; + + return DNSSEC_EOK; +} + +static int privkey_export_x509(gnutls_privkey_t key, gnutls_x509_privkey_t *_key) +{ +#ifdef HAVE_EXPORT_X509 + if (gnutls_privkey_export_x509(key, _key) != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_EXPORT_ERROR; + } +#else // Needed for GnuTLS < 3.4.0 (CentOS 7) + struct privkey { // Extracted needed items only! + gnutls_privkey_type_t type; + gnutls_pk_algorithm_t pk_algorithm; + gnutls_x509_privkey_t x509; + }; + struct privkey *pkey = (struct privkey *)key; + + assert(pkey->type == GNUTLS_PRIVKEY_X509); + + if (gnutls_x509_privkey_init(_key) != GNUTLS_E_SUCCESS) { + return DNSSEC_KEY_EXPORT_ERROR; + } + + if (gnutls_x509_privkey_cpy(*_key, pkey->x509) != GNUTLS_E_SUCCESS) { + gnutls_x509_privkey_deinit(*_key); + return DNSSEC_KEY_EXPORT_ERROR; + } +#endif + return DNSSEC_EOK; +} + +_public_ +int dnssec_pem_from_privkey(gnutls_privkey_t key, dnssec_binary_t *pem) +{ + if (!key || !pem) { + return DNSSEC_EINVAL; + } + + _cleanup_x509_privkey_ gnutls_x509_privkey_t _key = NULL; + + int r = privkey_export_x509(key, &_key); + if (r != DNSSEC_EOK) { + return r; + } + + dnssec_binary_t _pem = { 0 }; + r = dnssec_pem_from_x509(_key, &_pem); + if (r != DNSSEC_EOK) { + return r; + } + + *pem = _pem; + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/pem.h b/src/libdnssec/pem.h new file mode 100644 index 0000000..c84d87d --- /dev/null +++ b/src/libdnssec/pem.h @@ -0,0 +1,73 @@ +/* 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 pem + * + * \brief PEM key format operations. + * + * @{ + */ + +#pragma once + +#include <gnutls/gnutls.h> + +#include <libdnssec/binary.h> + +/*! + * Create GnuTLS X.509 private key from unencrypted PEM data. + * + * \param[in] pem PEM binary data. + * \param[out] key Resulting private key. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_pem_to_x509(const dnssec_binary_t *pem, gnutls_x509_privkey_t *key); + +/*! + * Create GnuTLS private key from unencrypted PEM data. + * + * \param[in] pem PEM binary data. + * \param[out] key Resulting private key. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_pem_to_privkey(const dnssec_binary_t *pem, gnutls_privkey_t *key); + +/*! + * Export GnuTLS X.509 private key to PEM binary. + * + * \param[in] key Key to be exported. + * \param[out] pem Generated key in unencrypted PEM format. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_pem_from_x509(gnutls_x509_privkey_t key, dnssec_binary_t *pem); + +/*! + * Export GnuTLS private key to PEM binary. + * + * \param[in] key Key to be exported. + * \param[out] pem Generated key in unencrypted PEM format. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_pem_from_privkey(gnutls_privkey_t key, dnssec_binary_t *pem); + +/*! @} */ diff --git a/src/libdnssec/random.c b/src/libdnssec/random.c new file mode 100644 index 0000000..5d6be5d --- /dev/null +++ b/src/libdnssec/random.c @@ -0,0 +1,53 @@ +/* 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/gnutls.h> +#include <gnutls/crypto.h> +#include <stddef.h> +#include <stdint.h> + +#include "libdnssec/error.h" +#include "libdnssec/random.h" +#include "libdnssec/shared/shared.h" + +/* -- public API ----------------------------------------------------------- */ + +_public_ +int dnssec_random_buffer(uint8_t *data, size_t size) +{ + if (!data) { + return DNSSEC_EINVAL; + } + + int result = gnutls_rnd(GNUTLS_RND_RANDOM, data, size); + if (result != 0) { + assert_unreachable(); + return DNSSEC_ERROR; + } + + return DNSSEC_EOK; +} + +_public_ +int dnssec_random_binary(dnssec_binary_t *binary) +{ + if (!binary) { + return DNSSEC_EINVAL; + } + + return dnssec_random_buffer(binary->data, binary->size); +} diff --git a/src/libdnssec/random.h b/src/libdnssec/random.h new file mode 100644 index 0000000..8d4784f --- /dev/null +++ b/src/libdnssec/random.h @@ -0,0 +1,79 @@ +/* Copyright (C) 2021 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 random + * + * \brief Pseudo-random number generating API. + * + * The module provides generating of pseudo-random numbers and buffers. + * + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <libdnssec/binary.h> + +/*! + * Fill a buffer with pseudo-random data. + * + * \param data Pointer to the output buffer. + * \param size Size of the output buffer. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_random_buffer(uint8_t *data, size_t size); + +/*! + * Fill a binary structure with random data. + * + * \param data Preallocated binary structure to be filled. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_random_binary(dnssec_binary_t *data); + +/*! + * Declare function dnssec_random_<type>(). + */ +#define dnssec_register_random_type(type) \ + static inline type dnssec_random_##type(void) { \ + type value; \ + dnssec_random_buffer((uint8_t *)&value, sizeof(value)); \ + return value; \ + } + +/*! + * Generate pseudo-random 16-bit number. + */ +static inline uint16_t dnssec_random_uint16_t(void); + +/*! + * Generate pseudo-random 32-bit number. + */ +static inline uint32_t dnssec_random_uint32_t(void); + +/*! \cond */ +dnssec_register_random_type(uint16_t); +dnssec_register_random_type(uint32_t); +dnssec_register_random_type(uint64_t); +/*! \endcond */ + +/*! @} */ diff --git a/src/libdnssec/shared/bignum.c b/src/libdnssec/shared/bignum.c new file mode 100644 index 0000000..3b347a6 --- /dev/null +++ b/src/libdnssec/shared/bignum.c @@ -0,0 +1,64 @@ +/* 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 <string.h> + +#include "libdnssec/shared/bignum.h" + +static void skip_leading_zeroes(dnssec_binary_t *value) +{ + while (value->size > 0 && value->data[0] == 0) { + value->data += 1; + value->size -= 1; + } +} + +size_t bignum_size_u(const dnssec_binary_t *_value) +{ + dnssec_binary_t value = *_value; + skip_leading_zeroes(&value); + + if (value.size == 0) { + return value.size + 1; + } else { + return value.size; + } +} + +size_t bignum_size_s(const dnssec_binary_t *_value) +{ + dnssec_binary_t value = *_value; + skip_leading_zeroes(&value); + + if (value.size == 0 || value.data[0] & 0x80) { + return value.size + 1; + } else { + return value.size; + } +} + +void bignum_write(wire_ctx_t *ctx, size_t width, const dnssec_binary_t *_value) +{ + dnssec_binary_t value = *_value; + skip_leading_zeroes(&value); + + size_t padding_len = width - value.size; + if (padding_len > 0) { + wire_ctx_clear(ctx, padding_len); + } + wire_ctx_write(ctx, value.data, value.size); +} diff --git a/src/libdnssec/shared/bignum.h b/src/libdnssec/shared/bignum.h new file mode 100644 index 0000000..e4ddede --- /dev/null +++ b/src/libdnssec/shared/bignum.h @@ -0,0 +1,41 @@ +/* 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 <stdlib.h> + +#include "libdnssec/binary.h" +#include "contrib/wire_ctx.h" + +/*! + * Size needed to write unsigned number in unsigned encoding. + */ +size_t bignum_size_u(const dnssec_binary_t *value); + +/*! + * Size needed to write unsigned number in signed encoding. + * + * Signed encoding expects the MSB to be zero. + */ +size_t bignum_size_s(const dnssec_binary_t *value); + +/*! + * Write unsigned number on a fixed width in a big-endian byte order. + * + * The destination size has to be set properly to accommodate used encoding. + */ +void bignum_write(wire_ctx_t *ctx, size_t width, const dnssec_binary_t *value); diff --git a/src/libdnssec/shared/binary_wire.h b/src/libdnssec/shared/binary_wire.h new file mode 100644 index 0000000..807cfc6 --- /dev/null +++ b/src/libdnssec/shared/binary_wire.h @@ -0,0 +1,53 @@ +/* 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 <stdlib.h> + +#include "contrib/wire_ctx.h" +#include "libdnssec/binary.h" + +static inline wire_ctx_t binary_init(const dnssec_binary_t *binary) +{ + assert(binary); + + return wire_ctx_init(binary->data, binary->size); +} + +static inline void binary_read(wire_ctx_t *ctx, dnssec_binary_t *data) +{ + assert(data); + + wire_ctx_read(ctx, data->data, data->size); +} + +static inline void binary_available(wire_ctx_t *ctx, dnssec_binary_t *data) +{ + assert(ctx); + assert(data); + + data->data = ctx->position; + data->size = wire_ctx_available(ctx); +} + +static inline void binary_write(wire_ctx_t *ctx, const dnssec_binary_t *data) +{ + assert(ctx); + assert(data); + + wire_ctx_write(ctx, data->data, data->size); +} diff --git a/src/libdnssec/shared/dname.c b/src/libdnssec/shared/dname.c new file mode 100644 index 0000000..61a5034 --- /dev/null +++ b/src/libdnssec/shared/dname.c @@ -0,0 +1,165 @@ +/* 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 <stdbool.h> +#include <stdlib.h> +#include <stdint.h> +#include <string.h> + +#include "libdnssec/shared/dname.h" +#include "libdnssec/shared/shared.h" +#include "contrib/tolower.h" + +/*! + * Get length of a domain name in wire format. + */ +size_t dname_length(const uint8_t *dname) +{ + if (!dname) { + return 0; + } + + const uint8_t *scan = dname; + uint8_t label_len; + do { + label_len = *scan; + scan += 1 + label_len; + } while (label_len > 0); + assert(scan > dname); + + size_t length = scan - dname; + if (length > DNAME_MAX_LENGTH) { + return 0; + } + + return length; +} + +/*! + * Copy domain name in wire format. + */ +uint8_t *dname_copy(const uint8_t *dname) +{ + if (!dname) { + return NULL; + } + + size_t length = dname_length(dname); + if (length == 0) { + return NULL; + } + + uint8_t *copy = malloc(length); + if (!copy) { + return NULL; + } + + memmove(copy, dname, length); + return copy; +} + +/*! + * Normalize dname label in-place. + * + * \return Number of processed bytes, 0 if we encounter the last label. + */ +static uint8_t normalize_label(uint8_t *label) +{ + assert(label); + + uint8_t len = *label; + if (len == 0 || len > DNAME_MAX_LABEL_LENGTH) { + return 0; + } + + for (uint8_t *scan = label + 1, *end = scan + len; scan < end; scan++) { + *scan = knot_tolower(*scan); + } + + return len + 1; +} + +/*! + * Normalize domain name in wire format. + */ +void dname_normalize(uint8_t *dname) +{ + if (!dname) { + return; + } + + uint8_t read, *scan = dname; + do { + read = normalize_label(scan); + scan += read; + } while (read > 0); +} + +/*! + * Compare dname labels case insensitively. + */ +static int label_casecmp(const uint8_t *a, const uint8_t *b, uint8_t len) +{ + assert(a); + assert(b); + + for (const uint8_t *a_end = a + len; a < a_end; a++, b++) { + if (knot_tolower(*a) != knot_tolower(*b)) { + return false; + } + } + + return true; +} + +/*! + * Check if two dnames are equal. + */ +bool dname_equal(const uint8_t *one, const uint8_t *two) +{ + if (!one || !two) { + return false; + } + + const uint8_t *scan_one = one; + const uint8_t *scan_two = two; + + for (;;) { + if (*scan_one != *scan_two) { + return false; + } + + uint8_t len = *scan_one; + if (len == 0) { + return true; + } else if (len > DNAME_MAX_LABEL_LENGTH) { + return false; + } + + scan_one += 1; + scan_two += 1; + + if (!label_casecmp(scan_one, scan_two, len)) { + return false; + } + + scan_one += len; + scan_two += len; + } + + return true; +} diff --git a/src/libdnssec/shared/dname.h b/src/libdnssec/shared/dname.h new file mode 100644 index 0000000..15e4e2a --- /dev/null +++ b/src/libdnssec/shared/dname.h @@ -0,0 +1,57 @@ +/* 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 <stdbool.h> +#include <stdint.h> +#include <stdlib.h> + +/*! + * Maximal length of domain name including labels and length bytes. + * \see RFC 1035 + */ +#define DNAME_MAX_LENGTH 255 + +/*! + * Maximal length of the domain name label, excluding the label size. + * \see RFC 1035 + */ +#define DNAME_MAX_LABEL_LENGTH 63 + +/*! + * Get length of a domain name in wire format. + */ +size_t dname_length(const uint8_t *dname); + +/*! + * Copy domain name in wire format. + */ +uint8_t *dname_copy(const uint8_t *dname); + +/*! + * Normalize domain name in wire format. + * + * Currently converts all letters to lowercase. + */ +void dname_normalize(uint8_t *dname); + +/*! + * Check if two dnames are equal. + * + * Case insensitive. + */ +bool dname_equal(const uint8_t *one, const uint8_t *two); diff --git a/src/libdnssec/shared/keyid_gnutls.c b/src/libdnssec/shared/keyid_gnutls.c new file mode 100644 index 0000000..eee27d3 --- /dev/null +++ b/src/libdnssec/shared/keyid_gnutls.c @@ -0,0 +1,99 @@ +/* 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 <gnutls/abstract.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> +#include <string.h> + +#include "contrib/string.h" +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/keyid.h" +#include "libdnssec/shared/keyid_gnutls.h" +#include "libdnssec/shared/shared.h" + +/*! + * Get binary key ID from a key (public or private). + */ +static int keyid_bin(gnutls_x509_privkey_t key, gnutls_pubkey_t pubkey, dnssec_binary_t *id) +{ + assert(key || pubkey); + assert(id); + + // Flags can be used to enable SHA-2 since GnuTLS 3.4.7. + + int flags = 0; + uint8_t buffer[DNSSEC_KEYID_BINARY_SIZE]; + size_t size = DNSSEC_KEYID_SIZE; + + int r = key ? gnutls_x509_privkey_get_key_id(key, flags, buffer, &size) + : gnutls_pubkey_get_key_id(pubkey, flags, buffer, &size); + + if (r != GNUTLS_E_SUCCESS || size != DNSSEC_KEYID_BINARY_SIZE) { + return DNSSEC_INVALID_KEY_ID; + } + + assert(size == DNSSEC_KEYID_BINARY_SIZE); + r = dnssec_binary_resize(id, size); + if (r != DNSSEC_EOK) { + return r; + } + + memcpy(id->data, buffer, size); + + return DNSSEC_EOK; +} + +/*! + * Get hexadecimal key ID from a key (public or private). + */ +static int keyid_hex(gnutls_x509_privkey_t key, gnutls_pubkey_t pubkey, char **id) +{ + _cleanup_binary_ dnssec_binary_t bin = { 0 }; + int r = keyid_bin(key, pubkey, &bin); + if (r != DNSSEC_EOK) { + return r; + } + + *id = bin_to_hex(bin.data, bin.size, false); + if (*id == NULL) { + return DNSSEC_ENOMEM; + } + + return DNSSEC_EOK; +} + +int keyid_x509(gnutls_x509_privkey_t key, dnssec_binary_t *id) +{ + return keyid_bin(key, NULL, id); +} + +int keyid_x509_hex(gnutls_x509_privkey_t key, char **id) +{ + return keyid_hex(key, NULL, id); +} + +int keyid_pubkey(gnutls_pubkey_t pubkey, dnssec_binary_t *id) +{ + return keyid_bin(NULL, pubkey, id); +} + +int keyid_pubkey_hex(gnutls_pubkey_t pubkey, char **id) +{ + return keyid_hex(NULL, pubkey, id); +} diff --git a/src/libdnssec/shared/keyid_gnutls.h b/src/libdnssec/shared/keyid_gnutls.h new file mode 100644 index 0000000..356e62e --- /dev/null +++ b/src/libdnssec/shared/keyid_gnutls.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/abstract.h> +#include <gnutls/gnutls.h> + +#include "libdnssec/binary.h" + +int keyid_x509(gnutls_x509_privkey_t key, dnssec_binary_t *id); + +int keyid_x509_hex(gnutls_x509_privkey_t key, char **id); + +int keyid_pubkey(gnutls_pubkey_t pubkey, dnssec_binary_t *id); + +int keyid_pubkey_hex(gnutls_pubkey_t pubkey, char **id); diff --git a/src/libdnssec/shared/shared.h b/src/libdnssec/shared/shared.h new file mode 100644 index 0000000..1cde2d1 --- /dev/null +++ b/src/libdnssec/shared/shared.h @@ -0,0 +1,121 @@ +/* Copyright (C) 2021 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 <assert.h> +#include <dirent.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <unistd.h> + +#include <gnutls/abstract.h> +#include <gnutls/crypto.h> +#include <gnutls/gnutls.h> +#include <gnutls/x509.h> + +#include "libdnssec/binary.h" +#include "libknot/attribute.h" + +/*! + * Macro to clear a structure of known size. + * + * \param pointer Pointer to the structure. + */ +#define clear_struct(pointer) memset((pointer), '\0', sizeof(*(pointer))) + +/* -- cleanup macros ------------------------------------------------------- */ + +static inline void free_ptr(void *ptr) +{ + free(*(void **)ptr); +} + +static inline void close_ptr(int *ptr) +{ + if (*ptr != -1) { + close(*ptr); + } +} + +static inline void fclose_ptr(FILE **ptr) +{ + if (*ptr) { + fclose(*ptr); + } +} + +static inline void closedir_ptr(DIR **ptr) +{ + if (*ptr) { + closedir(*ptr); + } +} + +static inline void free_gnutls_datum_ptr(gnutls_datum_t *ptr) +{ + gnutls_free(ptr->data); +} + +static inline void free_x509_privkey_ptr(gnutls_x509_privkey_t *ptr) +{ + if (*ptr) { + gnutls_x509_privkey_deinit(*ptr); + } +} + +static inline void free_pubkey_ptr(gnutls_pubkey_t *ptr) +{ + if (*ptr) { + gnutls_pubkey_deinit(*ptr); + } +} + +static inline void free_gnutls_hash_ptr(gnutls_hash_hd_t *ptr) +{ + if (*ptr) { + gnutls_hash_deinit(*ptr, NULL); + } +} + +#define _cleanup_free_ _cleanup_(free_ptr) +#define _cleanup_close_ _cleanup_(close_ptr) +#define _cleanup_fclose_ _cleanup_(fclose_ptr) +#define _cleanup_closedir_ _cleanup_(closedir_ptr) +#define _cleanup_binary_ _cleanup_(dnssec_binary_free) +#define _cleanup_datum_ _cleanup_(free_gnutls_datum_ptr) +#define _cleanup_x509_privkey_ _cleanup_(free_x509_privkey_ptr) +#define _cleanup_pubkey_ _cleanup_(free_pubkey_ptr) +#define _cleanup_hash_ _cleanup_(free_gnutls_hash_ptr) + +/* -- assertions ----------------------------------------------------------- */ + +#define assert_unreachable() assert(0) + +/* -- crypto helpers ------------------------------------------------------- */ + +static inline gnutls_datum_t binary_to_datum(const dnssec_binary_t *from) +{ + gnutls_datum_t to = { .size = from->size, .data = from->data }; + return to; +} + +static inline dnssec_binary_t binary_from_datum(const gnutls_datum_t *from) +{ + dnssec_binary_t to = { .size = from->size, .data = from->data }; + return to; +} diff --git a/src/libdnssec/sign.h b/src/libdnssec/sign.h new file mode 100644 index 0000000..0311b52 --- /dev/null +++ b/src/libdnssec/sign.h @@ -0,0 +1,114 @@ +/* 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 sign + * + * \brief DNSSEC signing API + * + * The module provides the low level DNSSEC signing and verification. + * + * @{ + */ + +#pragma once + +#include <stdint.h> +#include <stdlib.h> + +#include <libdnssec/binary.h> +#include <libdnssec/key.h> + +struct dnssec_sign_ctx; + +typedef enum { + DNSSEC_SIGN_NORMAL = 0, + DNSSEC_SIGN_REPRODUCIBLE = (1 << 0), +} dnssec_sign_flags_t; + +/*! + * DNSSEC signing context. + */ +typedef struct dnssec_sign_ctx dnssec_sign_ctx_t; + +/*! + * Create new DNSSEC signing context. + * + * \note \ref dnssec_sign_init is called as a part of this function. + * + * \param ctx_ptr Pointer to context to be allocated. + * \param key DNSSEC key to be used. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_sign_new(dnssec_sign_ctx_t **ctx_ptr, const dnssec_key_t *key); + +/*! + * Free DNSSEC signing context. + * + * \param ctx Signing context to be freed. + */ +void dnssec_sign_free(dnssec_sign_ctx_t *ctx); + +/*! + * Reinitialize DNSSEC signing context to start a new operation. + * + * \param ctx Signing context. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_sign_init(dnssec_sign_ctx_t *ctx); + +/*! + * Add data to be covered by DNSSEC signature. + * + * \param ctx Signing context. + * \param data Data to be signed. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_sign_add(dnssec_sign_ctx_t *ctx, const dnssec_binary_t *data); + +/*! + * Write down the DNSSEC signature. + * + * \param ctx Signing context. + * \param flags Additional flags to be used for signing. + * \param signature Signature to be allocated and written. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_sign_write(dnssec_sign_ctx_t *ctx, dnssec_sign_flags_t flags, + dnssec_binary_t *signature); + +/*! + * Verify DNSSEC signature. + * + * \param ctx Signing context. + * \param sign_cmp Verify by signing and comparing signatures. + * Not possible for non-deterministic algorithms! + * \param signature Signature to be verified. + * + * \return Error code. + * \retval DNSSEC_EOK Validation successful, valid signature. + * \retval DNSSEC_INVALID_SIGNATURE Validation successful, invalid signature. + */ +int dnssec_sign_verify(dnssec_sign_ctx_t *ctx, bool sign_cmp, + const dnssec_binary_t *signature); + +/*! @} */ diff --git a/src/libdnssec/sign/der.c b/src/libdnssec/sign/der.c new file mode 100644 index 0000000..cd6102d --- /dev/null +++ b/src/libdnssec/sign/der.c @@ -0,0 +1,229 @@ +/* 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 <stdbool.h> + +#include "libdnssec/shared/bignum.h" +#include "libdnssec/binary.h" +#include "libdnssec/error.h" +#include "libdnssec/sign/der.h" +#include "libdnssec/shared/binary_wire.h" + +/* + * In fact, this is a very tiny subset of ASN.1 encoding format implementation, + * which is necessary for the purpose of DNSSEC. + * + * References: RFC 3279 (X.509 PKI), X.690, RFC 6605 (ECDSA), RFC8080 (EDDSA) + * + * Dss-Sig-Value ::= SEQUENCE { r INTEGER, s INTEGER } + */ + +#define ASN1_TYPE_SEQUENCE 0x30 +#define ASN1_TYPE_INTEGER 0x02 + +#define ASN1_MAX_SIZE 127 + +/*! + * Check if the next object has a given type. + */ +static bool asn1_expect_type(wire_ctx_t *wire, uint8_t type) +{ + assert(wire); + return (wire_ctx_available(wire) >= 1 && wire_ctx_read_u8(wire) == type); +} + +/*! + * Decode the size of the object (only short format is supported). + */ +static int asn1_decode_size(wire_ctx_t *wire, size_t *size) +{ + assert(wire); + assert(size); + + if (wire_ctx_available(wire) < 1) { + return DNSSEC_MALFORMED_DATA; + } + + uint8_t byte = wire_ctx_read_u8(wire); + if (byte & 0x80) { + // long form, we do not need it for DNSSEC + return DNSSEC_NOT_IMPLEMENTED_ERROR; + } + + *size = byte; + + return DNSSEC_EOK; +} + +/*! + * Decode an unsigned integer object. + */ +static int asn1_decode_integer(wire_ctx_t *wire, dnssec_binary_t *_value) +{ + assert(wire); + assert(_value); + + if (!asn1_expect_type(wire, ASN1_TYPE_INTEGER)) { + return DNSSEC_MALFORMED_DATA; + } + + size_t size; + int result = asn1_decode_size(wire, &size); + if (result != DNSSEC_EOK) { + return result; + } + + if (size == 0 || size > wire_ctx_available(wire)) { + return DNSSEC_MALFORMED_DATA; + } + + dnssec_binary_t value = { .data = wire->position, .size = size }; + wire->position += size; + + // skip leading zeroes (unless equal to zero) + while (value.size > 1 && value.data[0] == 0) { + value.data += 1; + value.size -= 1; + } + + *_value = value; + + return DNSSEC_EOK; +} + +/*! + * Encode object header (type and length). + */ +static void asn1_write_header(wire_ctx_t *wire, uint8_t type, size_t length) +{ + assert(wire); + assert(length < ASN1_MAX_SIZE); + + wire_ctx_write_u8(wire, type); + wire_ctx_write_u8(wire, length); +} + +/*! + * Encode unsigned integer object. + */ +static void asn1_write_integer(wire_ctx_t *wire, size_t integer_size, + const dnssec_binary_t *integer) +{ + assert(wire); + assert(integer); + assert(integer->data); + + asn1_write_header(wire, ASN1_TYPE_INTEGER, integer_size); + bignum_write(wire, integer_size, integer); +} + +/*! + * Decode signature parameters from X.509 ECDSA signature. + */ +int dss_sig_value_decode(const dnssec_binary_t *der, + dnssec_binary_t *r, dnssec_binary_t *s) +{ + if (!der || !der->data || !r || !s) { + return DNSSEC_EINVAL; + } + + wire_ctx_t wire = binary_init(der); + + size_t size; + int result; + + // decode the sequence + + if (!asn1_expect_type(&wire, ASN1_TYPE_SEQUENCE)) { + return DNSSEC_MALFORMED_DATA; + } + + result = asn1_decode_size(&wire, &size); + if (result != DNSSEC_EOK) { + return result; + } + + if (size != wire_ctx_available(&wire)) { + return DNSSEC_MALFORMED_DATA; + } + + // decode the 'r' and 's' values + + dnssec_binary_t der_r; + result = asn1_decode_integer(&wire, &der_r); + if (result != DNSSEC_EOK) { + return result; + } + + dnssec_binary_t der_s; + result = asn1_decode_integer(&wire, &der_s); + if (result != DNSSEC_EOK) { + return result; + } + + if (wire_ctx_available(&wire) != 0) { + return DNSSEC_MALFORMED_DATA; + } + + *r = der_r; + *s = der_s; + + return DNSSEC_EOK; +} + +/*! + * Encode signature parameters from X.509 ECDSA signature. + */ +int dss_sig_value_encode(const dnssec_binary_t *r, const dnssec_binary_t *s, + dnssec_binary_t *der) +{ + if (!r || !r->data || !s || !s->data || !der) { + return DNSSEC_EINVAL; + } + + size_t r_size = bignum_size_s(r); + size_t s_size = bignum_size_s(s); + + // check supported inputs range + + if (r_size > ASN1_MAX_SIZE || s_size > ASN1_MAX_SIZE) { + return DNSSEC_NOT_IMPLEMENTED_ERROR; + } + + size_t seq_size = 2 + r_size + 2 + s_size; + if (seq_size > ASN1_MAX_SIZE) { + return DNSSEC_NOT_IMPLEMENTED_ERROR; + } + + // encode result + + size_t total_size = 2 + seq_size; + + dnssec_binary_t _der = { 0 }; + if (dnssec_binary_alloc(&_der, total_size)) { + return DNSSEC_ENOMEM; + } + + wire_ctx_t wire = binary_init(&_der); + asn1_write_header(&wire, ASN1_TYPE_SEQUENCE, seq_size); + asn1_write_integer(&wire, r_size, r); + asn1_write_integer(&wire, s_size, s); + assert(wire_ctx_available(&wire) == 0); + + *der = _der; + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/sign/der.h b/src/libdnssec/sign/der.h new file mode 100644 index 0000000..687b061 --- /dev/null +++ b/src/libdnssec/sign/der.h @@ -0,0 +1,56 @@ +/* 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 "libdnssec/binary.h" + +/* + * The ECDSA signatures in DNSSEC are encoded differently than in X.509 + * (PKCS #1). The cryptographic libraries usually produce the signatures in + * X.509 format, which uses Dss-Sig-Value to encapsulate 'r' and 's' values + * of the signature. + * + * This module provides decoding and encoding of this format. + * + * The 'r' and 's' values are treated as unsigned values: The leading zeroes + * are stripped on decoding; an extra leading zero is added on encoding in case + * the value starts with a set bit. + */ + +/*! + * Decode signature parameters from X.509 ECDSA signature. + * + * \param[in] der X.509 encoded signature. + * \param[out] s Value 's' of the signature, will point to the data in DER. + * \param[out] r Value 'r' of the signature, will point to the data in DER. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dss_sig_value_decode(const dnssec_binary_t *der, + dnssec_binary_t *r, dnssec_binary_t *s); + +/*! + * Encode signature parameters from X.509 ECDSA signature. + * + * \param[in] s Value 's' of the signature. + * \param[in] r Value 'r' of the signature. + * \param[out] der X.509 signature, the content will be allocated. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dss_sig_value_encode(const dnssec_binary_t *r, const dnssec_binary_t *s, + dnssec_binary_t *der); diff --git a/src/libdnssec/sign/sign.c b/src/libdnssec/sign/sign.c new file mode 100644 index 0000000..3a7bcba --- /dev/null +++ b/src/libdnssec/sign/sign.c @@ -0,0 +1,433 @@ +/* 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 <assert.h> +#include <stdlib.h> +#include <string.h> +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> + +#include "contrib/macros.h" +#include "contrib/vpool/vpool.h" +#include "libdnssec/shared/bignum.h" +#include "libdnssec/error.h" +#include "libdnssec/key.h" +#include "libdnssec/key/internal.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/sign.h" +#include "libdnssec/sign/der.h" +#include "libdnssec/shared/binary_wire.h" + +/*! + * Signature format conversion callback. + * + * \param ctx DNSSEC signing context. + * \param from Data in source format. + * \param to Allocated data in target format. + * + * \return Error code, DNSSEC_EOK if successful. + */ +typedef int (*signature_convert_cb)(dnssec_sign_ctx_t *ctx, + const dnssec_binary_t *from, + dnssec_binary_t *to); + +/*! + * Algorithm specific callbacks. + */ +typedef struct algorithm_functions { + //! Convert X.509 signature to DNSSEC format. + signature_convert_cb x509_to_dnssec; + //! Convert DNSSEC signature to X.509 format. + signature_convert_cb dnssec_to_x509; +} algorithm_functions_t; + +typedef struct dnssec_buffer { + uint8_t *allocd; //!< Pointer to allocated data. + uint8_t *data; //!< API: pointer to data to copy from. + size_t max_length; + size_t length; //!< API: current length. +} dnssec_buffer_t; + +/*! + * DNSSEC signing context. + */ +struct dnssec_sign_ctx { + const dnssec_key_t *key; //!< Signing key. + const algorithm_functions_t *functions; //!< Implementation specific. + + gnutls_sign_algorithm_t sign_algorithm; //!< Used algorithm for signing. + struct vpool buffer; //!< Buffer for the data to be signed. +}; + +/* -- signature format conversions ----------------------------------------- */ + +/*! + * Conversion of RSA signature between X.509 and DNSSEC format is a NOOP. + * + * \note Described in RFC 3110. + */ +static int rsa_copy_signature(dnssec_sign_ctx_t *ctx, + const dnssec_binary_t *from, + dnssec_binary_t *to) +{ + assert(ctx); + assert(from); + assert(to); + + return dnssec_binary_dup(from, to); +} + +static const algorithm_functions_t rsa_functions = { + .x509_to_dnssec = rsa_copy_signature, + .dnssec_to_x509 = rsa_copy_signature, +}; + +static size_t ecdsa_sign_integer_size(dnssec_sign_ctx_t *ctx) +{ + assert(ctx); + + switch (ctx->sign_algorithm) { + case GNUTLS_SIGN_ECDSA_SHA256: return 32; + case GNUTLS_SIGN_ECDSA_SHA384: return 48; + default: return 0; + }; +} + +/*! + * Convert ECDSA signature to DNSSEC format. + * + * \note Described in RFC 6605. + */ +static int ecdsa_x509_to_dnssec(dnssec_sign_ctx_t *ctx, + const dnssec_binary_t *x509, + dnssec_binary_t *dnssec) +{ + assert(ctx); + assert(x509); + assert(dnssec); + + dnssec_binary_t value_r = { 0 }; + dnssec_binary_t value_s = { 0 }; + + int result = dss_sig_value_decode(x509, &value_r, &value_s); + if (result != DNSSEC_EOK) { + return result; + } + + size_t int_size = ecdsa_sign_integer_size(ctx); + size_t r_size = bignum_size_u(&value_r); + size_t s_size = bignum_size_u(&value_s); + + if (r_size > int_size || s_size > int_size) { + return DNSSEC_MALFORMED_DATA; + } + + result = dnssec_binary_alloc(dnssec, 2 * int_size); + if (result != DNSSEC_EOK) { + return result; + } + + wire_ctx_t wire = binary_init(dnssec); + bignum_write(&wire, int_size, &value_r); + bignum_write(&wire, int_size, &value_s); + assert(wire_ctx_offset(&wire) == dnssec->size); + + return DNSSEC_EOK; +} + +static int ecdsa_dnssec_to_x509(dnssec_sign_ctx_t *ctx, + const dnssec_binary_t *dnssec, + dnssec_binary_t *x509) +{ + assert(ctx); + assert(x509); + assert(dnssec); + + size_t int_size = ecdsa_sign_integer_size(ctx); + + if (dnssec->size != 2 * int_size) { + return DNSSEC_INVALID_SIGNATURE; + } + + const dnssec_binary_t value_r = { .size = int_size, .data = dnssec->data }; + const dnssec_binary_t value_s = { .size = int_size, .data = dnssec->data + int_size }; + + return dss_sig_value_encode(&value_r, &value_s, x509); +} + +static const algorithm_functions_t ecdsa_functions = { + .x509_to_dnssec = ecdsa_x509_to_dnssec, + .dnssec_to_x509 = ecdsa_dnssec_to_x509, +}; + +#define eddsa_copy_signature rsa_copy_signature +static const algorithm_functions_t eddsa_functions = { + .x509_to_dnssec = eddsa_copy_signature, + .dnssec_to_x509 = eddsa_copy_signature, +}; + +/* -- crypto helper functions --------------------------------------------- */ + +static const algorithm_functions_t *get_functions(const dnssec_key_t *key) +{ + uint8_t algorithm = dnssec_key_get_algorithm(key); + + switch ((dnssec_key_algorithm_t)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_functions; + case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: + case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: + return &ecdsa_functions; + case DNSSEC_KEY_ALGORITHM_ED25519: + case DNSSEC_KEY_ALGORITHM_ED448: + return &eddsa_functions; + default: + return NULL; + } +} + +#ifndef HAVE_SIGN_DATA2 +/*! + * Get digest algorithm used with a given key. + */ +static gnutls_digest_algorithm_t get_digest_algorithm(const dnssec_key_t *key) +{ + uint8_t algorithm = dnssec_key_get_algorithm(key); + + switch ((dnssec_key_algorithm_t)algorithm) { + case DNSSEC_KEY_ALGORITHM_RSA_SHA1: + case DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3: + return GNUTLS_DIG_SHA1; + case DNSSEC_KEY_ALGORITHM_RSA_SHA256: + case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: + return GNUTLS_DIG_SHA256; + case DNSSEC_KEY_ALGORITHM_RSA_SHA512: + return GNUTLS_DIG_SHA512; + case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: + return GNUTLS_DIG_SHA384; + case DNSSEC_KEY_ALGORITHM_ED25519: + case DNSSEC_KEY_ALGORITHM_ED448: + return GNUTLS_DIG_SHA512; + default: + return GNUTLS_DIG_UNKNOWN; + } +} +#endif + +static gnutls_sign_algorithm_t algo_dnssec2gnutls(dnssec_key_algorithm_t algorithm) +{ + switch (algorithm) { + case DNSSEC_KEY_ALGORITHM_RSA_SHA1: + case DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3: + return GNUTLS_SIGN_RSA_SHA1; + case DNSSEC_KEY_ALGORITHM_RSA_SHA256: + return GNUTLS_SIGN_RSA_SHA256; + case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256: + return GNUTLS_SIGN_ECDSA_SHA256; + case DNSSEC_KEY_ALGORITHM_RSA_SHA512: + return GNUTLS_SIGN_RSA_SHA512; + case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384: + return GNUTLS_SIGN_ECDSA_SHA384; +#ifdef HAVE_ED25519 + case DNSSEC_KEY_ALGORITHM_ED25519: + return GNUTLS_SIGN_EDDSA_ED25519; +#endif +#ifdef HAVE_ED448 + case DNSSEC_KEY_ALGORITHM_ED448: + return GNUTLS_SIGN_EDDSA_ED448; +#endif + default: + return GNUTLS_SIGN_UNKNOWN; + } +} + +/* -- public API ---------------------------------------------------------- */ + +_public_ +bool dnssec_algorithm_key_support(dnssec_key_algorithm_t algorithm) +{ + gnutls_sign_algorithm_t a = algo_dnssec2gnutls(algorithm); + return a != GNUTLS_SIGN_UNKNOWN && gnutls_sign_is_secure(a); +} + +_public_ +int dnssec_sign_new(dnssec_sign_ctx_t **ctx_ptr, const dnssec_key_t *key) +{ + if (!ctx_ptr) { + return DNSSEC_EINVAL; + } + + dnssec_sign_ctx_t *ctx = calloc(1, sizeof(*ctx)); + + ctx->key = key; + + ctx->functions = get_functions(key); + if (ctx->functions == NULL) { + free(ctx); + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + const uint8_t algo_raw = dnssec_key_get_algorithm(key); + ctx->sign_algorithm = algo_dnssec2gnutls((dnssec_key_algorithm_t)algo_raw); + int result = dnssec_sign_init(ctx); + if (result != DNSSEC_EOK) { + free(ctx); + return result; + } + + *ctx_ptr = ctx; + + return DNSSEC_EOK; +} + +_public_ +void dnssec_sign_free(dnssec_sign_ctx_t *ctx) +{ + if (!ctx) { + return; + } + + vpool_reset(&ctx->buffer); + + free(ctx); +} + +_public_ +int dnssec_sign_init(dnssec_sign_ctx_t *ctx) +{ + if (!ctx) { + return DNSSEC_EINVAL; + } + + if (vpool_get_buf(&ctx->buffer) != NULL) { + vpool_wipe(&ctx->buffer); + } else { + vpool_init(&ctx->buffer, 1024, 0); + } + + return DNSSEC_EOK; +} + +_public_ +int dnssec_sign_add(dnssec_sign_ctx_t *ctx, const dnssec_binary_t *data) +{ + if (!ctx || !data || !data->data) { + return DNSSEC_EINVAL; + } + + void *result = vpool_insert(&ctx->buffer, vpool_get_length(&ctx->buffer), data->data, data->size); + if (result == NULL) { + return DNSSEC_SIGN_ERROR; + } + + return DNSSEC_EOK; +} + +_public_ +int dnssec_sign_write(dnssec_sign_ctx_t *ctx, dnssec_sign_flags_t flags, dnssec_binary_t *signature) +{ + if (!ctx || !signature) { + return DNSSEC_EINVAL; + } + + if (!dnssec_key_can_sign(ctx->key)) { + return DNSSEC_NO_PRIVATE_KEY; + } + + gnutls_datum_t data = { + .data = vpool_get_buf(&ctx->buffer), + .size = vpool_get_length(&ctx->buffer) + }; + + unsigned gnutls_flags = 0; +#ifdef HAVE_GNUTLS_REPRODUCIBLE + if (flags & DNSSEC_SIGN_REPRODUCIBLE) { + gnutls_flags |= GNUTLS_PRIVKEY_FLAG_REPRODUCIBLE; + } +#endif + + assert(ctx->key->private_key); + _cleanup_datum_ gnutls_datum_t raw = { 0 }; +#ifdef HAVE_SIGN_DATA2 + int result = gnutls_privkey_sign_data2(ctx->key->private_key, + ctx->sign_algorithm, + gnutls_flags, &data, &raw); +#else + gnutls_digest_algorithm_t digest_algorithm = get_digest_algorithm(ctx->key); + int result = gnutls_privkey_sign_data(ctx->key->private_key, + digest_algorithm, + gnutls_flags, &data, &raw); +#endif + if (result < 0) { + return DNSSEC_SIGN_ERROR; + } + + dnssec_binary_t bin_raw = binary_from_datum(&raw); + + return ctx->functions->x509_to_dnssec(ctx, &bin_raw, signature); +} + +_public_ +int dnssec_sign_verify(dnssec_sign_ctx_t *ctx, bool sign_cmp, const dnssec_binary_t *signature) +{ + if (!ctx || !signature) { + return DNSSEC_EINVAL; + } + + if (sign_cmp && dnssec_key_can_sign(ctx->key)) { + dnssec_binary_t sign = { 0 }; + int ret = dnssec_sign_write(ctx, DNSSEC_SIGN_REPRODUCIBLE, &sign); + if (ret == KNOT_EOK) { + ret = dnssec_binary_cmp(&sign, signature) + ? DNSSEC_INVALID_SIGNATURE + : DNSSEC_EOK; + } + dnssec_binary_free(&sign); + return ret; + } + + if (!dnssec_key_can_verify(ctx->key)) { + return DNSSEC_NO_PUBLIC_KEY; + } + + gnutls_datum_t data = { + .data = vpool_get_buf(&ctx->buffer), + .size = vpool_get_length(&ctx->buffer) + }; + + _cleanup_binary_ dnssec_binary_t bin_raw = { 0 }; + int result = ctx->functions->dnssec_to_x509(ctx, signature, &bin_raw); + if (result != DNSSEC_EOK) { + return result; + } + + gnutls_datum_t raw = binary_to_datum(&bin_raw); + + assert(ctx->key->public_key); + result = gnutls_pubkey_verify_data2(ctx->key->public_key, + ctx->sign_algorithm, + 0, &data, &raw); + if (result == GNUTLS_E_PK_SIG_VERIFY_FAILED) { + return DNSSEC_INVALID_SIGNATURE; + } else if (result < 0) { + return DNSSEC_ERROR; + } + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/tsig.c b/src/libdnssec/tsig.c new file mode 100644 index 0000000..b7bd980 --- /dev/null +++ b/src/libdnssec/tsig.c @@ -0,0 +1,242 @@ +/* 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 <assert.h> +#include <gnutls/gnutls.h> +#include <gnutls/crypto.h> +#include <limits.h> +#include <stdint.h> +#include <stdlib.h> +#include <string.h> + +#include "libdnssec/shared/dname.h" +#include "libdnssec/error.h" +#include "libdnssec/shared/shared.h" +#include "libdnssec/tsig.h" + +struct dnssec_tsig_ctx { + gnutls_mac_algorithm_t algorithm; + gnutls_hmac_hd_t hash; +}; + +/*! + * TSIG algorithm identifiers. + */ +typedef struct { + dnssec_tsig_algorithm_t id; + gnutls_mac_algorithm_t gnutls_id; + const char *name; + const char *dname; +} algorithm_id_t; + +/*! + * DNAME to algorithm conversion table. + */ +static const algorithm_id_t ALGORITHM_ID_TABLE[] = { + // RFC 4635 + { DNSSEC_TSIG_HMAC_SHA1, GNUTLS_MAC_SHA1, "hmac-sha1", "\x9hmac-sha1" }, + { DNSSEC_TSIG_HMAC_SHA224, GNUTLS_MAC_SHA224, "hmac-sha224", "\xbhmac-sha224" }, + { DNSSEC_TSIG_HMAC_SHA256, GNUTLS_MAC_SHA256, "hmac-sha256", "\xbhmac-sha256" }, + { DNSSEC_TSIG_HMAC_SHA384, GNUTLS_MAC_SHA384, "hmac-sha384", "\xbhmac-sha384" }, + { DNSSEC_TSIG_HMAC_SHA512, GNUTLS_MAC_SHA512, "hmac-sha512", "\xbhmac-sha512" }, + // RFC 2845 + { DNSSEC_TSIG_HMAC_MD5, GNUTLS_MAC_MD5, "hmac-md5", "\x8hmac-md5\x7sig-alg\x3reg\x3int" }, + { 0 } +}; + +/*! + * Algorithm match callback prototype. + */ +typedef bool (*algorithm_match_cb)(const algorithm_id_t *m, const void *data); + +/*! + * Lookup an algorithm in the algorithm table. + */ +static const algorithm_id_t *lookup_algorithm(algorithm_match_cb match, + const void *data) +{ + assert(match); + + for (const algorithm_id_t *a = ALGORITHM_ID_TABLE; a->id; a++) { + if (match(a, data)) { + return a; + } + } + + return NULL; +} + +static bool match_dname(const algorithm_id_t *algorithm, const void *data) +{ + const uint8_t *search = data; + return dname_equal(search, (uint8_t *)algorithm->dname); +} + +static bool match_name(const algorithm_id_t *algorithm, const void *data) +{ + const char *search = data; + return strcasecmp(search, algorithm->name) == 0; +} + +static bool match_id(const algorithm_id_t *algorithm, const void *data) +{ + dnssec_tsig_algorithm_t search = *((dnssec_tsig_algorithm_t *)data); + return algorithm->id == search; +} + +/*! + * Convert TSIG algorithm identifier to GnuTLS identifier. + */ +static gnutls_mac_algorithm_t algorithm_to_gnutls(dnssec_tsig_algorithm_t algorithm) +{ + const algorithm_id_t *found = lookup_algorithm(match_id, &algorithm); + return (found ? found->gnutls_id : GNUTLS_MAC_UNKNOWN); +} + +/* -- public API ----------------------------------------------------------- */ + +_public_ +dnssec_tsig_algorithm_t dnssec_tsig_algorithm_from_dname(const uint8_t *dname) +{ + if (!dname) { + return DNSSEC_TSIG_UNKNOWN; + } + + const algorithm_id_t *found = lookup_algorithm(match_dname, dname); + return (found ? found->id : DNSSEC_TSIG_UNKNOWN); +} + +_public_ +const uint8_t *dnssec_tsig_algorithm_to_dname(dnssec_tsig_algorithm_t algorithm) +{ + const algorithm_id_t *found = lookup_algorithm(match_id, &algorithm); + return (found ? (uint8_t *)found->dname : NULL); +} + +_public_ +dnssec_tsig_algorithm_t dnssec_tsig_algorithm_from_name(const char *name) +{ + if (!name) { + return DNSSEC_TSIG_UNKNOWN; + } + + const algorithm_id_t *found = lookup_algorithm(match_name, name); + return (found ? found->id : DNSSEC_TSIG_UNKNOWN); +} + +_public_ +const char *dnssec_tsig_algorithm_to_name(dnssec_tsig_algorithm_t algorithm) +{ + const algorithm_id_t *found = lookup_algorithm(match_id, &algorithm); + return (found ? found->name : NULL); +} + +_public_ +int dnssec_tsig_optimal_key_size(dnssec_tsig_algorithm_t tsig) +{ + gnutls_mac_algorithm_t mac = algorithm_to_gnutls(tsig); + if (mac == GNUTLS_MAC_UNKNOWN) { + return 0; + } + + return gnutls_mac_get_key_size(mac) * CHAR_BIT; +} + +_public_ +int dnssec_tsig_new(dnssec_tsig_ctx_t **ctx_ptr, + dnssec_tsig_algorithm_t algorithm, + const dnssec_binary_t *key) +{ + if (!ctx_ptr || !key) { + return DNSSEC_EINVAL; + } + + dnssec_tsig_ctx_t *ctx = calloc(1, sizeof(*ctx)); + if (!ctx) { + return DNSSEC_ENOMEM; + } + + ctx->algorithm = algorithm_to_gnutls(algorithm); + if (ctx->algorithm == GNUTLS_MAC_UNKNOWN) { + free(ctx); + return DNSSEC_INVALID_KEY_ALGORITHM; + } + + int result = gnutls_hmac_init(&ctx->hash, ctx->algorithm, key->data, key->size); + if (result != 0) { + free(ctx); + return DNSSEC_SIGN_INIT_ERROR; + } + + *ctx_ptr = ctx; + + return DNSSEC_EOK; +} + +_public_ +void dnssec_tsig_free(dnssec_tsig_ctx_t *ctx) +{ + if (!ctx) { + return; + } + + gnutls_hmac_deinit(ctx->hash, NULL); + free(ctx); +} + +_public_ +int dnssec_tsig_add(dnssec_tsig_ctx_t *ctx, const dnssec_binary_t *data) +{ + if (!ctx || !data) { + return DNSSEC_EINVAL; + } + + int result = gnutls_hmac(ctx->hash, data->data, data->size); + if (result != 0) { + return DNSSEC_SIGN_ERROR; + } + + return DNSSEC_EOK; +} + +_public_ +size_t dnssec_tsig_size(dnssec_tsig_ctx_t *ctx) +{ + if (!ctx) { + return 0; + } + + return gnutls_hmac_get_len(ctx->algorithm); +} + +_public_ +size_t dnssec_tsig_algorithm_size(dnssec_tsig_algorithm_t algorithm) +{ + int gnutls_algorithm = algorithm_to_gnutls(algorithm); + return gnutls_hmac_get_len(gnutls_algorithm); +} + +_public_ +int dnssec_tsig_write(dnssec_tsig_ctx_t *ctx, uint8_t *mac) +{ + if (!ctx || !mac) { + return DNSSEC_EINVAL; + } + + gnutls_hmac_output(ctx->hash, mac); + + return DNSSEC_EOK; +} diff --git a/src/libdnssec/tsig.h b/src/libdnssec/tsig.h new file mode 100644 index 0000000..31f15c5 --- /dev/null +++ b/src/libdnssec/tsig.h @@ -0,0 +1,155 @@ +/* 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 tsig + * + * \brief Low-level TSIG signing API. + * + * @{ + */ + +#pragma once + +#include <stdint.h> + +#include <libdnssec/binary.h> + +/*! + * TSIG algorithms. + * + * \note The numeric values are library specific. + */ +typedef enum dnssec_tsig_algorithm { + DNSSEC_TSIG_UNKNOWN = 0, + DNSSEC_TSIG_HMAC_MD5, + DNSSEC_TSIG_HMAC_SHA1, + DNSSEC_TSIG_HMAC_SHA224, + DNSSEC_TSIG_HMAC_SHA256, + DNSSEC_TSIG_HMAC_SHA384, + DNSSEC_TSIG_HMAC_SHA512 +} dnssec_tsig_algorithm_t; + +/*! + * Get TSIG algorithm number from domain name. + * + * \see https://www.iana.org/assignments/tsig-algorithm-names/tsig-algorithm-names.xhtml + * + * \param dname Domain name of the algorithm (e.g., 0x0b hmac-sha256). + * + * \return TSIG algorithm. + */ +dnssec_tsig_algorithm_t dnssec_tsig_algorithm_from_dname(const uint8_t *dname); + +/*! + * Get a domain name of the TSIG algorithm. + * + * \param algorithm TSIG algorithm. + * + * \return Domain name of the TSIG algorithm. + */ +const uint8_t *dnssec_tsig_algorithm_to_dname(dnssec_tsig_algorithm_t algorithm); + +/*! + * Get TSIG algorithm from a MAC name. + * + * \param name MAC name (e.g., hmac-sha256). + * + * \return TSIG algorithm. + */ +dnssec_tsig_algorithm_t dnssec_tsig_algorithm_from_name(const char *name); + +/*! + * Get MAC name from a TSIG algorithm. + * + * \param algorithm TSIG algorithm. + * + * \return MAC name of the TSIG algorithm. + */ +const char *dnssec_tsig_algorithm_to_name(dnssec_tsig_algorithm_t algorithm); + +/*! + * Get optimal size of a TSIG algorithm. + */ +int dnssec_tsig_optimal_key_size(dnssec_tsig_algorithm_t algorithm); + +struct dnssec_tsig_ctx; + +/*! + * TSIG signing context. + */ +typedef struct dnssec_tsig_ctx dnssec_tsig_ctx_t; + +/*! + * Create new TSIG signing context. + * + * \param[out] ctx Resulting TSIG context. + * \param[in] algorithm TSIG algorithm. + * \param[in] key Shared key to be used for signing. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_tsig_new(dnssec_tsig_ctx_t **ctx, dnssec_tsig_algorithm_t algorithm, + const dnssec_binary_t *key); + +/*! + * Free the TSIG signing context. + * + * \param ctx TSIG signing context to be freed. + */ +void dnssec_tsig_free(dnssec_tsig_ctx_t *ctx); + +/*! + * Add data to be signed by TSIG. + * + * \param ctx TSIG signing context. + * \param data Data to be signed. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_tsig_add(dnssec_tsig_ctx_t *ctx, const dnssec_binary_t *data); + +/*! + * Get size of the TSIG signature for given signing context. + * + * \param ctx TSIG signing context. + * + * \return The size of the TSIG signature. + */ +size_t dnssec_tsig_size(dnssec_tsig_ctx_t *ctx); + +/*! + * Get size of the TSIG signature for given algorithm. + * + * \param algorithm TSIG algorithm. + * + * \return The size of the TSIG signature. + */ +size_t dnssec_tsig_algorithm_size(dnssec_tsig_algorithm_t algorithm); + +/*! + * Write TSIG signature. + * + * \param[in] ctx TSIG signing context. + * \param[out] mac Resulting TSIG signature. + * + * \return Error code, DNSSEC_EOK if successful. + */ +int dnssec_tsig_write(dnssec_tsig_ctx_t *ctx, uint8_t *mac); + +/*! @} */ diff --git a/src/libdnssec/version.h b/src/libdnssec/version.h new file mode 100644 index 0000000..e1948bc --- /dev/null +++ b/src/libdnssec/version.h @@ -0,0 +1,25 @@ +/* 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 + +#define DNSSEC_VERSION_MAJOR 3 +#define DNSSEC_VERSION_MINOR 3 +#define DNSSEC_VERSION_PATCH 0x04 + +#define DNSSEC_VERSION_HEX ((DNSSEC_VERSION_MAJOR << 16) | \ + (DNSSEC_VERSION_MINOR << 8) | \ + (DNSSEC_VERSION_PATCH)) diff --git a/src/libdnssec/version.h.in b/src/libdnssec/version.h.in new file mode 100644 index 0000000..0ac1c4e --- /dev/null +++ b/src/libdnssec/version.h.in @@ -0,0 +1,25 @@ +/* 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 + +#define DNSSEC_VERSION_MAJOR @KNOT_VERSION_MAJOR@ +#define DNSSEC_VERSION_MINOR @KNOT_VERSION_MINOR@ +#define DNSSEC_VERSION_PATCH 0x0@KNOT_VERSION_PATCH@ + +#define DNSSEC_VERSION_HEX ((DNSSEC_VERSION_MAJOR << 16) | \ + (DNSSEC_VERSION_MINOR << 8) | \ + (DNSSEC_VERSION_PATCH)) |