summaryrefslogtreecommitdiffstats
path: root/src/libdnssec
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/libdnssec.pc.in13
-rw-r--r--src/libdnssec/Makefile.inc70
-rw-r--r--src/libdnssec/binary.c163
-rw-r--r--src/libdnssec/binary.h116
-rw-r--r--src/libdnssec/crypto.c42
-rw-r--r--src/libdnssec/crypto.h52
-rw-r--r--src/libdnssec/digest.c105
-rw-r--r--src/libdnssec/digest.h76
-rw-r--r--src/libdnssec/dnssec.h36
-rw-r--r--src/libdnssec/error.c87
-rw-r--r--src/libdnssec/error.h106
-rw-r--r--src/libdnssec/key.h309
-rw-r--r--src/libdnssec/key/algorithm.c180
-rw-r--r--src/libdnssec/key/algorithm.h30
-rw-r--r--src/libdnssec/key/convert.c375
-rw-r--r--src/libdnssec/key/convert.h44
-rw-r--r--src/libdnssec/key/dnskey.c91
-rw-r--r--src/libdnssec/key/dnskey.h46
-rw-r--r--src/libdnssec/key/ds.c128
-rw-r--r--src/libdnssec/key/internal.h35
-rw-r--r--src/libdnssec/key/key.c461
-rw-r--r--src/libdnssec/key/keytag.c88
-rw-r--r--src/libdnssec/key/privkey.c140
-rw-r--r--src/libdnssec/key/privkey.h35
-rw-r--r--src/libdnssec/key/simple.c55
-rw-r--r--src/libdnssec/keyid.c87
-rw-r--r--src/libdnssec/keyid.h64
-rw-r--r--src/libdnssec/keystore.h155
-rw-r--r--src/libdnssec/keystore/internal.h50
-rw-r--r--src/libdnssec/keystore/keystore.c186
-rw-r--r--src/libdnssec/keystore/pkcs11.c397
-rw-r--r--src/libdnssec/keystore/pkcs8.c497
-rw-r--r--src/libdnssec/keytag.h44
-rw-r--r--src/libdnssec/nsec.h155
-rw-r--r--src/libdnssec/nsec/bitmap.c142
-rw-r--r--src/libdnssec/nsec/hash.c125
-rw-r--r--src/libdnssec/nsec/nsec.c130
-rw-r--r--src/libdnssec/p11/p11.c113
-rw-r--r--src/libdnssec/p11/p11.h41
-rw-r--r--src/libdnssec/pem.c182
-rw-r--r--src/libdnssec/pem.h73
-rw-r--r--src/libdnssec/random.c53
-rw-r--r--src/libdnssec/random.h79
-rw-r--r--src/libdnssec/shared/bignum.c64
-rw-r--r--src/libdnssec/shared/bignum.h41
-rw-r--r--src/libdnssec/shared/binary_wire.h53
-rw-r--r--src/libdnssec/shared/dname.c165
-rw-r--r--src/libdnssec/shared/dname.h57
-rw-r--r--src/libdnssec/shared/keyid_gnutls.c99
-rw-r--r--src/libdnssec/shared/keyid_gnutls.h30
-rw-r--r--src/libdnssec/shared/shared.h121
-rw-r--r--src/libdnssec/sign.h114
-rw-r--r--src/libdnssec/sign/der.c229
-rw-r--r--src/libdnssec/sign/der.h56
-rw-r--r--src/libdnssec/sign/sign.c433
-rw-r--r--src/libdnssec/tsig.c242
-rw-r--r--src/libdnssec/tsig.h155
-rw-r--r--src/libdnssec/version.h25
-rw-r--r--src/libdnssec/version.h.in25
59 files changed, 7365 insertions, 0 deletions
diff --git a/src/libdnssec.pc.in b/src/libdnssec.pc.in
new file mode 100644
index 0000000..6c0aa5c
--- /dev/null
+++ b/src/libdnssec.pc.in
@@ -0,0 +1,13 @@
+prefix=@prefix@
+exec_prefix=@prefix@
+libdir=@libdir@
+includedir=@includedir@
+soname=@libdnssec_SONAME@
+
+Name: libdnssec
+Description: Knot DNS DNSSEC library
+URL: https://www.knot-dns.cz
+Version: @PACKAGE_VERSION@
+Requires.private: gnutls >= 3.3
+Libs: -L${libdir} -ldnssec
+Cflags: -I${includedir}
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,
+ &gt_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, &params->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(&params->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(&params1->salt, &params2->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))