summaryrefslogtreecommitdiffstats
path: root/src/libdnssec
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 00:53:35 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-05-06 00:53:35 +0000
commit69c6a41ffb878ef98c9378ed4b1634a404cfaa7f (patch)
treeb2a4f704565d62fbb129ab9dc3b35977c50e6e7f /src/libdnssec
parentInitial commit. (diff)
downloadknot-d02c1c4ad3b5dddb2ceca2c451a5b417770810ef.tar.xz
knot-d02c1c4ad3b5dddb2ceca2c451a5b417770810ef.zip
Adding upstream version 2.7.6.upstream/2.7.6upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--src/libdnssec.pc.in13
-rw-r--r--src/libdnssec/Makefile.inc87
-rw-r--r--src/libdnssec/binary.c169
-rw-r--r--src/libdnssec/binary.h133
-rw-r--r--src/libdnssec/contrib/vpool.c241
-rw-r--r--src/libdnssec/contrib/vpool.h60
-rw-r--r--src/libdnssec/crypto.c42
-rw-r--r--src/libdnssec/crypto.h74
-rw-r--r--src/libdnssec/dnssec.h35
-rw-r--r--src/libdnssec/error.c84
-rw-r--r--src/libdnssec/error.h112
-rw-r--r--src/libdnssec/key.h318
-rw-r--r--src/libdnssec/key/algorithm.c163
-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.c82
-rw-r--r--src/libdnssec/key/dnskey.h46
-rw-r--r--src/libdnssec/key/ds.c115
-rw-r--r--src/libdnssec/key/internal.h35
-rw-r--r--src/libdnssec/key/key.c440
-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.h80
-rw-r--r--src/libdnssec/keystore.h297
-rw-r--r--src/libdnssec/keystore/internal.h52
-rw-r--r--src/libdnssec/keystore/keystore.c183
-rw-r--r--src/libdnssec/keystore/pkcs11.c440
-rw-r--r--src/libdnssec/keystore/pkcs8.c211
-rw-r--r--src/libdnssec/keystore/pkcs8_dir.c379
-rw-r--r--src/libdnssec/keytag.h63
-rw-r--r--src/libdnssec/list.h85
-rw-r--r--src/libdnssec/list/list.c298
-rw-r--r--src/libdnssec/list/ucw_clists.h260
-rw-r--r--src/libdnssec/nsec.h214
-rw-r--r--src/libdnssec/nsec/bitmap.c142
-rw-r--r--src/libdnssec/nsec/hash.c125
-rw-r--r--src/libdnssec/nsec/nsec.c116
-rw-r--r--src/libdnssec/p11/p11.c113
-rw-r--r--src/libdnssec/p11/p11.h41
-rw-r--r--src/libdnssec/random.c53
-rw-r--r--src/libdnssec/random.h85
-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/fs.c47
-rw-r--r--src/libdnssec/shared/fs.h25
-rw-r--r--src/libdnssec/shared/hex.c167
-rw-r--r--src/libdnssec/shared/hex.h39
-rw-r--r--src/libdnssec/shared/keyid_gnutls.c94
-rw-r--r--src/libdnssec/shared/keyid_gnutls.h30
-rw-r--r--src/libdnssec/shared/pem.c198
-rw-r--r--src/libdnssec/shared/pem.h74
-rw-r--r--src/libdnssec/shared/shared.h129
-rw-r--r--src/libdnssec/sign.h137
-rw-r--r--src/libdnssec/sign/der.c229
-rw-r--r--src/libdnssec/sign/der.h56
-rw-r--r--src/libdnssec/sign/sign.c408
-rw-r--r--src/libdnssec/tsig.c242
-rw-r--r--src/libdnssec/tsig.h207
-rw-r--r--src/libdnssec/version.h25
-rw-r--r--src/libdnssec/version.h.in25
67 files changed, 8852 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..112f95b
--- /dev/null
+++ b/src/libdnssec/Makefile.inc
@@ -0,0 +1,87 @@
+lib_LTLIBRARIES += libdnssec.la
+pkgconfig_DATA += libdnssec.pc
+
+noinst_LTLIBRARIES += libshared.la
+
+libshared_la_CPPFLAGS = $(AM_CPPFLAGS) $(gnutls_CFLAGS)
+
+libshared_la_SOURCES = \
+ libdnssec/shared/bignum.c \
+ libdnssec/shared/bignum.h \
+ libdnssec/shared/binary_wire.h \
+ libdnssec/shared/dname.c \
+ libdnssec/shared/dname.h \
+ libdnssec/shared/fs.c \
+ libdnssec/shared/fs.h \
+ libdnssec/shared/hex.c \
+ libdnssec/shared/hex.h \
+ libdnssec/shared/keyid_gnutls.c \
+ libdnssec/shared/keyid_gnutls.h \
+ libdnssec/shared/pem.c \
+ libdnssec/shared/pem.h \
+ libdnssec/shared/shared.h
+
+libdnssec_la_CPPFLAGS = $(AM_CPPFLAGS) $(CFLAG_VISIBILITY) $(gnutls_CFLAGS)
+libdnssec_la_LDFLAGS = $(AM_LDFLAGS) $(libdnssec_VERSION_INFO) $(gnutls_LIBS)
+if EXCLUDE_LIBS_LIBDNSSEC
+libdnssec_la_LDFLAGS += $(LDFLAG_EXCLUDE_LIBS)
+endif
+libdnssec_la_LIBADD = libshared.la
+if ENABLE_PKCS11
+libdnssec_la_LIBADD += $(pthread_LIBS)
+endif
+
+include_libdnssecdir = $(includedir)/libdnssec
+include_libdnssec_HEADERS = \
+ libdnssec/binary.h \
+ libdnssec/crypto.h \
+ libdnssec/dnssec.h \
+ libdnssec/error.h \
+ libdnssec/key.h \
+ libdnssec/keyid.h \
+ libdnssec/keystore.h \
+ libdnssec/keytag.h \
+ libdnssec/list.h \
+ libdnssec/nsec.h \
+ libdnssec/random.h \
+ libdnssec/sign.h \
+ libdnssec/tsig.h \
+ libdnssec/version.h
+
+libdnssec_la_SOURCES = \
+ libdnssec/contrib/vpool.c \
+ libdnssec/contrib/vpool.h \
+ libdnssec/binary.c \
+ libdnssec/crypto.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/keystore/pkcs8_dir.c \
+ libdnssec/list/list.c \
+ libdnssec/list/ucw_clists.h \
+ libdnssec/nsec/bitmap.c \
+ libdnssec/nsec/hash.c \
+ libdnssec/nsec/nsec.c \
+ libdnssec/p11/p11.c \
+ libdnssec/p11/p11.h \
+ libdnssec/random.c \
+ 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..a57b421
--- /dev/null
+++ b/src/libdnssec/binary.c
@@ -0,0 +1,169 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include <string.h>
+
+#include "libdnssec/binary.h"
+#include "libdnssec/error.h"
+#include "libdnssec/shared/shared.h"
+
+// Workaround for symbol redefinition if linked statically.
+#define base64_encode base64encode
+#define base64_decode base64decode
+#define base64_encode_alloc base64encodealloc
+#define base64_decode_alloc base64decodealloc
+#include "contrib/base64.c"
+
+/* -- 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 = 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 = 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..fb0cf9b
--- /dev/null
+++ b/src/libdnssec/binary.h
@@ -0,0 +1,133 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \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.
+ *
+ * Example of use:
+ * ~~~~~ {.c}
+ *
+ * dnssec_binary_t data = { 0 };
+ *
+ * int result = dnssec_binary_alloc(&data, 32);
+ * if (result != DNSSEC_EOK) {
+ * return result;
+ * }
+ *
+ * memcpy(&data.data, buffer, data.size);
+ *
+ * // ...
+ *
+ * dnssec_binary_free(&data);
+ *
+ * ~~~~~
+ *
+ * @{
+ */
+
+#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/contrib/vpool.c b/src/libdnssec/contrib/vpool.c
new file mode 100644
index 0000000..d2e1006
--- /dev/null
+++ b/src/libdnssec/contrib/vpool.c
@@ -0,0 +1,241 @@
+/*
+ * Copyright (c) 2006, 2008 Alexey Vatchenko <av@bsdua.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+#include <sys/types.h>
+
+#include <errno.h>
+#include <limits.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libdnssec/contrib/vpool.h"
+
+static void vpool_shift(struct vpool *pool);
+static int vpool_new_size(struct vpool *pool, size_t datsize,
+ size_t *size);
+static int vpool_resize(struct vpool *pool, size_t datsize);
+
+static void
+vpool_shift(struct vpool *pool)
+{
+ if (pool->v_buf != pool->v_basebuf) {
+ memmove(pool->v_basebuf, pool->v_buf, pool->v_off);
+ pool->v_buf = pool->v_basebuf;
+ }
+}
+
+static int
+vpool_new_size(struct vpool *pool, size_t datsize, size_t *size)
+{
+ size_t need;
+ size_t rem;
+
+ if (datsize <= pool->v_size - pool->v_off) {
+ *size = pool->v_size;
+ return (0);
+ }
+
+ /* Check limit of new requested size */
+ if (pool->v_limit - pool->v_off < datsize) {
+ return (EFBIG);
+ }
+ need = pool->v_off + datsize;
+
+ /* Check limit of new size aligned to block size */
+ rem = need % pool->v_blksize;
+ if (rem != 0) {
+ if (pool->v_limit - pool->v_off >=
+ datsize + (pool->v_blksize - rem)) {
+ need += pool->v_blksize - rem;
+ } else {
+ need = pool->v_limit;
+ }
+ }
+
+ *size = need;
+ return (0);
+}
+
+static int
+vpool_resize(struct vpool *pool, size_t datsize)
+{
+ void *ret;
+ size_t size;
+ int error;
+
+ error = vpool_new_size(pool, datsize, &size);
+ if (error != 0) {
+ return (error);
+ }
+
+ if (size > pool->v_size) {
+ ret = malloc(size);
+ if (ret == NULL) {
+ return (ENOMEM);
+ }
+
+ memcpy(ret, pool->v_buf, pool->v_off);
+ free(pool->v_basebuf);
+ pool->v_basebuf = pool->v_buf = ret;
+ pool->v_size = size;
+ } else if ((pool->v_size - pool->v_off) -
+ (pool->v_buf - pool->v_basebuf) < datsize) {
+ vpool_shift(pool);
+ }
+
+ return (0);
+}
+
+void
+vpool_init(struct vpool *pool, size_t blksize, size_t limit)
+{
+
+ pool->v_basebuf = pool->v_buf = NULL;
+ pool->v_off = pool->v_size = 0;
+
+ pool->v_blksize = (blksize == 0) ? 4096 : blksize; /* XXX */
+ pool->v_limit = (limit == 0) ? SIZE_MAX : limit;
+
+ pool->v_lasterr = 0;
+}
+
+void
+vpool_final(struct vpool *pool)
+{
+ free(pool->v_basebuf);
+}
+
+void
+vpool_reset(struct vpool *pool)
+{
+ free(pool->v_basebuf);
+ pool->v_basebuf = pool->v_buf = NULL;
+ pool->v_off = pool->v_size = 0;
+ pool->v_lasterr = 0;
+}
+
+void
+vpool_wipe(struct vpool *pool)
+{
+ pool->v_off = 0;
+ pool->v_lasterr = 0;
+}
+
+void *
+vpool_insert(struct vpool *pool, size_t where, void *data, size_t datsize)
+{
+ void *ret;
+ int error;
+
+ error = vpool_resize(pool, datsize);
+ if (error != 0) {
+ pool->v_lasterr = error;
+ return (NULL);
+ }
+
+ /*
+ * If ``where'' is greater than or equal to offset then
+ * we are appending data to the end of the buffer.
+ */
+ if (where > pool->v_off) {
+ where = pool->v_off;
+ }
+
+ ret = (uint8_t *)pool->v_buf + where;
+ if (pool->v_off - where > 0) {
+ memmove(ret + datsize, ret, pool->v_off - where);
+ }
+ memcpy(ret, data, datsize);
+ pool->v_off += datsize;
+ pool->v_lasterr = 0;
+
+ return (ret);
+}
+
+void *
+vpool_expand(struct vpool *pool, size_t where, size_t size)
+{
+ void *ret;
+ int error;
+
+ error = vpool_resize(pool, size);
+ if (error != 0) {
+ pool->v_lasterr = error;
+ return (NULL);
+ }
+
+ /*
+ * If ``where'' is greater than or equal to offset then
+ * we are appending data to the end of the buffer.
+ */
+ if (where > pool->v_off) {
+ where = pool->v_off;
+ }
+
+ ret = (uint8_t *)pool->v_buf + where;
+ if (pool->v_off - where > 0) {
+ memmove(ret + size, ret, pool->v_off - where);
+ }
+ pool->v_off += size;
+ pool->v_lasterr = 0;
+
+ return (ret);
+}
+
+int
+vpool_truncate(struct vpool *pool,
+ size_t where, size_t size, enum vpool_trunc how)
+{
+ /* Check if caller wants to remove more data than we have */
+ if (where >= pool->v_off ||
+ size > pool->v_off || pool->v_off - size < where) {
+ pool->v_lasterr = ERANGE;
+ return (pool->v_lasterr);
+ }
+
+ if (how == VPOOL_EXCLUDE) {
+ if (where == 0) {
+ /*
+ * Optimization.
+ * Don't move data, just adjust pointer.
+ */
+ pool->v_buf = (uint8_t *)pool->v_buf + size;
+ } else {
+ memmove((uint8_t *)pool->v_buf + where,
+ (uint8_t *)pool->v_buf + where + size,
+ pool->v_off - size - where);
+ }
+ pool->v_off -= size;
+ } else {
+ pool->v_buf = (uint8_t *)pool->v_buf + where;
+ pool->v_off = size;
+ }
+
+ pool->v_lasterr = 0;
+ return (0);
+}
+
+void
+vpool_export(struct vpool *pool, void **buf, size_t *size)
+{
+ vpool_shift(pool);
+ *buf = pool->v_buf;
+ *size = pool->v_off;
+ pool->v_basebuf = pool->v_buf = NULL;
+ pool->v_off = pool->v_size = 0;
+ pool->v_lasterr = 0;
+}
diff --git a/src/libdnssec/contrib/vpool.h b/src/libdnssec/contrib/vpool.h
new file mode 100644
index 0000000..82e3d66
--- /dev/null
+++ b/src/libdnssec/contrib/vpool.h
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2006, 2008 Alexey Vatchenko <av@bsdua.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted, provided that the above
+ * copyright notice and this permission notice appear in all copies.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
+ * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
+ * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
+ * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
+ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
+ * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
+ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
+ */
+
+/*
+ * VPool: implementation of pool of data with a variable size.
+ */
+#ifndef _VPOOL_H_
+#define _VPOOL_H_
+
+#include <stddef.h>
+#include <limits.h>
+
+struct vpool {
+ void *v_basebuf; /* pointer returned by (re|m)alloc() */
+ void *v_buf; /* actual data starts here */
+ size_t v_off;
+ size_t v_size;
+
+ size_t v_blksize;
+ size_t v_limit;
+ int v_lasterr;
+};
+
+enum vpool_trunc {VPOOL_EXCLUDE, VPOOL_INCLUDE};
+#define VPOOL_TAIL UINT_MAX
+
+void vpool_init(struct vpool *pool, size_t blksize, size_t limit);
+void vpool_final(struct vpool *pool);
+
+void vpool_reset(struct vpool *pool);
+void vpool_wipe(struct vpool *pool);
+
+void * vpool_insert(struct vpool *pool,
+ size_t where, void *data, size_t datsize);
+void * vpool_expand(struct vpool *pool, size_t where, size_t size);
+
+int vpool_truncate(struct vpool *pool,
+ size_t where, size_t size, enum vpool_trunc how);
+
+#define vpool_is_empty(pool) ((pool)->v_off == 0)
+#define vpool_get_buf(pool) ((pool)->v_buf)
+#define vpool_get_length(pool) ((pool)->v_off)
+#define vpool_get_error(pool) ((pool)->v_lasterr)
+
+void vpool_export(struct vpool *pool, void **buf, size_t *size);
+
+#endif /* !_VPOOL_H_ */
diff --git a/src/libdnssec/crypto.c b/src/libdnssec/crypto.c
new file mode 100644
index 0000000..f54e20f
--- /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 <http://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..46541b4
--- /dev/null
+++ b/src/libdnssec/crypto.h
@@ -0,0 +1,74 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \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().
+ *
+ * ~~~~~ {.c}
+ * int main(void)
+ * {
+ * int exit_code = 0;
+ *
+ * dnssec_crypto_init();
+ *
+ * pid_t child_pid = fork();
+ * if (child_pid < 0) {
+ * perror("fork");
+ * exit_code = 1;
+ * } else if (child_pid == 0) {
+ * dnssec_crypto_reinit();
+ * exit_code = child();
+ * } else {
+ * exit_code = parent();
+ * }
+ *
+ * dnssec_crypto_cleanup();
+ * return exit_code;
+ * }
+ * ~~~~~
+ *
+ * @{
+ */
+
+#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/dnssec.h b/src/libdnssec/dnssec.h
new file mode 100644
index 0000000..8383901
--- /dev/null
+++ b/src/libdnssec/dnssec.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 <http://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/list.h>
+#include <libdnssec/nsec.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..97f7f8f
--- /dev/null
+++ b/src/libdnssec/error.c
@@ -0,0 +1,84 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <string.h>
+
+#include "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" },
+
+ { 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..eaa6afd
--- /dev/null
+++ b/src/libdnssec/error.h
@@ -0,0 +1,112 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \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.
+ *
+ * ~~~~~ {.c}
+ * int result;
+ *
+ * result = dnssec_key_set_pubkey(NULL, NULL);
+ * assert(result == DNSSEC_EINVAL);
+ *
+ * fprintf(stderr, "Error: %s.\n", dnssec_strerror(result));
+ * // Error: Invalid argument.
+ * ~~~~~
+ *
+ * @{
+ */
+
+#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_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..27de778
--- /dev/null
+++ b/src/libdnssec/key.h
@@ -0,0 +1,318 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \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.
+ *
+ * The following example shows construction of a key from DNSKEY RDATA:
+ *
+ * ~~~~~ {.c}
+ *
+ * dnssec_binary_t rdata = // ...;
+ *
+ * int result;
+ * dnssec_key_t *key = NULL;
+ *
+ * // create new DNSSEC key
+ * result = dnssec_key_new(&key);
+ * if (result != DNSSEC_EOK) {
+ * return result;
+ * }
+ *
+ * // load the DNSKEY RDATA
+ * result = dnssec_key_set_rdata(key, &rdata);
+ * if (result != DNSSEC_EOK) {
+ * dnssec_key_free(key);
+ * return result;
+ * }
+ *
+ * // print key tag
+ * printf("key %s\n", dnssec_key_get_keytag(key));
+ *
+ * // make sure what we can do with the key
+ * assert(dnssec_key_can_verify(key) == true);
+ * assert(dnssec_key_can_sign(key) == false);
+ *
+ * // ...
+ *
+ * // cleanup
+ * dnssec_key_free(key);
+ *
+ * ~~~~~
+ *
+ * @{
+ */
+
+#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_INVALID = 0,
+ DNSSEC_KEY_ALGORITHM_RSA_SHA1 = 5,
+ DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3 = 7,
+ DNSSEC_KEY_ALGORITHM_RSA_SHA256 = 8,
+ DNSSEC_KEY_ALGORITHM_RSA_SHA512 = 10,
+ 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_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.
+ */
+bool dnssec_algorithm_key_support(dnssec_key_algorithm_t algo);
+
+/*!
+ * Allocate new DNSSEC key.
+ *
+ * The protocol field of the key is set to 3 (DNSSEC).
+ * The flags field of the key is set to 256 (zone key, no SEP).
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int dnssec_key_new(dnssec_key_t **key);
+
+/*!
+ * Clear the DNSSEC key.
+ *
+ * Has the same effect as calling \ref dnssec_key_free and \ref dnssec_key_new.
+ */
+void dnssec_key_clear(dnssec_key_t *key);
+
+/*!
+ * Free the key allocated by \ref dnssec_key_new.
+ */
+void dnssec_key_free(dnssec_key_t *key);
+
+/*!
+ * Create a copy of a DNSSEC key.
+ *
+ * Only a public part of the key is copied.
+ */
+dnssec_key_t *dnssec_key_dup(const dnssec_key_t *key);
+
+/*!
+ * Get the key tag of the DNSSEC key.
+ */
+uint16_t dnssec_key_get_keytag(const dnssec_key_t *key);
+
+/*!
+ * Get the domain name of the DNSSEC key.
+ */
+const uint8_t *dnssec_key_get_dname(const dnssec_key_t *key);
+
+/*!
+ * Set the domain name of the DNSSEC key.
+ */
+int dnssec_key_set_dname(dnssec_key_t *key, const uint8_t *dname);
+
+/*!
+ * Get the flags field of the DNSSEC key.
+ */
+uint16_t dnssec_key_get_flags(const dnssec_key_t *key);
+
+/*!
+ * Set the flags field of the DNSSEC key.
+ */
+int dnssec_key_set_flags(dnssec_key_t *key, uint16_t flags);
+
+/*!
+ * Get the protocol field of the DNSSEC key.
+ */
+uint8_t dnssec_key_get_protocol(const dnssec_key_t *key);
+
+/*!
+ * Get the protocol field of the DNSSEC key.
+ */
+int dnssec_key_set_protocol(dnssec_key_t *key, uint8_t protocol);
+
+/*!
+ * Get the algorithm field of the DNSSEC key.
+ */
+uint8_t dnssec_key_get_algorithm(const dnssec_key_t *key);
+
+/*!
+ * Set the algorithm field of the DNSSEC key.
+ *
+ * The function will fail if the algorithm is incompatible with the
+ * loaded key. This means, that the function can be used to set the initial
+ * algorithm and later, only the hashing algorithm can be changed.
+ */
+int dnssec_key_set_algorithm(dnssec_key_t *key, uint8_t algorithm);
+
+/*!
+ * Get the public key field of the DNSSEC key.
+ *
+ * The returned content must not be modified by the caller. A reference
+ * to internally allocated structure is returned.
+ */
+int dnssec_key_get_pubkey(const dnssec_key_t *key, dnssec_binary_t *pubkey);
+
+/*!
+ * Set the public key field of the DNSSEC key.
+ *
+ * A valid algorithm has to be set prior to calling this function.
+ *
+ * The function will fail if the key is already loaded in the structure.
+ */
+int dnssec_key_set_pubkey(dnssec_key_t *key, const dnssec_binary_t *pubkey);
+
+/*!
+ * Get the bit size of the cryptographic key used with the DNSSEC key.
+ */
+unsigned dnssec_key_get_size(const dnssec_key_t *key);
+
+/*!
+ * \brief Compute key ID from public key.
+ *
+ * \param key Key structure holding the public key.
+ * \param id Output: key ID in hex.
+ *
+ * \return DNSSEC_E*
+ */
+int dnssec_key_get_keyid(const dnssec_key_t *key, char **id);
+
+/*!
+ * Get the RDATA of the DNSSEC key.
+ *
+ * The returned content must not be modified by the caller. A reference
+ * to internally allocated structure is returned.
+ */
+int dnssec_key_get_rdata(const dnssec_key_t *key, dnssec_binary_t *rdata);
+
+/*!
+ * Set the RDATA of the DNSSEC key.
+ *
+ * Calling this function has the same effect as setting the individual
+ * fields of the key step-by-step. The same limitations apply.
+ */
+int dnssec_key_set_rdata(dnssec_key_t *key, const dnssec_binary_t *rdata);
+
+/*!
+ * Load PKCS #8 private key in the unencrypted PEM format.
+ *
+ * At least an algorithm must be set prior to calling this function.
+ *
+ * The function will create public key, unless it was already set (using
+ * \ref dnssec_key_set_pubkey or \ref dnssec_key_set_rdata). If the public key
+ * was set, the function will prevent loading of non-matching private key.
+ */
+int dnssec_key_load_pkcs8(dnssec_key_t *key, const dnssec_binary_t *pem);
+
+/*!
+ * Check if the key can be used for signing.
+ */
+bool dnssec_key_can_sign(const dnssec_key_t *key);
+
+/*!
+ * Check if the key can be used for verification.
+ */
+bool dnssec_key_can_verify(const dnssec_key_t *key);
+
+/*!
+ * Get private key size range for a DNSSEC algorithm.
+ *
+ * \param[in] algorithm DNSKEY algorithm.
+ * \param[out] min Minimal size of the private key (can be NULL).
+ * \param[out] max Maximal size of the private key (can be NULL).
+ *
+ * \return DNSSEC_EOK for valid parameters.
+ */
+int dnssec_algorithm_key_size_range(dnssec_key_algorithm_t algorithm,
+ unsigned *min, unsigned *max);
+
+/*!
+ * Check if the private key size matches DNSKEY constraints.
+ *
+ * \param algorithm DNSKEY algorithm.
+ * \param bits Private key size.
+ *
+ * \return DNSKEY algorithm matches the key size constraints.
+ */
+bool dnssec_algorithm_key_size_check(dnssec_key_algorithm_t algorithm,
+ unsigned bits);
+
+/*!
+ * Get default key size for given algorithm.
+ *
+ * The default size is balance between security and response lengths with
+ * respect to use in DNS.
+ */
+int dnssec_algorithm_key_size_default(dnssec_key_algorithm_t algorithm);
+
+/*!
+ * 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;
+
+/*!
+ * Check whether a DS algorithm is supported.
+ *
+ * @note: less secure algorithms may go unsupported on purpose.
+ */
+bool dnssec_algorithm_digest_support(dnssec_key_digest_t algo);
+
+/*!
+ * Create DS (Delgation 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..84f5386
--- /dev/null
+++ b/src/libdnssec/key/algorithm.c
@@ -0,0 +1,163 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <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;
+ case DNSSEC_KEY_ALGORITHM_ED25519:
+#ifdef HAVE_ED25519
+ return GNUTLS_PK_EDDSA_ED25519;
+#endif
+ case DNSSEC_KEY_ALGORITHM_ED448:
+ default:
+ return GNUTLS_PK_UNKNOWN;
+ }
+}
+
+/* -- public API ----------------------------------------------------------- */
+
+_public_
+bool dnssec_algorithm_key_support(dnssec_key_algorithm_t algo)
+{
+ return algorithm_to_gnutls(algo) != GNUTLS_PK_UNKNOWN;
+}
+
+_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..586682c
--- /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 <http://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..b8ebb7d
--- /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 <http://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..bd2196e
--- /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 <http://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..dc89bcd
--- /dev/null
+++ b/src/libdnssec/key/dnskey.c
@@ -0,0 +1,82 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "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;
+ dnssec_binary_t rdata_pubkey = { 0 };
+
+ wire_ctx_t wire = binary_init(rdata);
+ 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..de681c8
--- /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 <http://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..dcb4afe
--- /dev/null
+++ b/src/libdnssec/key/ds.c
@@ -0,0 +1,115 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include "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 algo)
+{
+ return lookup_algorithm(algo) != GNUTLS_DIG_UNKNOWN;
+}
+
+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..10c9c8e
--- /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 <http://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..6f24f87
--- /dev/null
+++ b/src/libdnssec/key/key.c
@@ -0,0 +1,440 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <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/pem.h"
+#include "libdnssec/shared/shared.h"
+#include "libdnssec/shared/binary_wire.h"
+#include "contrib/wire_ctx.h"
+
+/*!
+ * Minimal size of DNSKEY RDATA.
+ */
+#define DNSKEY_RDATA_MIN_SIZE DNSKEY_RDATA_OFFSET_PUBKEY
+
+/*!
+ * RDATA template for newly allocated keys.
+ */
+static const dnssec_binary_t DNSKEY_RDATA_TEMPLATE = {
+ .size = 4,
+ .data = (uint8_t []) { 0x01, 0x00, 0x03, 0x00 }
+};
+
+/* -- key allocation ------------------------------------------------------- */
+
+_public_
+int dnssec_key_new(dnssec_key_t **key_ptr)
+{
+ if (!key_ptr) {
+ return DNSSEC_EINVAL;
+ }
+
+ dnssec_key_t *key = calloc(1, sizeof(*key));
+ if (!key) {
+ return DNSSEC_ENOMEM;
+ }
+
+ int r = dnssec_binary_dup(&DNSKEY_RDATA_TEMPLATE, &key->rdata);
+ if (r != DNSSEC_EOK) {
+ free(key);
+ return DNSSEC_ENOMEM;
+ }
+
+ *key_ptr = key;
+
+ return DNSSEC_EOK;
+}
+
+/*!
+ * Clear allocated fields inside the key structure, except RDATA.
+ */
+static void key_free_internals(dnssec_key_t *key)
+{
+ assert(key);
+
+ free(key->dname);
+ key->dname = NULL;
+
+ gnutls_privkey_deinit(key->private_key);
+ key->private_key = NULL;
+
+ gnutls_pubkey_deinit(key->public_key);
+ key->public_key = NULL;
+}
+
+_public_
+void dnssec_key_clear(dnssec_key_t *key)
+{
+ if (!key) {
+ return;
+ }
+
+ // reuse RDATA
+ dnssec_binary_t rdata = key->rdata;
+
+ // clear the structure
+ key_free_internals(key);
+ clear_struct(key);
+
+ // restore template RDATA (downsize, no need to realloc)
+ assert(rdata.size >= DNSKEY_RDATA_MIN_SIZE);
+ rdata.size = DNSKEY_RDATA_MIN_SIZE;
+ memmove(rdata.data, DNSKEY_RDATA_TEMPLATE.data, rdata.size);
+
+ key->rdata = rdata;
+}
+
+_public_
+void dnssec_key_free(dnssec_key_t *key)
+{
+ if (!key) {
+ return;
+ }
+
+ key_free_internals(key);
+ dnssec_binary_free(&key->rdata);
+
+ free(key);
+}
+
+_public_
+dnssec_key_t *dnssec_key_dup(const dnssec_key_t *key)
+{
+ if (!key) {
+ return NULL;
+ }
+
+ dnssec_key_t *dup = NULL;
+
+ if (dnssec_key_new(&dup) != DNSSEC_EOK ||
+ dnssec_key_set_dname(dup, key->dname) != DNSSEC_EOK ||
+ dnssec_key_set_rdata(dup, &key->rdata) != DNSSEC_EOK
+ ) {
+ dnssec_key_free(dup);
+ return NULL;
+ }
+
+ return dup;
+}
+
+/* -- freely modifiable attributes ----------------------------------------- */
+
+_public_
+const uint8_t *dnssec_key_get_dname(const dnssec_key_t *key)
+{
+ if (!key) {
+ return NULL;
+ }
+
+ return key->dname;
+}
+
+_public_
+int dnssec_key_set_dname(dnssec_key_t *key, const uint8_t *dname)
+{
+ if (!key) {
+ return DNSSEC_EINVAL;
+ }
+
+ uint8_t *copy = NULL;
+ if (dname) {
+ copy = dname_copy(dname);
+ if (!copy) {
+ return DNSSEC_ENOMEM;
+ }
+
+ dname_normalize(copy);
+ }
+
+ free(key->dname);
+ key->dname = copy;
+
+ return DNSSEC_EOK;
+}
+
+_public_
+uint16_t dnssec_key_get_flags(const dnssec_key_t *key)
+{
+ if (!key) {
+ return 0;
+ }
+
+ wire_ctx_t wire = binary_init(&key->rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_FLAGS);
+ return wire_ctx_read_u16(&wire);
+}
+
+_public_
+int dnssec_key_set_flags(dnssec_key_t *key, uint16_t flags)
+{
+ if (!key) {
+ return DNSSEC_EINVAL;
+ }
+
+ wire_ctx_t wire = binary_init(&key->rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_FLAGS);
+ wire_ctx_write_u16(&wire, flags);
+
+ return DNSSEC_EOK;
+}
+
+_public_
+uint8_t dnssec_key_get_protocol(const dnssec_key_t *key)
+{
+ if (!key) {
+ return 0;
+ }
+
+ wire_ctx_t wire = binary_init(&key->rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PROTOCOL);
+ return wire_ctx_read_u8(&wire);
+}
+
+_public_
+int dnssec_key_set_protocol(dnssec_key_t *key, uint8_t protocol)
+{
+ if (!key) {
+ return DNSSEC_EINVAL;
+ }
+
+ wire_ctx_t wire = binary_init(&key->rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PROTOCOL);
+ wire_ctx_write_u8(&wire, protocol);
+
+ return DNSSEC_EOK;
+}
+
+/* -- restricted attributes ------------------------------------------------ */
+
+_public_
+uint16_t dnssec_key_get_keytag(const dnssec_key_t *key)
+{
+ uint16_t keytag = 0;
+ if (dnssec_key_can_verify(key)) {
+ dnssec_keytag(&key->rdata, &keytag);
+ }
+
+ return keytag;
+}
+
+/*!
+ * Check if current public key algorithm matches with the new algorithm.
+ */
+static bool can_change_algorithm(dnssec_key_t *key, uint8_t algorithm)
+{
+ assert(key);
+
+ if (!key->public_key) {
+ return true;
+ }
+
+ gnutls_pk_algorithm_t update = algorithm_to_gnutls(algorithm);
+ if (update == GNUTLS_PK_UNKNOWN) {
+ return false;
+ }
+
+ int current = gnutls_pubkey_get_pk_algorithm(key->public_key, NULL);
+ assert(current >= 0);
+
+ return current == update;
+}
+
+_public_
+uint8_t dnssec_key_get_algorithm(const dnssec_key_t *key)
+{
+ if (!key) {
+ return 0;
+ }
+
+ wire_ctx_t wire = binary_init(&key->rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_ALGORITHM);
+ return wire_ctx_read_u8(&wire);
+}
+
+_public_
+int dnssec_key_set_algorithm(dnssec_key_t *key, uint8_t algorithm)
+{
+ if (!key) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (!can_change_algorithm(key, algorithm)) {
+ return DNSSEC_INVALID_KEY_ALGORITHM;
+ }
+
+ wire_ctx_t wire = binary_init(&key->rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_ALGORITHM);
+ wire_ctx_write_u8(&wire, algorithm);
+
+ return DNSSEC_EOK;
+}
+
+_public_
+int dnssec_key_get_pubkey(const dnssec_key_t *key, dnssec_binary_t *pubkey)
+{
+ if (!key || !pubkey) {
+ return DNSSEC_EINVAL;
+ }
+
+ wire_ctx_t wire = binary_init(&key->rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PUBKEY);
+ binary_available(&wire, pubkey);
+
+ return DNSSEC_EOK;
+}
+
+_public_
+int dnssec_key_set_pubkey(dnssec_key_t *key, const dnssec_binary_t *pubkey)
+{
+ if (!key || !pubkey || !pubkey->data) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (key->public_key) {
+ return DNSSEC_KEY_ALREADY_PRESENT;
+ }
+
+ if (dnssec_key_get_algorithm(key) == 0) {
+ return DNSSEC_INVALID_KEY_ALGORITHM;
+ }
+
+ int result = dnskey_rdata_set_pubkey(&key->rdata, pubkey);
+ if (result != DNSSEC_EOK) {
+ return result;
+ }
+
+ result = dnskey_rdata_to_crypto_key(&key->rdata, &key->public_key);
+ if (result != DNSSEC_EOK) {
+ key->rdata.size = DNSKEY_RDATA_OFFSET_PUBKEY; // downsize
+ return result;
+ }
+
+ return DNSSEC_EOK;
+}
+
+_public_
+unsigned dnssec_key_get_size(const dnssec_key_t *key)
+{
+ if (!key || !key->public_key) {
+ return 0;
+ }
+
+ unsigned bits = 0;
+ uint8_t algorithm = dnssec_key_get_algorithm(key);
+ switch (algorithm) {
+ case 13:
+ bits = 256;
+ break;
+ case 14:
+ bits = 384;
+ break;
+ case 15:
+ bits = 256;
+ break;
+ case 16:
+ bits = 456;
+ break;
+ default:
+ gnutls_pubkey_get_pk_algorithm(key->public_key, &bits);
+ }
+
+ return bits;
+}
+
+_public_
+int dnssec_key_get_keyid(const dnssec_key_t *key, char **id)
+{
+ if (!key || !id) {
+ return DNSSEC_EINVAL;
+ }
+
+ return keyid_pubkey_hex(key->public_key, id);
+}
+
+_public_
+int dnssec_key_get_rdata(const dnssec_key_t *key, dnssec_binary_t *rdata)
+{
+ if (!key || !rdata) {
+ return DNSSEC_EINVAL;
+ }
+
+ *rdata = key->rdata;
+
+ return DNSSEC_EOK;
+}
+
+_public_
+int dnssec_key_set_rdata(dnssec_key_t *key, const dnssec_binary_t *rdata)
+{
+ if (!key || !rdata || !rdata->data) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (rdata->size < DNSKEY_RDATA_MIN_SIZE) {
+ return DNSSEC_MALFORMED_DATA;
+ }
+
+ if (key->public_key) {
+ return DNSSEC_KEY_ALREADY_PRESENT;
+ }
+
+ gnutls_pubkey_t new_pubkey = NULL;
+ int result = dnskey_rdata_to_crypto_key(rdata, &new_pubkey);
+ if (result != DNSSEC_EOK) {
+ return result;
+ }
+
+ result = dnssec_binary_resize(&key->rdata, rdata->size);
+ if (result != DNSSEC_EOK) {
+ gnutls_pubkey_deinit(new_pubkey);
+ return result;
+ }
+
+ // commit result
+ memmove(key->rdata.data, rdata->data, rdata->size);
+ key->public_key = new_pubkey;
+
+ return DNSSEC_EOK;
+}
+
+/* -- key presence checking ------------------------------------------------ */
+
+_public_
+bool dnssec_key_can_sign(const dnssec_key_t *key)
+{
+ return key && key->private_key;
+}
+
+_public_
+bool dnssec_key_can_verify(const dnssec_key_t *key)
+{
+ return key && key->public_key;
+}
diff --git a/src/libdnssec/key/keytag.c b/src/libdnssec/key/keytag.c
new file mode 100644
index 0000000..edff684
--- /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 <http://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..ec1dcbd
--- /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 <http://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..c0c5bb2
--- /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 <http://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..a2d8ea4
--- /dev/null
+++ b/src/libdnssec/key/simple.c
@@ -0,0 +1,55 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <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/shared/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 = pem_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..c99a657
--- /dev/null
+++ b/src/libdnssec/keyid.c
@@ -0,0 +1,87 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <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) != DNSSEC_KEYID_SIZE) {
+ return false;
+ }
+
+ for (int i = 0; i < DNSSEC_KEYID_SIZE; 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; i < DNSSEC_KEYID_SIZE; 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..24e201e
--- /dev/null
+++ b/src/libdnssec/keyid.h
@@ -0,0 +1,80 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \file
+ *
+ * \addtogroup keyid
+ *
+ * \brief DNSSEC key ID manipulation.
+ *
+ * The module contains auxiliary functions for manipulation with key IDs.
+ *
+ * Example:
+ *
+ * ~~~~~ {.c}
+ *
+ * char *key_id = "ef26672cafede0732dd18fba6488fa390b5589af";
+ * assert(dnssec_keyid_is_valid(key_id));
+ *
+ * char copy[DNSSEC_KEY_ID_SIZE + 1] = { 0 };
+ * memcpy(copy, key_id, sizeof(copy));
+ * for (int i = 0; i < DNSSEC_KEY_ID_SIZE; i++) {
+ * copy[i] = toupper((unsigned char)copy[i]);
+ * }
+ *
+ * assert(dnssec_keyid_equal(key_id, copy));
+ *
+ * ~~~~~
+ *
+ * @{
+ */
+
+#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..2382518
--- /dev/null
+++ b/src/libdnssec/keystore.h
@@ -0,0 +1,297 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \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, and allows implementation of custom stores.
+ *
+ * PKCS #11 provides access Hardware Security Modules.
+ *
+ * Example of using default PKCS #8 and to generate an RSA key:
+ *
+ * ~~~~~ {.c}
+ *
+ * int result;
+ * dnssec_keystore_t *store = NULL;
+ *
+ * // create key store access context
+ * dnssec_keystore_init_pkcs8_dir(&store);
+ *
+ * // open the key store
+ * result = dnssec_keystore_open(&store, "/path/to/keydb");
+ * if (result != DNSSEC_EOK) {
+ * return result;
+ * }
+ *
+ * // generate new private key in the key store
+ * int algorithm = DNSSEC_KEY_ALGORITHM_RSA_SHA256;
+ * unsigned bits = 2048;
+ * char *id = NULL;
+ * int dnssec_keystore_generate_key(store, algorithm, bits, &key_id);
+ * if (result != DNSSEC_EOK) {
+ * dnssec_keystore_close(store);
+ * return result;
+ * }
+ * printf("ID of the new key: %s\n", key_id);
+ *
+ * // create new signing key
+ * dnssec_key_t *key = NULL;
+ * result = dnssec_key_new(&key);
+ * if (result != DNSSEC_EOK) {
+ * free(key_id);
+ * dnssec_keystore_close(store);
+ * return result;
+ * }
+ *
+ * // import the key from the key store
+ * result = dnssec_key_import_keystore(key, store, key_id, algorithm);
+ * if (result != DNSSEC_EOK) {
+ * free(key_id);
+ * dnssec_key_free(key);
+ * dnssec_keystore_close(store);
+ * return result;
+ * }
+ *
+ * // use the key for signing ...
+ *
+ * // cleanup
+ * free(key_id);
+ * dnssec_key_free(key);
+ * dnssec_keystore_close(store);
+ * dnssec_keystore_deinit(store);
+ *
+ * ~~~~~
+ * @{
+ */
+
+#pragma once
+
+#include <libdnssec/binary.h>
+#include <libdnssec/key.h>
+#include <libdnssec/list.h>
+
+struct dnssec_keystore;
+
+/*!
+ * DNSSEC private keys store.
+ */
+typedef struct dnssec_keystore dnssec_keystore_t;
+
+/*!
+ * PKCS #8 key store callback functions for custom providers.
+ */
+typedef struct dnssec_keystore_pkcs8_functions {
+ /*!
+ * Callback to allocate key store handle.
+ *
+ * \param[out] handle_ptr Allocated key store handle.
+ */
+ int (*handle_new)(void **handle_ptr);
+
+ /*!
+ * Callback to deallocate key store handle.
+ *
+ * \param handle Key store handle.
+ */
+ int (*handle_free)(void *handle);
+
+ /*!
+ * Callback to initialize the key store.
+ *
+ * \param handle Key store handle.
+ * \param config Configuration string.
+ */
+ int (*init)(void *handle, const char *config);
+
+ /*!
+ * Callback to open the key store.
+ *
+ * \param[out] handle Key store handle.
+ * \param[in] config Configuration string.
+ */
+ int (*open)(void *handle, const char *config);
+
+ /*!
+ * Callback to close the key store.
+ *
+ * \param handle Key store handle.
+ */
+ int (*close)(void *handle);
+
+ /*!
+ * Callback to read a PEM key.
+ *
+ * \param[in] handle Key store handle.
+ * \param[in] id Key ID of the key to be retrieved (ASCII form).
+ * \param[out] pem Key material in uncencrypted PEM format.
+ */
+ int (*read)(void *handle, const char *id, dnssec_binary_t *pem);
+
+ /*!
+ * Callback to write a PEM key.
+ *
+ * \param handle Key store handle.
+ * \param id Key ID of the key to be saved (ASCII form).
+ * \param pem Key material in unencrypted PEM format.
+ */
+ int (*write)(void *handle, const char *id, const dnssec_binary_t *pem);
+
+ /*!
+ * Callback to get a list of all PEM key IDs.
+ *
+ * \param[in] handle Key store handle.
+ * \param[out] list Allocated list of key IDs.
+ */
+ int (*list)(void *handle, dnssec_list_t **list);
+
+ /*!
+ * Callback to remove a PEM key.
+ *
+ * \param handle Key store handle.
+ * \param id Key ID of the key to be removed (ASCII form).
+ */
+ int (*remove)(void *handle, const char *id);
+} dnssec_keystore_pkcs8_functions_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_dir(dnssec_keystore_t **store);
+
+/*!
+ * Create custom PKCS #8 private key store context.
+ *
+ * \param[out] store Opened key store.
+ * \param[in] impl Implementation of the key store provider.
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int dnssec_keystore_init_pkcs8_custom(dnssec_keystore_t **store,
+ const dnssec_keystore_pkcs8_functions_t *impl);
+
+/*!
+ * 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);
+
+/*!
+ * Get a list of key IDs stored in the key store.
+ *
+ * \todo Not implemented.
+ *
+ * \param[in] store Key store.
+ * \param[out] list Resulting list of key IDs.
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int dnssec_keystore_list_keys(dnssec_keystore_t *store, dnssec_list_t **list);
+
+/*!
+ * 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[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_key(dnssec_keystore_t *store,
+ dnssec_key_algorithm_t algorithm,
+ unsigned bits, 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_key(dnssec_keystore_t *store, const char *id);
+
+/*!
+ * Import public and/or private key from the key store into a DNSSEC key.
+ *
+ * The key algorithm has to be set before calling this function.
+ *
+ * \param key DNSSEC key to be initialized.
+ * \param keystore Private key store.
+ * \param id ID of the key.
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int dnssec_key_import_keystore(dnssec_key_t *key, dnssec_keystore_t *keystore,
+ const char *id);
+
+/*! @} */
diff --git a/src/libdnssec/keystore/internal.h b/src/libdnssec/keystore/internal.h
new file mode 100644
index 0000000..a87dd03
--- /dev/null
+++ b/src/libdnssec/keystore/internal.h
@@ -0,0 +1,52 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <gnutls/gnutls.h>
+#include <gnutls/abstract.h>
+
+#include "libdnssec/binary.h"
+#include "libdnssec/key.h"
+#include "libdnssec/keystore.h"
+#include "libdnssec/list.h"
+
+typedef struct keystore_functions {
+ // construction of internal context
+ int (*ctx_new)(void **ctx_ptr, void *data);
+ int (*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 (*list_keys)(void *ctx, dnssec_list_t **list);
+ int (*generate_key)(void *ctx, gnutls_pk_algorithm_t algorithm,
+ unsigned bits, 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);
+} 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,
+ void *ctx_custom_data);
diff --git a/src/libdnssec/keystore/keystore.c b/src/libdnssec/keystore/keystore.c
new file mode 100644
index 0000000..203441a
--- /dev/null
+++ b/src/libdnssec/keystore/keystore.c
@@ -0,0 +1,183 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <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,
+ void *ctx_custom_data)
+{
+ 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, ctx_custom_data);
+ 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_list_keys(dnssec_keystore_t *store, dnssec_list_t **list)
+{
+ if (!store || !list) {
+ return DNSSEC_EINVAL;
+ }
+
+ return store->functions->list_keys(store->ctx, list);
+}
+
+_public_
+int dnssec_keystore_generate_key(dnssec_keystore_t *store,
+ dnssec_key_algorithm_t _algorithm,
+ unsigned bits, 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, 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_key(dnssec_keystore_t *store, const char *key_id)
+{
+ if (!store || !key_id) {
+ return DNSSEC_EINVAL;
+ }
+
+ return store->functions->remove_key(store->ctx, key_id);
+}
+
+_public_
+int dnssec_key_import_keystore(dnssec_key_t *key, dnssec_keystore_t *store,
+ const char *id)
+{
+ if (!key || !store || !id || dnssec_key_get_algorithm(key) == 0) {
+ 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;
+}
diff --git a/src/libdnssec/keystore/pkcs11.c b/src/libdnssec/keystore/pkcs11.c
new file mode 100644
index 0000000..dbe1a37
--- /dev/null
+++ b/src/libdnssec/keystore/pkcs11.c
@@ -0,0 +1,440 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <gnutls/gnutls.h>
+#include <pthread.h>
+
+#include "libdnssec/error.h"
+#include "libdnssec/shared/hex.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/shared/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, _unused_ void *data)
+{
+ 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 int pkcs11_ctx_free(void *ctx)
+{
+ if (ctx) {
+ free(ctx);
+ }
+
+ return DNSSEC_EOK;
+}
+
+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 char *get_object_id(gnutls_pkcs11_obj_t object)
+{
+ assert(object);
+
+ uint8_t buffer[DNSSEC_KEYID_BINARY_SIZE] = { 0 };
+ size_t size = sizeof(buffer);
+
+ int r = gnutls_pkcs11_obj_get_info(object, GNUTLS_PKCS11_OBJ_ID, buffer, &size);
+ if (r != GNUTLS_E_SUCCESS || size != sizeof(buffer)) {
+ return NULL;
+ }
+
+ const dnssec_binary_t bin = { .data = buffer, .size = sizeof(buffer) };
+ char *id = NULL;
+ if (bin_to_hex(&bin, &id) != DNSSEC_EOK) {
+ return NULL;
+ }
+
+ assert(strlen(id) == DNSSEC_KEYID_SIZE);
+
+ return id;
+}
+
+static int pkcs11_list_keys(void *_ctx, dnssec_list_t **list)
+{
+ pkcs11_ctx_t *ctx = _ctx;
+
+ dnssec_list_t *ids = dnssec_list_new();
+ if (!ids) {
+ return DNSSEC_ENOMEM;
+ }
+
+ gnutls_pkcs11_obj_t *objects = NULL;
+ unsigned count = 0;
+
+ int flags = GNUTLS_PKCS11_OBJ_FLAG_PRIVKEY |
+ GNUTLS_PKCS11_OBJ_FLAG_LOGIN;
+
+ int r = gnutls_pkcs11_obj_list_import_url4(&objects, &count, ctx->url, flags);
+ if (r != GNUTLS_E_SUCCESS) {
+ dnssec_list_free(ids);
+ return DNSSEC_P11_TOKEN_NOT_AVAILABLE;
+ }
+
+ for (unsigned i = 0; i < count; i++) {
+ gnutls_pkcs11_obj_t object = objects[i];
+ char *id = get_object_id(object);
+ dnssec_list_append(ids, id);
+ gnutls_pkcs11_obj_deinit(object);
+ }
+
+ gnutls_free(objects);
+
+ *list = ids;
+ return DNSSEC_EOK;
+}
+
+static int pkcs11_generate_key(void *_ctx, gnutls_pk_algorithm_t algorithm,
+ unsigned bits, 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, NULL, &gt_cka_id, 0, NULL, 0, flags);
+ if (r != GNUTLS_E_SUCCESS) {
+ return DNSSEC_KEY_GENERATE_ERROR;
+ }
+
+ char *id = NULL;
+ r = bin_to_hex(&cka_id, &id);
+ if (r != DNSSEC_EOK) {
+ 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 = pem_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;
+ }
+
+ return bin_to_hex(&id, id_ptr);
+}
+
+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;
+}
+
+/* -- 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,
+ .list_keys = pkcs11_list_keys,
+ .generate_key = pkcs11_generate_key,
+ .import_key = pkcs11_import_key,
+ .remove_key = pkcs11_remove_key,
+ .get_private = pkcs11_get_private,
+ };
+
+ return keystore_create(store_ptr, &IMPLEMENTATION, NULL);
+}
+
+#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..f05eda0
--- /dev/null
+++ b/src/libdnssec/keystore/pkcs8.c
@@ -0,0 +1,211 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+
+#include "libdnssec/error.h"
+#include "libdnssec/key/algorithm.h"
+#include "libdnssec/keyid.h"
+#include "libdnssec/keystore.h"
+#include "libdnssec/keystore/internal.h"
+#include "libdnssec/shared/pem.h"
+#include "libdnssec/shared/shared.h"
+
+/*!
+ * PKCS #8 key store context.
+ */
+typedef struct pkcs8_ctx {
+ /*! Storage implementation callbacks. */
+ const dnssec_keystore_pkcs8_functions_t *functions;
+ /*! Implementation specific context data. */
+ void *data;
+} pkcs8_ctx_t;
+
+/* -- internal API --------------------------------------------------------- */
+
+static int pkcs8_ctx_new(void **ctx_ptr, void *_functions)
+{
+ assert(ctx_ptr);
+ assert(_functions);
+
+ pkcs8_ctx_t *ctx = calloc(1, sizeof(*ctx));
+ if (!ctx) {
+ return DNSSEC_ENOMEM;
+ }
+
+ ctx->functions = _functions;
+
+ int r = ctx->functions->handle_new(&ctx->data);
+ if (r != DNSSEC_EOK) {
+ free(ctx);
+ return r;
+ }
+
+ *ctx_ptr = ctx;
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs8_ctx_free(void *_ctx)
+{
+ pkcs8_ctx_t *ctx = _ctx;
+ int r = ctx->functions->handle_free(ctx->data);
+
+ free(ctx);
+
+ return r;
+}
+
+static int pkcs8_init(void *_ctx, const char *config)
+{
+ pkcs8_ctx_t *ctx = _ctx;
+ return ctx->functions->init(ctx->data, config);
+}
+
+static int pkcs8_open(void *_ctx, const char *config)
+{
+ pkcs8_ctx_t *ctx = _ctx;
+ return ctx->functions->open(ctx->data, config);
+}
+
+static int pkcs8_close(void *_ctx)
+{
+ pkcs8_ctx_t *ctx = _ctx;
+ return ctx->functions->close(ctx->data);
+}
+
+static int pkcs8_list_keys(void *_ctx, dnssec_list_t **list)
+{
+ pkcs8_ctx_t *ctx = _ctx;
+ return ctx->functions->list(ctx->data, list);
+}
+
+static int pkcs8_generate_key(void *_ctx, gnutls_pk_algorithm_t algorithm,
+ unsigned bits, char **id_ptr)
+{
+ assert(_ctx);
+ assert(id_ptr);
+
+ pkcs8_ctx_t *ctx = _ctx;
+
+ // generate key
+
+ char *new_id = NULL;
+ _cleanup_binary_ dnssec_binary_t data = { 0 };
+ int r = pem_generate(algorithm, bits, &data, &new_id);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ // save key
+
+ r = ctx->functions->write(ctx->data, new_id, &data);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ // finish
+
+ *id_ptr = new_id;
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs8_import_key(void *_ctx, const dnssec_binary_t *pem, char **id_ptr)
+{
+ pkcs8_ctx_t *ctx = _ctx;
+
+ // retrieve key ID
+
+ char *id = NULL;
+ int r = pem_keyid(pem, &id);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ // save the key
+
+ r = ctx->functions->write(ctx->data, id, pem);
+ if (r != DNSSEC_EOK) {
+ free(id);
+ return r;
+ }
+
+ *id_ptr = id;
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs8_remove_key(void *_ctx, const char *id)
+{
+ pkcs8_ctx_t *ctx = _ctx;
+ return ctx->functions->remove(ctx->data, id);
+}
+
+static int pkcs8_get_private(void *_ctx, const char *id, gnutls_privkey_t *key_ptr)
+{
+ assert(_ctx);
+ assert(id);
+ assert(key_ptr);
+
+ pkcs8_ctx_t *ctx = _ctx;
+
+ // load private key data
+
+ _cleanup_binary_ dnssec_binary_t pem = { 0 };
+ int r = ctx->functions->read(ctx->data, id, &pem);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ // construct the key
+
+ gnutls_privkey_t key = NULL;
+ r = pem_privkey(&pem, &key);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ *key_ptr = key;
+
+ return DNSSEC_EOK;
+}
+
+static const keystore_functions_t PKCS8_FUNCTIONS = {
+ .ctx_new = pkcs8_ctx_new,
+ .ctx_free = pkcs8_ctx_free,
+ .init = pkcs8_init,
+ .open = pkcs8_open,
+ .close = pkcs8_close,
+ .list_keys = pkcs8_list_keys,
+ .generate_key = pkcs8_generate_key,
+ .import_key = pkcs8_import_key,
+ .remove_key = pkcs8_remove_key,
+ .get_private = pkcs8_get_private,
+};
+
+/* -- public API ----------------------------------------------------------- */
+
+_public_
+int dnssec_keystore_init_pkcs8_custom(dnssec_keystore_t **store_ptr,
+ const dnssec_keystore_pkcs8_functions_t *store_functions)
+{
+ if (!store_ptr || !store_functions) {
+ return DNSSEC_EINVAL;
+ }
+
+ return keystore_create(store_ptr, &PKCS8_FUNCTIONS, (void *)store_functions);
+}
diff --git a/src/libdnssec/keystore/pkcs8_dir.c b/src/libdnssec/keystore/pkcs8_dir.c
new file mode 100644
index 0000000..bc67ad5
--- /dev/null
+++ b/src/libdnssec/keystore/pkcs8_dir.c
@@ -0,0 +1,379 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "libdnssec/binary.h"
+#include "libdnssec/error.h"
+#include "libdnssec/shared/fs.h"
+#include "libdnssec/key.h"
+#include "libdnssec/keystore.h"
+#include "libdnssec/keystore/internal.h"
+#include "libdnssec/shared/shared.h"
+
+#define DIR_INIT_MODE 0750
+
+/*!
+ * Context for PKCS #8 key directory.
+ */
+typedef struct pkcs8_dir_handle {
+ 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);
+}
+
+/*!
+ * Strip '.pem' suffix, basename has to be valid key ID.
+ */
+static char *filename_to_keyid(const char *filename)
+{
+ assert(filename);
+
+ size_t len = strlen(filename);
+
+ const char ext[] = ".pem";
+ const size_t ext_len = sizeof(ext) - 1;
+
+ if (len < ext_len || strcmp(filename + len - ext_len, ext) != 0) {
+ return NULL;
+ }
+
+ return strndup(filename, len - ext_len);
+}
+
+/* -- PKCS #8 dir access API ----------------------------------------------- */
+
+static int pkcs8_dir_handle_new(void **handle_ptr)
+{
+ if (!handle_ptr) {
+ return DNSSEC_EINVAL;
+ }
+
+ pkcs8_dir_handle_t *handle = calloc(1, sizeof(*handle));
+ if (!handle) {
+ return DNSSEC_ENOMEM;
+ }
+
+ *handle_ptr = handle;
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs8_dir_handle_free(void *handle)
+{
+ free(handle);
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs8_dir_init(void *handle, const char *path)
+{
+ if (!handle || !path) {
+ return DNSSEC_EINVAL;
+ }
+
+ return fs_mkdir(path, DIR_INIT_MODE, true);
+}
+
+static int pkcs8_dir_open(void *_handle, const char *config)
+{
+ if (!_handle || !config) {
+ return DNSSEC_EINVAL;
+ }
+
+ pkcs8_dir_handle_t *handle = _handle;
+
+ char *path = realpath(config, NULL);
+ if (!path) {
+ return DNSSEC_NOT_FOUND;
+ }
+
+ handle->dir_name = path;
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs8_dir_close(void *_handle)
+{
+ if (!_handle) {
+ return DNSSEC_EINVAL;
+ }
+
+ pkcs8_dir_handle_t *handle = _handle;
+
+ free(handle->dir_name);
+ memset(handle, 0, sizeof(*handle));
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs8_dir_read(void *_handle, const char *id, dnssec_binary_t *pem)
+{
+ if (!_handle || !id || !pem) {
+ return DNSSEC_EINVAL;
+ }
+
+ pkcs8_dir_handle_t *handle = _handle;
+
+ // open file and get it's size
+
+ _cleanup_close_ int file = 0;
+ 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 pkcs8_dir_write(void *_handle, const char *id, const dnssec_binary_t *pem)
+{
+ if (!_handle || !id || !pem) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (pem->size == 0 || pem->data == NULL) {
+ return DNSSEC_MALFORMED_DATA;
+ }
+
+ pkcs8_dir_handle_t *handle = _handle;
+
+ // create the file
+
+ _cleanup_close_ int file = 0;
+ int result = key_open_write(handle->dir_name, id, &file);
+ if (result != DNSSEC_EOK) {
+ if (key_is_duplicate(result, handle, id, pem)) {
+ return DNSSEC_EOK;
+ }
+ return result;
+ }
+
+ // 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);
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs8_dir_list(void *_handle, dnssec_list_t **list_ptr)
+{
+ if (!_handle || !list_ptr) {
+ return DNSSEC_EINVAL;
+ }
+
+ pkcs8_dir_handle_t *handle = _handle;
+
+ _cleanup_closedir_ DIR *dir = opendir(handle->dir_name);
+ if (!dir) {
+ return DNSSEC_NOT_FOUND;
+ }
+
+ dnssec_list_t *list = dnssec_list_new();
+ if (!list) {
+ return DNSSEC_ENOMEM;
+ }
+
+ errno = 0;
+ struct dirent *result;
+ while ((result = readdir(dir)) != NULL) {
+ char *keyid = filename_to_keyid(result->d_name);
+ if (keyid) {
+ dnssec_list_append(list, keyid);
+ }
+ }
+
+ if (errno != 0) {
+ dnssec_list_free_full(list, NULL, NULL);
+ return dnssec_errno_to_error(errno);
+ }
+
+ *list_ptr = list;
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs8_dir_remove(void *_handle, const char *id)
+{
+ if (!_handle || !id) {
+ return DNSSEC_EINVAL;
+ }
+
+ pkcs8_dir_handle_t *handle = _handle;
+
+ _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;
+}
+
+const dnssec_keystore_pkcs8_functions_t PKCS8_DIR_FUNCTIONS = {
+ .handle_new = pkcs8_dir_handle_new,
+ .handle_free = pkcs8_dir_handle_free,
+ .init = pkcs8_dir_init,
+ .open = pkcs8_dir_open,
+ .close = pkcs8_dir_close,
+ .read = pkcs8_dir_read,
+ .write = pkcs8_dir_write,
+ .list = pkcs8_dir_list,
+ .remove = pkcs8_dir_remove,
+};
+
+/* -- public API ----------------------------------------------------------- */
+
+_public_
+int dnssec_keystore_init_pkcs8_dir(dnssec_keystore_t **store_ptr)
+{
+ if (!store_ptr) {
+ return DNSSEC_EINVAL;
+ }
+
+ return dnssec_keystore_init_pkcs8_custom(store_ptr, &PKCS8_DIR_FUNCTIONS);
+}
diff --git a/src/libdnssec/keytag.h b/src/libdnssec/keytag.h
new file mode 100644
index 0000000..5a8424e
--- /dev/null
+++ b/src/libdnssec/keytag.h
@@ -0,0 +1,63 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \file
+ *
+ * \addtogroup keytag
+ *
+ * \brief Low-level key tag computation API.
+ *
+ * The module provides simple interface for DNSKEY key id computation.
+ *
+ * Example:
+ *
+ * ~~~~~ {.c}
+ *
+ * dnssec_binary_t dnskey_rdata = // ... ;
+ *
+ * int result;
+ * uint16_t keytag = 0;
+ *
+ * result = dnssec_keytag(&dnskey_rdata, &keytag);
+ * if (result != DNSSEC_EOK) {
+ * return result;
+ * }
+ *
+ * printf("keytag: %s\n", keytag);
+ *
+ * ~~~~~
+ *
+ * @{
+ */
+
+#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/list.h b/src/libdnssec/list.h
new file mode 100644
index 0000000..838c0b5
--- /dev/null
+++ b/src/libdnssec/list.h
@@ -0,0 +1,85 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \file
+ *
+ * \addtogroup list
+ *
+ * \brief DNSSEC generic lists.
+ *
+ * @{
+ */
+
+#pragma once
+
+#include <stdbool.h>
+#include <stdlib.h>
+
+struct dnssec_list;
+typedef struct dnssec_list dnssec_list_t;
+
+struct dnssec_item;
+typedef struct dnssec_item dnssec_item_t;
+
+void *dnssec_item_get(const dnssec_item_t *item);
+void dnssec_item_set(dnssec_item_t *item, void *data);
+
+dnssec_list_t *dnssec_list_new(void);
+void dnssec_list_free(dnssec_list_t *list);
+
+typedef void (*dnssec_item_free_cb)(void *data, void *ctx);
+
+/*!
+ * Free the list including contained items.
+ *
+ * If \c free_cb is NULL, standard libc \c free will used to free the items.
+ *
+ * \param list List to be freed.
+ * \param free_cb Free function called for each item in the list.
+ * \param free_ctx Context passed to item free.
+ */
+void dnssec_list_free_full(dnssec_list_t *list, dnssec_item_free_cb free_cb,
+ void *free_ctx);
+
+void dnssec_list_clear(dnssec_list_t *list);
+void dnssec_list_clear_full(dnssec_list_t *list, dnssec_item_free_cb free_cb,
+ void *free_ctx);
+
+dnssec_item_t *dnssec_list_head(dnssec_list_t *list);
+dnssec_item_t *dnssec_list_tail(dnssec_list_t *list);
+dnssec_item_t *dnssec_list_next(dnssec_list_t *list, dnssec_item_t *item);
+dnssec_item_t *dnssec_list_prev(dnssec_list_t *list, dnssec_item_t *item);
+dnssec_item_t *dnssec_list_nth(dnssec_list_t *list, size_t position);
+
+bool dnssec_list_is_empty(dnssec_list_t *list);
+size_t dnssec_list_size(dnssec_list_t *list);
+
+int dnssec_list_insert_after(dnssec_item_t *item, void *data);
+int dnssec_list_insert_before(dnssec_item_t *item, void *data);
+int dnssec_list_append(dnssec_list_t *list, void *data);
+int dnssec_list_prepend(dnssec_list_t *list, void *data);
+
+void dnssec_list_remove(dnssec_item_t *item);
+
+dnssec_item_t *dnssec_list_search(dnssec_list_t *list, void *data);
+bool dnssec_list_contains(dnssec_list_t *list, void *data);
+
+#define dnssec_list_foreach(var, list) \
+ for (dnssec_item_t *__tmp, *var = dnssec_list_head(list); \
+ __tmp = dnssec_list_next(list, var), var != NULL; \
+ var = __tmp)
+
+/*! @} */
diff --git a/src/libdnssec/list/list.c b/src/libdnssec/list/list.c
new file mode 100644
index 0000000..5d9aded
--- /dev/null
+++ b/src/libdnssec/list/list.c
@@ -0,0 +1,298 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <stdlib.h>
+
+#include "libdnssec/list.h"
+#include "libdnssec/list/ucw_clists.h"
+#include "libdnssec/error.h"
+#include "libdnssec/shared/shared.h"
+
+struct dnssec_list {
+ clist list;
+};
+
+struct dnssec_item {
+ cnode node;
+ void *data;
+};
+
+/*!
+ * Allocate new list item and set item data.
+ */
+static dnssec_item_t *item_new(void *data)
+{
+ dnssec_item_t *item = malloc(sizeof(*item));
+ if (item) {
+ clear_struct(item);
+ item->data = data;
+ }
+
+ return item;
+}
+
+/*!
+ * Wrapper around libc free with unused context.
+ */
+static void wrap_free(void *ptr, void *ctx _unused_)
+{
+ free(ptr);
+}
+
+/* -- public API ----------------------------------------------------------- */
+
+_public_
+void *dnssec_item_get(const dnssec_item_t *item)
+{
+ return item ? item->data : NULL;
+}
+
+_public_
+void dnssec_item_set(dnssec_item_t *item, void *data)
+{
+ if (item) {
+ item->data = data;
+ }
+}
+
+_public_
+dnssec_list_t *dnssec_list_new(void)
+{
+ dnssec_list_t *list = malloc(sizeof(*list));
+ if (list) {
+ clist_init(&list->list);
+ }
+
+ return list;
+}
+
+_public_
+void dnssec_list_clear(dnssec_list_t *list)
+{
+ if (!list) {
+ return;
+ }
+
+ dnssec_list_foreach(item, list) {
+ free(item);
+ }
+}
+
+_public_
+void dnssec_list_clear_full(dnssec_list_t *list, dnssec_item_free_cb free_cb,
+ void *free_ctx)
+{
+ if (!list) {
+ return;
+ }
+
+ if (!free_cb) {
+ free_cb = wrap_free;
+ }
+
+ dnssec_list_foreach(item, list) {
+ free_cb(item->data, free_ctx);
+ free(item);
+ }
+}
+
+_public_
+void dnssec_list_free(dnssec_list_t *list)
+{
+ if (!list) {
+ return;
+ }
+
+ dnssec_list_clear(list);
+ free(list);
+}
+
+_public_
+void dnssec_list_free_full(dnssec_list_t *list, dnssec_item_free_cb free_cb,
+ void *free_ctx)
+{
+ if (!list) {
+ return;
+ }
+
+ dnssec_list_clear_full(list, free_cb, free_ctx);
+ free(list);
+}
+
+_public_
+dnssec_item_t *dnssec_list_head(dnssec_list_t *list)
+{
+ if (!list) {
+ return NULL;
+ }
+
+ return clist_head(&list->list);
+}
+
+_public_
+dnssec_item_t *dnssec_list_tail(dnssec_list_t *list)
+{
+ if (!list) {
+ return NULL;
+ }
+
+ return clist_tail(&list->list);
+}
+
+_public_
+dnssec_item_t *dnssec_list_next(dnssec_list_t *list, dnssec_item_t *item)
+{
+ if (!list || !item) {
+ return NULL;
+ }
+
+ return clist_next(&list->list, &item->node);
+}
+
+_public_
+dnssec_item_t *dnssec_list_prev(dnssec_list_t *list, dnssec_item_t *item)
+{
+ if (!list || !item) {
+ return NULL;
+ }
+
+ return clist_prev(&list->list, &item->node);
+}
+
+_public_
+dnssec_item_t *dnssec_list_nth(dnssec_list_t *list, size_t position)
+{
+ size_t index = 0;
+ dnssec_item_t *item = dnssec_list_head(list);
+
+ while (item) {
+ if (index == position) {
+ return item;
+ } else {
+ item = dnssec_list_next(list, item);
+ index += 1;
+ }
+ }
+
+ return NULL;
+}
+
+_public_
+bool dnssec_list_is_empty(dnssec_list_t *list)
+{
+ return !list || clist_empty(&list->list);
+}
+
+_public_
+size_t dnssec_list_size(dnssec_list_t *list)
+{
+ return list ? clist_size(&list->list) : 0;
+}
+
+_public_
+int dnssec_list_insert_before(dnssec_item_t *item, void *data)
+{
+ if (!item) {
+ return DNSSEC_EINVAL;
+ }
+
+ dnssec_item_t *add = item_new(data);
+ if (!add) {
+ return DNSSEC_ENOMEM;
+ }
+
+ clist_insert_before(&add->node, &item->node);
+
+ return DNSSEC_EOK;
+}
+
+_public_
+int dnssec_list_insert_after(dnssec_item_t *item, void *data)
+{
+ if (!item) {
+ return DNSSEC_EINVAL;
+ }
+
+ dnssec_item_t *add = item_new(data);
+ if (!add) {
+ return DNSSEC_ENOMEM;
+ }
+
+ clist_insert_after(&add->node, &item->node);
+
+ return DNSSEC_EOK;
+}
+
+_public_
+int dnssec_list_append(dnssec_list_t *list, void *data)
+{
+ if (!list) {
+ return DNSSEC_EINVAL;
+ }
+
+ dnssec_item_t *add = item_new(data);
+ if (!add) {
+ return DNSSEC_ENOMEM;
+ }
+
+ clist_add_tail(&list->list , &add->node);
+
+ return DNSSEC_EOK;
+}
+
+_public_
+int dnssec_list_prepend(dnssec_list_t *list, void *data)
+{
+ if (!list) {
+ return DNSSEC_EINVAL;
+ }
+
+ dnssec_item_t *add = item_new(data);
+ if (!add) {
+ return DNSSEC_ENOMEM;
+ }
+
+ clist_add_head(&list->list , &add->node);
+
+ return DNSSEC_EOK;
+}
+
+_public_
+void dnssec_list_remove(dnssec_item_t *item)
+{
+ if (item) {
+ clist_remove(&item->node);
+ free(item);
+ }
+}
+
+_public_
+dnssec_item_t *dnssec_list_search(dnssec_list_t *list, void *data)
+{
+ dnssec_list_foreach(item, list) {
+ if (item->data == data) {
+ return item;
+ }
+ }
+
+ return NULL;
+}
+
+_public_
+bool dnssec_list_contains(dnssec_list_t *list, void *data)
+{
+ return dnssec_list_search(list, data) != NULL;
+}
diff --git a/src/libdnssec/list/ucw_clists.h b/src/libdnssec/list/ucw_clists.h
new file mode 100644
index 0000000..080d01e
--- /dev/null
+++ b/src/libdnssec/list/ucw_clists.h
@@ -0,0 +1,260 @@
+/*
+ * UCW Library -- Circular Linked Lists
+ *
+ * (c) 2003--2010 Martin Mares <mj@ucw.cz>
+ *
+ * This software may be freely distributed and used according to the terms
+ * of the GNU Lesser General Public License.
+ */
+
+#ifndef _UCW_CLISTS_H
+#define _UCW_CLISTS_H
+
+/**
+ * Common header for list nodes.
+ **/
+typedef struct cnode {
+ struct cnode *next, *prev;
+} cnode;
+
+/**
+ * Circular doubly linked list.
+ **/
+typedef struct clist {
+ struct cnode head;
+} clist;
+
+/**
+ * Initialize a new circular linked list. Must be called before any other function.
+ **/
+static inline void clist_init(clist *l)
+{
+ cnode *head = &l->head;
+ head->next = head->prev = head;
+}
+
+/**
+ * Return the first node on \p l or NULL if \p l is empty.
+ **/
+static inline void *clist_head(clist *l)
+{
+ return (l->head.next != &l->head) ? l->head.next : NULL;
+}
+
+/**
+ * Return the last node on \p l or NULL if \p l is empty.
+ **/
+static inline void *clist_tail(clist *l)
+{
+ return (l->head.prev != &l->head) ? l->head.prev : NULL;
+}
+
+/**
+ * Find the next node to \p n or NULL if \p n is the last one.
+ **/
+static inline void *clist_next(clist *l, cnode *n)
+{
+ return (n->next != &l->head) ? (void *) n->next : NULL;
+}
+
+/**
+ * Find the previous node to \p n or NULL if \p n is the first one.
+ **/
+static inline void *clist_prev(clist *l, cnode *n)
+{
+ return (n->prev != &l->head) ? (void *) n->prev : NULL;
+}
+
+/**
+ * Return a non-zero value iff \p l is empty.
+ **/
+static inline int clist_empty(clist *l)
+{
+ return (l->head.next == &l->head);
+}
+
+/**
+ * Loop over all nodes in the \ref list and perform the next C statement on them. The current node is stored in \p n which must be defined before as pointer to any type.
+ * The list should not be changed during this loop command.
+ **/
+#define CLIST_WALK(n,list) for(n=(void*)(list).head.next; (cnode*)(n) != &(list).head; n=(void*)((cnode*)(n))->next)
+
+/**
+ * Same as \ref CLIST_WALK(), but allows removal of the current node. This macro requires one more variable to store some temporary pointers.
+ **/
+#define CLIST_WALK_DELSAFE(n,list,tmp) for(n=(void*)(list).head.next; tmp=(void*)((cnode*)(n))->next, (cnode*)(n) != &(list).head; n=(void*)tmp)
+
+/**
+ * Same as \ref CLIST_WALK(), but it defines the variable for the current node in place. \p type should be a pointer type.
+ **/
+#define CLIST_FOR_EACH(type,n,list) for(type n=(void*)(list).head.next; (cnode*)(n) != &(list).head; n=(void*)((cnode*)(n))->next)
+
+/**
+ * Same as \ref CLIST_WALK_DELSAFE(), but it defines the variable for the current node in place. \p type should be a pointer type. The temporary variable must be still known before.
+ **/
+#define CLIST_FOR_EACH_DELSAFE(type,n,list,tmp) for(type n=(void*)(list).head.next; tmp=(void*)((cnode*)(n))->next, (cnode*)(n) != &(list).head; n=(void*)tmp)
+
+/**
+ * Reversed version of \ref CLIST_FOR_EACH().
+ **/
+#define CLIST_FOR_EACH_BACKWARDS(type,n,list) for(type n=(void*)(list).head.prev; (cnode*)(n) != &(list).head; n=(void*)((cnode*)(n))->prev)
+
+/**
+ * Insert a new node just after the node \p after. To insert at the head of the list, use \ref clist_add_head() instead.
+ **/
+static inline void clist_insert_after(cnode *what, cnode *after)
+{
+ cnode *before = after->next;
+ what->next = before;
+ what->prev = after;
+ before->prev = what;
+ after->next = what;
+}
+
+/**
+ * Insert a new node just before the node \p before. To insert at the tail of the list, use \ref clist_add_tail() instead.
+ **/
+static inline void clist_insert_before(cnode *what, cnode *before)
+{
+ cnode *after = before->prev;
+ what->next = before;
+ what->prev = after;
+ before->prev = what;
+ after->next = what;
+}
+
+/**
+ * Insert a new node in front of all other nodes.
+ **/
+static inline void clist_add_head(clist *l, cnode *n)
+{
+ clist_insert_after(n, &l->head);
+}
+
+/**
+ * Insert a new node after all other nodes.
+ **/
+static inline void clist_add_tail(clist *l, cnode *n)
+{
+ clist_insert_before(n, &l->head);
+}
+
+/**
+ * Remove node \p n.
+ **/
+static inline void clist_remove(cnode *n)
+{
+ cnode *before = n->prev;
+ cnode *after = n->next;
+ before->next = after;
+ after->prev = before;
+}
+
+/**
+ * Remove the first node in \p l, if it exists. Return the pointer to that node or NULL.
+ **/
+static inline void *clist_remove_head(clist *l)
+{
+ cnode *n = clist_head(l);
+ if (n)
+ clist_remove(n);
+ return n;
+}
+
+/**
+ * Remove the last node in \p l, if it exists. Return the pointer to that node or NULL.
+ **/
+static inline void *clist_remove_tail(clist *l)
+{
+ cnode *n = clist_tail(l);
+ if (n)
+ clist_remove(n);
+ return n;
+}
+
+/**
+ * Merge two lists by inserting the list \p what just after the node \p after in a different list.
+ * The first list is then cleared.
+ **/
+static inline void clist_insert_list_after(clist *what, cnode *after)
+{
+ if (!clist_empty(what))
+ {
+ cnode *w = &what->head;
+ w->prev->next = after->next;
+ after->next->prev = w->prev;
+ w->next->prev = after;
+ after->next = w->next;
+ clist_init(what);
+ }
+}
+
+/**
+ * Move all items from a source list to a destination list. The source list
+ * becomes empty, the original contents of the destination list are destroyed.
+ **/
+static inline void clist_move(clist *to, clist *from)
+{
+ clist_init(to);
+ clist_insert_list_after(from, &to->head);
+ clist_init(from);
+}
+
+/**
+ * Compute the number of nodes in \p l. Beware of linear time complexity.
+ **/
+static inline unsigned int clist_size(clist *l)
+{
+ unsigned int i = 0;
+ CLIST_FOR_EACH(cnode *, n, *l)
+ i++;
+ return i;
+}
+
+/**
+ * Remove a node \p n and mark it as unlinked by setting the previous and next pointers to NULL.
+ **/
+static inline void clist_unlink(cnode *n)
+{
+ clist_remove(n);
+ n->prev = n->next = NULL;
+}
+
+/**
+ * Remove the first node on \p l and mark it as unlinked.
+ * Return the pointer to that node or NULL.
+ **/
+static inline void *clist_unlink_head(clist *l)
+{
+ cnode *n = clist_head(l);
+ if (n)
+ clist_unlink(n);
+ return n;
+}
+
+/**
+ * Remove the last node on \p l and mark it as unlinked.
+ * Return the pointer to that node or NULL.
+ **/
+static inline void *clist_unlink_tail(clist *l)
+{
+ cnode *n = clist_tail(l);
+ if (n)
+ clist_unlink(n);
+ return n;
+}
+
+/**
+ * Check if a node is linked a list. Unlinked nodes are recognized by having their
+ * previous and next pointers equal to NULL. Returns 0 or 1.
+ *
+ * Nodes initialized to all zeroes are unlinked, inserting a node anywhere in a list
+ * makes it linked. Normal removal functions like \ref clist_remove() do not mark nodes
+ * as unlinked, you need to call \ref clist_unlink() instead.
+ **/
+static inline int clist_is_linked(cnode *n)
+{
+ return !!n->next;
+}
+
+#endif
diff --git a/src/libdnssec/nsec.h b/src/libdnssec/nsec.h
new file mode 100644
index 0000000..ce69752
--- /dev/null
+++ b/src/libdnssec/nsec.h
@@ -0,0 +1,214 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \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.
+ *
+ * Example of NSEC3 hash computation:
+ *
+ * ~~~~~ {.c}
+ *
+ * int result;
+ *
+ * // NSEC3 parameters for hashing
+ * nssec_nsec3_params_t params = {
+ * .algorithm = DNSSEC_NSEC3_ALGORITHM_SHA1,
+ * .flags = 0,
+ * .iterations = 10,
+ * .salt = {
+ * .size = 4,
+ * .data = (uint8_t *){ 0xc0, 0x1d, 0xca, 0xfe }
+ * }
+ * };
+ *
+ * // domain name (in wire format)
+ * uint8_t *dname = "\0x08""knot-dns""\0x02""cz";
+ *
+ * // resulting hash
+ * dnssec_binary_t hash = { 0 };
+ *
+ * result = dnssec_nsec3_hash(&dname, &params, &hash);
+ * if (result != DNSSEC_EOK) {
+ * return result;
+ * }
+ *
+ * assert(hash.size == 20);
+ * // hash.data contains binary data, which encoded in Base32 would be:
+ * // 7PTVGE7QV67EM61ROS9238P5RAKR2DM7
+ *
+ * dnssec_binary_free(&hash);
+ *
+ * ~~~~~
+ *
+ * Example of NSEC/NSEC3 bitmap construction.
+ *
+ * ~~~~~ {.c}
+ *
+ * int result;
+ * dnssec_nsec_bitmap_t *ctx;
+ * dnssec_binary_t bitmap;
+ *
+ * // create encoding context
+ * ctx = dnssec_nsec_bitmap_new();
+ * if (ctx == NULL) {
+ * return KNOT_ENOMEM;
+ * }
+ *
+ * // add resource records into the bitmap
+ * dnssec_nsec_bitmap_add(ctx, 1); // A RR type
+ * dnssec_nsec_bitmap_add(ctx, 28); // AAAA RR type
+ *
+ * // allocate space for the encoded bitmap
+ * size_t size = dnssec_nsec_bitmap_size(ctx);
+ * result = dnssec_binary_alloc(&bitmap, size);
+ * if (result != DNSSEC_EOK) {
+ * dnssec_nsec_bitmap_free(ctx);
+ * return result;
+ * }
+ *
+ * // write the encoded bitmap and free the context
+ * dnssec_nsec_bitmap_write(ctx, &bitmap);
+ * dnssec_nsec_bitmap_free(ctx);
+ *
+ * // use the bitmap ...
+ *
+ * dnssec_binary_free(&bitmap);
+ * ~~~~~
+ *
+ * @{
+ */
+
+#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 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..001613a
--- /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 <http://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..897320f
--- /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 <http://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..bb6084a
--- /dev/null
+++ b/src/libdnssec/nsec/nsec.c
@@ -0,0 +1,116 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include "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;
+}
diff --git a/src/libdnssec/p11/p11.c b/src/libdnssec/p11/p11.c
new file mode 100644
index 0000000..07f34f2
--- /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 <http://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..a0f9969
--- /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 <http://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);
+
+/*!
+ * Clenaup PKCS11 global context.
+ *
+ * Should be called when the library is deinitialized to prevent memory leaks.
+ */
+void p11_cleanup(void);
diff --git a/src/libdnssec/random.c b/src/libdnssec/random.c
new file mode 100644
index 0000000..30b0c2d
--- /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 <http://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..0886e51
--- /dev/null
+++ b/src/libdnssec/random.h
@@ -0,0 +1,85 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \file
+ *
+ * \addtogroup random
+ *
+ * \brief Pseudo-random number generating API.
+ *
+ * The module provides generating of pseudo-random numbers and buffers.
+ *
+ * Example:
+ *
+ * ~~~
+ *
+ * uint16_t transaction_id = dnssec_random_uint16_t();
+ *
+ * ~~~
+ *
+ * @{
+ */
+
+#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, DNSEC_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, DNSEC_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);
+/*! \endcond */
+
+/*! @} */
diff --git a/src/libdnssec/shared/bignum.c b/src/libdnssec/shared/bignum.c
new file mode 100644
index 0000000..921a1e6
--- /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 <http://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..4186a63
--- /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 <http://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..78ccff0
--- /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 <http://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..51605f6
--- /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 <http://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..82adeb7
--- /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 <http://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/fs.c b/src/libdnssec/shared/fs.c
new file mode 100644
index 0000000..10c25d9
--- /dev/null
+++ b/src/libdnssec/shared/fs.c
@@ -0,0 +1,47 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include <stdbool.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+#include "libdnssec/error.h"
+
+int fs_mkdir(const char *path, mode_t mode, bool ignore_existing)
+{
+ if (mkdir(path, mode) == 0) {
+ return DNSSEC_EOK;
+ }
+
+ if (!ignore_existing || errno != EEXIST) {
+ return dnssec_errno_to_error(errno);
+ }
+
+ assert(errno == EEXIST);
+
+ struct stat st = { 0 };
+ if (stat(path, &st) != 0) {
+ return dnssec_errno_to_error(errno);
+ }
+
+ if (!S_ISDIR(st.st_mode)) {
+ return dnssec_errno_to_error(ENOTDIR);
+ }
+
+ return DNSSEC_EOK;
+}
diff --git a/src/libdnssec/shared/fs.h b/src/libdnssec/shared/fs.h
new file mode 100644
index 0000000..542c87b
--- /dev/null
+++ b/src/libdnssec/shared/fs.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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <stdbool.h>
+#include <sys/types.h>
+
+/*!
+ * Equivalent to mkdir(2), can succeed if the directory already exists.
+ */
+int fs_mkdir(const char *path, mode_t mode, bool ignore_existing);
diff --git a/src/libdnssec/shared/hex.c b/src/libdnssec/shared/hex.c
new file mode 100644
index 0000000..03c1491
--- /dev/null
+++ b/src/libdnssec/shared/hex.c
@@ -0,0 +1,167 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include <string.h>
+#include <stdbool.h>
+
+#include "libdnssec/binary.h"
+#include "libdnssec/error.h"
+
+#include "contrib/ctype.h"
+
+/* -- binary to hex -------------------------------------------------------- */
+
+static const char BIN_TO_HEX[] = { '0', '1', '2', '3', '4', '5', '6', '7',
+ '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' };
+
+int bin_to_hex_static(const dnssec_binary_t *bin, dnssec_binary_t *hex)
+{
+ if (!bin || !hex) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (bin->size * 2 != hex->size) {
+ return DNSSEC_EINVAL;
+ }
+
+ for (size_t i = 0; i < bin->size; i++) {
+ hex->data[2*i] = BIN_TO_HEX[bin->data[i] >> 4];
+ hex->data[2*i+1] = BIN_TO_HEX[bin->data[i] & 0x0f];
+ }
+
+ return DNSSEC_EOK;
+}
+
+int bin_to_hex(const dnssec_binary_t *bin, char **hex_ptr)
+{
+ if (!bin || !hex_ptr) {
+ return DNSSEC_EINVAL;
+ }
+
+ size_t hex_size = bin->size * 2;
+ char *hex = malloc(hex_size + 1);
+ if (!hex) {
+ return DNSSEC_ENOMEM;
+ }
+
+ dnssec_binary_t hex_bin = { .data = (uint8_t *)hex, .size = hex_size };
+ bin_to_hex_static(bin, &hex_bin);
+ hex[hex_size] = '\0';
+
+ *hex_ptr = hex;
+
+ return DNSSEC_EOK;
+}
+
+/* -- hex to binary -------------------------------------------------------- */
+
+/*!
+ * Convert HEX character to numeric value (assumes valid and lowercase input).
+ */
+static uint8_t hex_to_number(const char hex)
+{
+ if (hex >= '0' && hex <= '9') {
+ return hex - '0';
+ } else if (hex >= 'a' && hex <= 'f') {
+ return hex - 'a' + 10;
+ } else {
+ assert(hex >= 'A' && hex <= 'F');
+ return hex - 'A' + 10;
+ }
+}
+
+/*!
+ * Check if the input string has valid size and contains valid characters.
+ */
+static bool hex_valid_input(const dnssec_binary_t *hex)
+{
+ assert(hex);
+
+ if (hex->size % 2 != 0) {
+ return false;
+ }
+
+ for (int i = 0; i < hex->size; i++) {
+ if (!is_xdigit(hex->data[i])) {
+ return false;
+ }
+ }
+
+ return true;
+}
+
+/*!
+ * Perform hex to bin conversion without checking the validity.
+ */
+static void hex_to_bin_convert(const dnssec_binary_t *hex, dnssec_binary_t *bin)
+{
+ assert(hex);
+ assert(bin);
+
+ for (size_t i = 0; i < bin->size; i++) {
+ uint8_t high = hex_to_number(hex->data[2 * i]);
+ uint8_t low = hex_to_number(hex->data[2 * i + 1]);
+ bin->data[i] = high << 4 | low;
+ }
+}
+
+int hex_to_bin_static(const dnssec_binary_t *hex, dnssec_binary_t *bin)
+{
+ if (!hex || !bin) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (hex->size / 2 != bin->size) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (!hex_valid_input(hex)) {
+ return DNSSEC_MALFORMED_DATA;
+ }
+
+ hex_to_bin_convert(hex, bin);
+
+ return DNSSEC_EOK;
+}
+
+int hex_to_bin(const char *hex_str, dnssec_binary_t *bin)
+{
+ if (!hex_str || !bin) {
+ return DNSSEC_EINVAL;
+ }
+
+ dnssec_binary_t hex = { .data = (uint8_t *)hex_str, .size = strlen(hex_str) };
+ if (!hex_valid_input(&hex)) {
+ return DNSSEC_MALFORMED_DATA;
+ }
+
+ size_t bin_size = hex.size / 2;
+ if (bin_size == 0) {
+ bin->size = 0;
+ bin->data = NULL;
+ return DNSSEC_EOK;
+ }
+
+ int result = dnssec_binary_alloc(bin, bin_size);
+ if (result != DNSSEC_EOK) {
+ return result;
+ }
+
+ hex_to_bin_static(&hex, bin);
+
+ return DNSSEC_EOK;
+}
diff --git a/src/libdnssec/shared/hex.h b/src/libdnssec/shared/hex.h
new file mode 100644
index 0000000..5a47cf4
--- /dev/null
+++ b/src/libdnssec/shared/hex.h
@@ -0,0 +1,39 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include "libdnssec/binary.h"
+
+/*!
+ * Convert binary data to preallocated hexadecimal string.
+ */
+int bin_to_hex_static(const dnssec_binary_t *bin, dnssec_binary_t *hex);
+
+/**
+ * Convert binary data to hexadecimal string.
+ */
+int bin_to_hex(const dnssec_binary_t *bin, char **hex_ptr);
+
+/*!
+ * Convert hex encoded string to preallocated binary data.
+ */
+int hex_to_bin_static(const dnssec_binary_t *hex, dnssec_binary_t *bin);
+
+/*!
+ * Convert hex encoded string to binary data.
+ */
+int hex_to_bin(const char *hex, dnssec_binary_t *bin);
diff --git a/src/libdnssec/shared/keyid_gnutls.c b/src/libdnssec/shared/keyid_gnutls.c
new file mode 100644
index 0000000..4fde08e
--- /dev/null
+++ b/src/libdnssec/shared/keyid_gnutls.c
@@ -0,0 +1,94 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include <gnutls/abstract.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/x509.h>
+#include <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"
+#include "libdnssec/shared/hex.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 = alloca(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;
+ }
+
+ return bin_to_hex(&bin, id);
+}
+
+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..27ee4cd
--- /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 <http://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/pem.c b/src/libdnssec/shared/pem.c
new file mode 100644
index 0000000..0e5ba00
--- /dev/null
+++ b/src/libdnssec/shared/pem.c
@@ -0,0 +1,198 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <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/keyid.h"
+#include "libdnssec/shared/keyid_gnutls.h"
+#include "libdnssec/shared/pem.h"
+#include "libdnssec/shared/shared.h"
+
+/* -- internal API --------------------------------------------------------- */
+
+/*!
+ * Create GnuTLS X.509 private key from unencrypted PEM data.
+ */
+int pem_x509(const dnssec_binary_t *pem, gnutls_x509_privkey_t *key)
+{
+ assert(pem);
+ assert(key);
+
+ 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;
+}
+
+/*!
+ * Create GnuTLS private key from unencrypted PEM data.
+ */
+int pem_privkey(const dnssec_binary_t *pem, gnutls_privkey_t *key)
+{
+ assert(pem);
+ assert(key);
+
+ gnutls_x509_privkey_t key_x509 = NULL;
+ int r = pem_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;
+}
+
+/*!
+ * Generate new key and export it in the PEM format.
+ */
+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 = 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;
+}
+
+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);
+}
+
+/*!
+ * Export GnuTLS X.509 private key to PEM binary.
+ */
+int pem_from_x509(gnutls_x509_privkey_t key, dnssec_binary_t *pem)
+{
+ assert(key);
+ assert(pem);
+
+ 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;
+}
+
+/*!
+ * Get key ID of a private key in PEM format.
+ */
+int pem_keyid(const dnssec_binary_t *pem, char **id)
+{
+ assert(pem && pem->size > 0 && pem->data);
+ assert(id);
+
+ _cleanup_x509_privkey_ gnutls_x509_privkey_t key = NULL;
+ int r = pem_x509(pem, &key);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ return keyid_x509_hex(key, id);
+}
diff --git a/src/libdnssec/shared/pem.h b/src/libdnssec/shared/pem.h
new file mode 100644
index 0000000..c96065b
--- /dev/null
+++ b/src/libdnssec/shared/pem.h
@@ -0,0 +1,74 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <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 pem_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 pem_privkey(const dnssec_binary_t *pem, gnutls_privkey_t *key);
+
+/*!
+ * Generate a private key and export it in the PEM format.
+ *
+ * \param[in] algorithm Algorithm to be used.
+ * \param[in] bits Size of the key to be generated.
+ * \param[out] pem Generated key in unencrypted PEM format.
+ * \param[out] id Key ID of the generated key.
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int pem_generate(gnutls_pk_algorithm_t algorithm, unsigned bits,
+ dnssec_binary_t *pem, char **id);
+
+/*!
+ * 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 pem_from_x509(gnutls_x509_privkey_t key, dnssec_binary_t *pem);
+
+/*!
+ * Get key ID of a private key in PEM format.
+ *
+ * \param[in] pem Key in unencrypted PEM format.
+ * \param[out] id ID of the key.
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int pem_keyid(const dnssec_binary_t *pem, char **id);
diff --git a/src/libdnssec/shared/shared.h b/src/libdnssec/shared/shared.h
new file mode 100644
index 0000000..1f55033
--- /dev/null
+++ b/src/libdnssec/shared/shared.h
@@ -0,0 +1,129 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#include <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"
+
+#define _public_ __attribute__((visibility("default")))
+#define _hidden_ __attribute__((visibility("hidden")))
+
+#define _unused_ __attribute__((unused))
+
+/**
+ * Macro to clear a structure of known size.
+ *
+ * \param pointer Pointer to the structure.
+ */
+#define clear_struct(pointer) memset((pointer), '\0', sizeof(*(pointer)))
+
+#define streq(one, two) (strcmp((one), (two)) == 0)
+
+/* -- cleanup macros ------------------------------------------------------- */
+
+#define _cleanup_(var) __attribute__((cleanup(var)))
+
+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..4313533
--- /dev/null
+++ b/src/libdnssec/sign.h
@@ -0,0 +1,137 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \file
+ *
+ * \addtogroup sign
+ *
+ * \brief DNSSEC signing API
+ *
+ * The module provides the low level DNSSEC signing and verification.
+ *
+ * Example of signature validation:
+ *
+ * ~~~~~ {.c}
+ *
+ * dnssec_key_t *dnskey = // ... ;
+ * dnssec_binary_t *rrsig_header = // ... ;
+ * dnssec_binary_t *covered_rdata = // ... ;
+ * dnssec_binary_t *signature = // ... ;
+ *
+ * int result;
+ * dnssec_sign_ctx_t *ctx = NULL;
+ *
+ * result = dnssec_sign_new(&ctx, dnskey);
+ * if (result != DNSSEC_EOK) {
+ * return result;
+ * }
+ *
+ * dnssec_sign_add(ctx, rrsig_header);
+ * dnssec_sign_add(ctx, covered_rdata);
+ *
+ * result = dnssec_sign_verify(ctx, signature);
+ * if (result == DNSSEC_EOK) {
+ * // valid signature
+ * } else if (result == DNSSEC_INVALID_SIGNATURE) {
+ * // invalid signature
+ * } else {
+ * // error
+ * }
+ *
+ * dnssec_sign_free(ctx);
+ *
+ * ~~~~~
+ *
+ *
+ * @{
+ */
+
+#pragma once
+
+#include <stdint.h>
+#include <stdlib.h>
+
+#include <libdnssec/binary.h>
+#include <libdnssec/key.h>
+
+struct dnssec_sign_ctx;
+
+/*!
+ * 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 signature Signature to be allocated and written.
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int dnssec_sign_write(dnssec_sign_ctx_t *ctx, dnssec_binary_t *signature);
+
+/*!
+ * Verify DNSSEC signature.
+ *
+ * \param ctx Signing context.
+ * \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, 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..4d79876
--- /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 <http://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..db8e910
--- /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 <http://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..16b4e64
--- /dev/null
+++ b/src/libdnssec/sign/sign.c
@@ -0,0 +1,408 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <assert.h>
+#include <stdlib.h>
+#include <string.h>
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+#include "contrib/macros.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"
+#include "libdnssec/contrib/vpool.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:
+ return GNUTLS_DIG_SHA512;
+ case DNSSEC_KEY_ALGORITHM_ED448:
+ default:
+ return GNUTLS_DIG_UNKNOWN;
+ }
+}
+#endif
+
+static gnutls_sign_algorithm_t get_sign_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_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;
+ case DNSSEC_KEY_ALGORITHM_ED25519:
+#ifdef HAVE_ED25519
+ return GNUTLS_SIGN_EDDSA_ED25519;
+#endif
+ case DNSSEC_KEY_ALGORITHM_ED448:
+#ifdef HAVE_ED448
+ return GNUTLS_SIGN_EDDSA_ED448;
+#endif
+ default:
+ return GNUTLS_SIGN_UNKNOWN;
+ }
+}
+
+/* -- public API ---------------------------------------------------------- */
+
+_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;
+ }
+
+ ctx->sign_algorithm = get_sign_algorithm(key);
+ 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_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)
+ };
+
+ 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,
+ 0, &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,
+ 0, &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, const dnssec_binary_t *signature)
+{
+ if (!ctx || !signature) {
+ return DNSSEC_EINVAL;
+ }
+
+ 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..3edc8a3
--- /dev/null
+++ b/src/libdnssec/tsig.c
@@ -0,0 +1,242 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+
+#include <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 indentifiers.
+ */
+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 tsig)
+{
+ const algorithm_id_t *found = lookup_algorithm(match_id, (void *)tsig);
+ 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, (void *)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, (void *)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..e861982
--- /dev/null
+++ b/src/libdnssec/tsig.h
@@ -0,0 +1,207 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+*/
+/*!
+ * \file
+ *
+ * \addtogroup tsig
+ *
+ * \brief Low-level TSIG signing API.
+ *
+ * Example:
+ *
+ * ~~~~~ {.c}
+ *
+ * int result;
+ *
+ * dnssec_binary_t *covered = // ... ;
+ * dnssec_binary_t *signature = // ... ;
+ *
+ * // convert algorithm from textual representation
+ * dnssec_tsig_algorithm_t algorithm;
+ * algorithm = dnssec_tsig_algorithm_from_name("hmac-sha256");
+ * assert(algorithm == DNSSEC_TSIG_HMAC_SHA256);
+ *
+ * // get shared key
+ * dnssec_binary_t key = {
+ * .size = 4,
+ * .data = (uint8_t *) { 0x11, 0x22, 0x33, 0x44 }
+ * };
+ *
+ * // create computation context
+ * dnssec_tsig_ctx_t *ctx;
+ * result = dnssec_tsig_new(&ctx, algorithm, &key);
+ * if (result != DNSSEC_EOK) {
+ * return result;
+ * }
+ *
+ * // add data to be covered by the signature
+ * dnssec_tsig_add(ctx, covered);
+ *
+ * // compute the expected signature (MAC)
+ * size_t size = dnssec_tsig_size(ctx);
+ * dnssec_binary_t expected_signature = { 0 };
+ * result = dnssec_binary_alloc(&expected_signature, size);
+ * if (result != DNSSEC_EOK) {
+ * dnssec_tsig_free(ctx);
+ * return result;
+ * }
+ * dnssec_tsig_write(ctx, &expected_signature);
+ *
+ * // compare the signatures
+ * if (dnssec_binary_cmp(signature, &expected_signature) == 0) {
+ * // valid signature
+ * } else {
+ * // invalid signature
+ * }
+ *
+ * // cleanup
+ * dnssec_binary_free(&expected_signature);
+ * dnssec_tsig_free(ctx);
+ *
+ * ~~~~~
+ *
+ * @{
+ */
+
+#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..e2c362f
--- /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 <http://www.gnu.org/licenses/>.
+*/
+
+#pragma once
+
+#define DNSSEC_VERSION_MAJOR 2
+#define DNSSEC_VERSION_MINOR 7
+#define DNSSEC_VERSION_PATCH 0x06
+
+#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..de8752f
--- /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 <http://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))