summaryrefslogtreecommitdiffstats
path: root/src/libdnssec/key
diff options
context:
space:
mode:
Diffstat (limited to 'src/libdnssec/key')
-rw-r--r--src/libdnssec/key/algorithm.c180
-rw-r--r--src/libdnssec/key/algorithm.h30
-rw-r--r--src/libdnssec/key/convert.c375
-rw-r--r--src/libdnssec/key/convert.h44
-rw-r--r--src/libdnssec/key/dnskey.c91
-rw-r--r--src/libdnssec/key/dnskey.h46
-rw-r--r--src/libdnssec/key/ds.c128
-rw-r--r--src/libdnssec/key/internal.h35
-rw-r--r--src/libdnssec/key/key.c439
-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
13 files changed, 1686 insertions, 0 deletions
diff --git a/src/libdnssec/key/algorithm.c b/src/libdnssec/key/algorithm.c
new file mode 100644
index 0000000..a9bc3ee
--- /dev/null
+++ b/src/libdnssec/key/algorithm.c
@@ -0,0 +1,180 @@
+/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <gnutls/gnutls.h>
+
+#include "libdnssec/error.h"
+#include "libdnssec/key.h"
+#include "libdnssec/key/algorithm.h"
+#include "libdnssec/shared/shared.h"
+
+/* -- internal ------------------------------------------------------------- */
+
+struct limits {
+ unsigned min;
+ unsigned max;
+ unsigned def;
+ bool (*validate)(unsigned bits);
+};
+
+static const struct limits *get_limits(dnssec_key_algorithm_t algorithm)
+{
+ static const struct limits RSA = {
+ .min = 1024,
+ .max = 4096,
+ .def = 2048,
+ };
+
+ static const struct limits EC256 = {
+ .min = 256,
+ .max = 256,
+ .def = 256,
+ };
+
+ static const struct limits EC384 = {
+ .min = 384,
+ .max = 384,
+ .def = 384,
+ };
+
+ static const struct limits ED25519 = {
+ .min = 256,
+ .max = 256,
+ .def = 256,
+ };
+
+ static const struct limits ED448 = {
+ .min = 456,
+ .max = 456,
+ .def = 456,
+ };
+
+ switch (algorithm) {
+ case DNSSEC_KEY_ALGORITHM_RSA_SHA1:
+ case DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3:
+ case DNSSEC_KEY_ALGORITHM_RSA_SHA256:
+ case DNSSEC_KEY_ALGORITHM_RSA_SHA512:
+ return &RSA;
+ case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256:
+ return &EC256;
+ case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384:
+ return &EC384;
+ case DNSSEC_KEY_ALGORITHM_ED25519:
+ return &ED25519;
+ case DNSSEC_KEY_ALGORITHM_ED448:
+ return &ED448;
+ default:
+ return NULL;
+ }
+}
+
+/* -- internal API --------------------------------------------------------- */
+
+gnutls_pk_algorithm_t algorithm_to_gnutls(dnssec_key_algorithm_t dnssec)
+{
+ switch (dnssec) {
+ case DNSSEC_KEY_ALGORITHM_RSA_SHA1:
+ case DNSSEC_KEY_ALGORITHM_RSA_SHA1_NSEC3:
+ case DNSSEC_KEY_ALGORITHM_RSA_SHA256:
+ case DNSSEC_KEY_ALGORITHM_RSA_SHA512:
+ return GNUTLS_PK_RSA;
+ case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256:
+ case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384:
+ return GNUTLS_PK_EC;
+#ifdef HAVE_ED25519
+ case DNSSEC_KEY_ALGORITHM_ED25519:
+ return GNUTLS_PK_EDDSA_ED25519;
+#endif
+#ifdef HAVE_ED448
+ case DNSSEC_KEY_ALGORITHM_ED448:
+ return GNUTLS_PK_EDDSA_ED448;
+#endif
+ default:
+ return GNUTLS_PK_UNKNOWN;
+ }
+}
+
+/* -- public API ----------------------------------------------------------- */
+
+_public_
+bool dnssec_algorithm_reproducible(dnssec_key_algorithm_t algorithm, bool enabled)
+{
+ (void)enabled;
+ switch (algorithm) {
+ case DNSSEC_KEY_ALGORITHM_ED25519:
+ case DNSSEC_KEY_ALGORITHM_ED448:
+ return true; // those are always reproducible
+ case DNSSEC_KEY_ALGORITHM_ECDSA_P256_SHA256:
+ case DNSSEC_KEY_ALGORITHM_ECDSA_P384_SHA384:
+#ifdef HAVE_GNUTLS_REPRODUCIBLE
+ return enabled; // Reproducible only if GnuTLS supports && enabled
+#else
+ return false;
+#endif
+ default:
+ return false;
+ }
+}
+
+_public_
+int dnssec_algorithm_key_size_range(dnssec_key_algorithm_t algorithm,
+ unsigned *min_ptr, unsigned *max_ptr)
+{
+ if (!min_ptr && !max_ptr) {
+ return DNSSEC_EINVAL;
+ }
+
+ const struct limits *limits = get_limits(algorithm);
+ if (!limits) {
+ return DNSSEC_INVALID_KEY_ALGORITHM;
+ }
+
+ if (min_ptr) {
+ *min_ptr = limits->min;
+ }
+ if (max_ptr) {
+ *max_ptr = limits->max;
+ }
+
+ return DNSSEC_EOK;
+}
+
+_public_
+bool dnssec_algorithm_key_size_check(dnssec_key_algorithm_t algorithm,
+ unsigned bits)
+{
+ const struct limits *limits = get_limits(algorithm);
+ if (!limits) {
+ return false;
+ }
+
+ if (bits < limits->min || bits > limits->max) {
+ return false;
+ }
+
+ if (limits->validate && !limits->validate(bits)) {
+ return false;
+ }
+
+ return true;
+}
+
+_public_
+int dnssec_algorithm_key_size_default(dnssec_key_algorithm_t algorithm)
+{
+ const struct limits *limits = get_limits(algorithm);
+ return limits ? limits->def : 0;
+}
diff --git a/src/libdnssec/key/algorithm.h b/src/libdnssec/key/algorithm.h
new file mode 100644
index 0000000..4906675
--- /dev/null
+++ b/src/libdnssec/key/algorithm.h
@@ -0,0 +1,30 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gnutls/gnutls.h>
+
+#include "libdnssec/key.h"
+
+/*!
+ * Convert DNSKEY algorithm identifier to GnuTLS identifier.
+ *
+ * \param dnssec DNSSEC DNSKEY algorithm identifier.
+ *
+ * \return GnuTLS private key algorithm identifier, GNUTLS_PK_UNKNOWN on error.
+ */
+gnutls_pk_algorithm_t algorithm_to_gnutls(dnssec_key_algorithm_t dnssec);
diff --git a/src/libdnssec/key/convert.c b/src/libdnssec/key/convert.c
new file mode 100644
index 0000000..56168f7
--- /dev/null
+++ b/src/libdnssec/key/convert.c
@@ -0,0 +1,375 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <gnutls/abstract.h>
+#include <gnutls/gnutls.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "libdnssec/shared/bignum.h"
+#include "libdnssec/binary.h"
+#include "libdnssec/error.h"
+#include "libdnssec/key.h"
+#include "libdnssec/key/algorithm.h"
+#include "libdnssec/key/dnskey.h"
+#include "libdnssec/shared/shared.h"
+#include "libdnssec/shared/binary_wire.h"
+
+/* -- wrappers for GnuTLS types -------------------------------------------- */
+
+static size_t bignum_size_u_datum(const gnutls_datum_t *_bignum)
+{
+ const dnssec_binary_t bignum = binary_from_datum(_bignum);
+ return bignum_size_u(&bignum);
+}
+
+static void wire_write_bignum_datum(wire_ctx_t *ctx, size_t width,
+ const gnutls_datum_t *_bignum)
+{
+ const dnssec_binary_t bignum = binary_from_datum(_bignum);
+ bignum_write(ctx, width, &bignum);
+}
+
+static gnutls_datum_t wire_take_datum(wire_ctx_t *ctx, size_t count)
+{
+ gnutls_datum_t result = { .data = ctx->position, .size = count };
+ ctx->position += count;
+
+ return result;
+}
+
+/* -- DNSSEC to crypto ------------------------------------------------------*/
+
+/*!
+ * Convert RSA public key to DNSSEC format.
+ */
+static int rsa_pubkey_to_rdata(gnutls_pubkey_t key, dnssec_binary_t *rdata)
+{
+ assert(key);
+ assert(rdata);
+
+ _cleanup_datum_ gnutls_datum_t modulus = { 0 };
+ _cleanup_datum_ gnutls_datum_t exponent = { 0 };
+
+ int result = gnutls_pubkey_get_pk_rsa_raw(key, &modulus, &exponent);
+ if (result != GNUTLS_E_SUCCESS) {
+ return DNSSEC_KEY_EXPORT_ERROR;
+ }
+
+ size_t exponent_size = bignum_size_u_datum(&exponent);
+ if (exponent_size > UINT8_MAX) {
+ return DNSSEC_KEY_EXPORT_ERROR;
+ }
+
+ size_t modulus_size = bignum_size_u_datum(&modulus);
+
+ result = dnssec_binary_alloc(rdata, 1 + exponent_size + modulus_size);
+ if (result != DNSSEC_EOK) {
+ return result;
+ }
+
+ wire_ctx_t wire = binary_init(rdata);
+ wire_ctx_write_u8(&wire, exponent_size);
+ wire_write_bignum_datum(&wire, exponent_size, &exponent);
+ wire_write_bignum_datum(&wire, modulus_size, &modulus);
+ assert(wire_ctx_offset(&wire) == rdata->size);
+
+ return DNSSEC_EOK;
+}
+
+/*!
+ * Get point size for an ECDSA curve.
+ */
+static size_t ecdsa_curve_point_size(gnutls_ecc_curve_t curve)
+{
+ switch (curve) {
+ case GNUTLS_ECC_CURVE_SECP256R1: return 32;
+ case GNUTLS_ECC_CURVE_SECP384R1: return 48;
+ default: return 0;
+ }
+}
+
+#if defined(HAVE_ED25519) || defined(HAVE_ED448)
+static size_t eddsa_curve_point_size(gnutls_ecc_curve_t curve)
+{
+ switch (curve) {
+#ifdef HAVE_ED25519
+ case GNUTLS_ECC_CURVE_ED25519: return 32;
+#endif
+#ifdef HAVE_ED448
+ case GNUTLS_ECC_CURVE_ED448: return 57;
+#endif
+ default: return 0;
+ }
+}
+#endif
+
+/*!
+ * Convert ECDSA public key to DNSSEC format.
+ */
+static int ecdsa_pubkey_to_rdata(gnutls_pubkey_t key, dnssec_binary_t *rdata)
+{
+ assert(key);
+ assert(rdata);
+
+ _cleanup_datum_ gnutls_datum_t point_x = { 0 };
+ _cleanup_datum_ gnutls_datum_t point_y = { 0 };
+ gnutls_ecc_curve_t curve = { 0 };
+
+ int result = gnutls_pubkey_get_pk_ecc_raw(key, &curve, &point_x, &point_y);
+ if (result != GNUTLS_E_SUCCESS) {
+ return DNSSEC_KEY_EXPORT_ERROR;
+ }
+
+ size_t point_size = ecdsa_curve_point_size(curve);
+ if (point_size == 0) {
+ return DNSSEC_INVALID_PUBLIC_KEY;
+ }
+
+ result = dnssec_binary_alloc(rdata, 2 * point_size);
+ if (result != DNSSEC_EOK) {
+ return result;
+ }
+
+ wire_ctx_t wire = binary_init(rdata);
+ wire_write_bignum_datum(&wire, point_size, &point_x);
+ wire_write_bignum_datum(&wire, point_size, &point_y);
+ assert(wire_ctx_offset(&wire) == rdata->size);
+
+ return DNSSEC_EOK;
+}
+
+/*!
+ * Convert EDDSA public key to DNSSEC format.
+ */
+#if defined(HAVE_ED25519) || defined(HAVE_ED448)
+static int eddsa_pubkey_to_rdata(gnutls_pubkey_t key, dnssec_binary_t *rdata)
+{
+ assert(key);
+ assert(rdata);
+
+ _cleanup_datum_ gnutls_datum_t point_x = { 0 };
+ gnutls_ecc_curve_t curve = { 0 };
+
+ int result = gnutls_pubkey_get_pk_ecc_raw(key, &curve, &point_x, NULL);
+ if (result != GNUTLS_E_SUCCESS) {
+ return DNSSEC_KEY_EXPORT_ERROR;
+ }
+
+ size_t point_size = eddsa_curve_point_size(curve);
+ if (point_size == 0) {
+ return DNSSEC_INVALID_PUBLIC_KEY;
+ }
+
+ result = dnssec_binary_alloc(rdata, point_size);
+ if (result != DNSSEC_EOK) {
+ return result;
+ }
+
+ wire_ctx_t wire = binary_init(rdata);
+ wire_write_bignum_datum(&wire, point_size, &point_x);
+ assert(wire_ctx_offset(&wire) == rdata->size);
+
+ return DNSSEC_EOK;
+}
+#endif
+
+/* -- crypto to DNSSEC ------------------------------------------------------*/
+
+/*!
+ * Convert RSA key in DNSSEC format to crypto key.
+ */
+static int rsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t key)
+{
+ assert(rdata);
+ assert(key);
+
+ if (rdata->size == 0) {
+ return DNSSEC_INVALID_PUBLIC_KEY;
+ }
+
+ wire_ctx_t ctx = binary_init(rdata);
+
+ // parse public exponent
+
+ uint8_t exponent_size = wire_ctx_read_u8(&ctx);
+ if (exponent_size == 0 || wire_ctx_available(&ctx) < exponent_size) {
+ return DNSSEC_INVALID_PUBLIC_KEY;
+ }
+
+ gnutls_datum_t exponent = wire_take_datum(&ctx, exponent_size);
+
+ // parse modulus
+
+ size_t modulus_size = wire_ctx_available(&ctx);
+ if (modulus_size == 0) {
+ return DNSSEC_INVALID_PUBLIC_KEY;
+ }
+
+ gnutls_datum_t modulus = wire_take_datum(&ctx, modulus_size);
+
+ assert(wire_ctx_offset(&ctx) == rdata->size);
+
+ int result = gnutls_pubkey_import_rsa_raw(key, &modulus, &exponent);
+ if (result != GNUTLS_E_SUCCESS) {
+ return DNSSEC_KEY_IMPORT_ERROR;
+ }
+
+ return DNSSEC_EOK;
+}
+
+/*!
+ * Get ECDSA curve based on DNSKEY RDATA size.
+ */
+static gnutls_ecc_curve_t ecdsa_curve_from_rdata_size(size_t rdata_size)
+{
+ switch (rdata_size) {
+ case 64: return GNUTLS_ECC_CURVE_SECP256R1;
+ case 96: return GNUTLS_ECC_CURVE_SECP384R1;
+ default: return GNUTLS_ECC_CURVE_INVALID;
+ }
+}
+
+/*!
+ * Get EDDSA curve based on DNSKEY RDATA size.
+ */
+#if defined(HAVE_ED25519) || defined(HAVE_ED448)
+static gnutls_ecc_curve_t eddsa_curve_from_rdata_size(size_t rdata_size)
+{
+ switch (rdata_size) {
+#ifdef HAVE_ED25519
+ case 32: return GNUTLS_ECC_CURVE_ED25519;
+#endif
+#ifdef HAVE_ED448
+ case 57: return GNUTLS_ECC_CURVE_ED448;
+#endif
+ default: return GNUTLS_ECC_CURVE_INVALID;
+ }
+}
+#endif
+
+/*!
+ * Convert ECDSA key in DNSSEC format to crypto key.
+ */
+static int ecdsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t key)
+{
+ assert(rdata);
+ assert(key);
+
+ gnutls_ecc_curve_t curve = ecdsa_curve_from_rdata_size(rdata->size);
+ if (curve == GNUTLS_ECC_CURVE_INVALID) {
+ return DNSSEC_INVALID_PUBLIC_KEY;
+ }
+
+ // parse points
+
+ wire_ctx_t ctx = binary_init(rdata);
+
+ size_t point_size = wire_ctx_available(&ctx) / 2;
+ gnutls_datum_t point_x = wire_take_datum(&ctx, point_size);
+ gnutls_datum_t point_y = wire_take_datum(&ctx, point_size);
+ assert(wire_ctx_offset(&ctx) == rdata->size);
+
+ int result = gnutls_pubkey_import_ecc_raw(key, curve, &point_x, &point_y);
+ if (result != GNUTLS_E_SUCCESS) {
+ return DNSSEC_KEY_IMPORT_ERROR;
+ }
+
+ return DNSSEC_EOK;
+}
+
+/*!
+ * Convert EDDSA key in DNSSEC format to crypto key.
+ */
+#if defined(HAVE_ED25519) || defined(HAVE_ED448)
+static int eddsa_rdata_to_pubkey(const dnssec_binary_t *rdata, gnutls_pubkey_t key)
+{
+ assert(rdata);
+ assert(key);
+
+ gnutls_ecc_curve_t curve = eddsa_curve_from_rdata_size(rdata->size);
+ if (curve == GNUTLS_ECC_CURVE_INVALID) {
+ return DNSSEC_INVALID_PUBLIC_KEY;
+ }
+
+ wire_ctx_t ctx = binary_init(rdata);
+
+ size_t point_size = wire_ctx_available(&ctx);
+ gnutls_datum_t point_x = wire_take_datum(&ctx, point_size);
+ assert(wire_ctx_offset(&ctx) == rdata->size);
+
+ int result = gnutls_pubkey_import_ecc_raw(key, curve, &point_x, NULL);
+ if (result != GNUTLS_E_SUCCESS) {
+ return DNSSEC_KEY_IMPORT_ERROR;
+ }
+
+ return DNSSEC_EOK;
+}
+#endif
+
+/* -- internal API --------------------------------------------------------- */
+
+/*!
+ * Encode public key to the format used in DNSKEY RDATA.
+ */
+int convert_pubkey_to_dnskey(gnutls_pubkey_t key, dnssec_binary_t *rdata)
+{
+ assert(key);
+ assert(rdata);
+
+ int algorithm = gnutls_pubkey_get_pk_algorithm(key, NULL);
+ if (algorithm < 0) {
+ return DNSSEC_INVALID_PUBLIC_KEY;
+ }
+
+ switch ((gnutls_pk_algorithm_t)algorithm) {
+ case GNUTLS_PK_RSA: return rsa_pubkey_to_rdata(key, rdata);
+ case GNUTLS_PK_EC: return ecdsa_pubkey_to_rdata(key, rdata);
+#ifdef HAVE_ED25519
+ case GNUTLS_PK_EDDSA_ED25519: return eddsa_pubkey_to_rdata(key, rdata);
+#endif
+#ifdef HAVE_ED448
+ case GNUTLS_PK_EDDSA_ED448: return eddsa_pubkey_to_rdata(key, rdata);
+#endif
+ default: return DNSSEC_INVALID_KEY_ALGORITHM;
+ }
+}
+
+/*!
+ * Create public key from the format encoded in DNSKEY RDATA.
+ */
+int convert_dnskey_to_pubkey(uint8_t algorithm, const dnssec_binary_t *rdata,
+ gnutls_pubkey_t key)
+{
+ assert(rdata);
+ assert(key);
+
+ gnutls_pk_algorithm_t gnutls_alg = algorithm_to_gnutls(algorithm);
+
+ switch(gnutls_alg) {
+ case GNUTLS_PK_RSA: return rsa_rdata_to_pubkey(rdata, key);
+ case GNUTLS_PK_EC: return ecdsa_rdata_to_pubkey(rdata, key);
+#ifdef HAVE_ED25519
+ case GNUTLS_PK_EDDSA_ED25519: return eddsa_rdata_to_pubkey(rdata, key);
+#endif
+#ifdef HAVE_ED448
+ case GNUTLS_PK_EDDSA_ED448: return eddsa_rdata_to_pubkey(rdata, key);
+#endif
+ default: return DNSSEC_INVALID_KEY_ALGORITHM;
+ }
+}
diff --git a/src/libdnssec/key/convert.h b/src/libdnssec/key/convert.h
new file mode 100644
index 0000000..079a5f6
--- /dev/null
+++ b/src/libdnssec/key/convert.h
@@ -0,0 +1,44 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gnutls/abstract.h>
+
+#include "libdnssec/binary.h"
+#include "libdnssec/key.h"
+
+/*!
+ * Encode public key into the format used in DNSKEY RDATA.
+ *
+ * \param[in] key Public key to be encoded.
+ * \param[out] rdata Encoded key (allocated).
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int convert_pubkey_to_dnskey(gnutls_pubkey_t key, dnssec_binary_t *rdata);
+
+/*!
+ * Create public key from the format encoded in DNSKEY RDATA.
+ *
+ * \param[in] algorithm DNSSEC algorithm identification.
+ * \param[in] rdata Public key in DNSKEY RDATA format.
+ * \param[out] key GnuTLS public key (initialized).
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int convert_dnskey_to_pubkey(uint8_t algorithm, const dnssec_binary_t *rdata,
+ gnutls_pubkey_t key);
diff --git a/src/libdnssec/key/dnskey.c b/src/libdnssec/key/dnskey.c
new file mode 100644
index 0000000..6360700
--- /dev/null
+++ b/src/libdnssec/key/dnskey.c
@@ -0,0 +1,91 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "libdnssec/binary.h"
+#include "libdnssec/error.h"
+#include "libdnssec/key/dnskey.h"
+#include "libdnssec/key/convert.h"
+#include "libdnssec/shared/binary_wire.h"
+
+/* -- internal API --------------------------------------------------------- */
+
+/*!
+ * Update 'Public key' field of DNSKEY RDATA.
+ */
+int dnskey_rdata_set_pubkey(dnssec_binary_t *rdata, const dnssec_binary_t *pubkey)
+{
+ assert(rdata);
+ assert(pubkey);
+
+ size_t new_size = DNSKEY_RDATA_OFFSET_PUBKEY + pubkey->size;
+ int result = dnssec_binary_resize(rdata, new_size);
+ if (result != DNSSEC_EOK) {
+ return result;
+ }
+
+ wire_ctx_t wire = binary_init(rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PUBKEY);
+ binary_write(&wire, pubkey);
+ assert(wire_ctx_offset(&wire) == rdata->size);
+
+ return DNSSEC_EOK;
+}
+
+/*!
+ * Create a GnuTLS public key from DNSKEY RDATA.
+ *
+ * \param rdata DNSKEY RDATA.
+ * \param key_ptr Resulting public key.
+ */
+int dnskey_rdata_to_crypto_key(const dnssec_binary_t *rdata, gnutls_pubkey_t *key_ptr)
+{
+ assert(rdata);
+ assert(key_ptr);
+
+ uint8_t algorithm = 0, protocol = 0, flags_hi = 0;
+ dnssec_binary_t rdata_pubkey = { 0 };
+
+ wire_ctx_t wire = binary_init(rdata);
+
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_FLAGS);
+ flags_hi = wire_ctx_read_u8(&wire);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PROTOCOL);
+ protocol = wire_ctx_read_u8(&wire);
+ if (flags_hi != 0x1 || protocol != 0x3) {
+ return DNSSEC_INVALID_PUBLIC_KEY;
+ }
+
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_ALGORITHM);
+ algorithm = wire_ctx_read_u8(&wire);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PUBKEY);
+ binary_available(&wire, &rdata_pubkey);
+
+ gnutls_pubkey_t key = NULL;
+ int result = gnutls_pubkey_init(&key);
+ if (result != GNUTLS_E_SUCCESS) {
+ return DNSSEC_ENOMEM;
+ }
+
+ result = convert_dnskey_to_pubkey(algorithm, &rdata_pubkey, key);
+ if (result != DNSSEC_EOK) {
+ gnutls_pubkey_deinit(key);
+ return result;
+ }
+
+ *key_ptr = key;
+
+ return DNSSEC_EOK;
+}
diff --git a/src/libdnssec/key/dnskey.h b/src/libdnssec/key/dnskey.h
new file mode 100644
index 0000000..e765046
--- /dev/null
+++ b/src/libdnssec/key/dnskey.h
@@ -0,0 +1,46 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gnutls/abstract.h>
+
+#include "libdnssec/binary.h"
+#include "libdnssec/error.h"
+
+/*!
+ * DNSKEY RDATA fields offsets.
+ *
+ * \see RFC 4034 (section 2.1)
+ */
+enum dnskey_rdata_offsets {
+ DNSKEY_RDATA_OFFSET_FLAGS = 0,
+ DNSKEY_RDATA_OFFSET_PROTOCOL = 2,
+ DNSKEY_RDATA_OFFSET_ALGORITHM = 3,
+ DNSKEY_RDATA_OFFSET_PUBKEY = 4,
+};
+
+/*!
+ * Update 'Public key' field of DNSKEY RDATA.
+ */
+int dnskey_rdata_set_pubkey(dnssec_binary_t *rdata,
+ const dnssec_binary_t *pubkey);
+
+/*!
+ * Create a GnuTLS public key from DNSKEY RDATA.
+ */
+int dnskey_rdata_to_crypto_key(const dnssec_binary_t *rdata,
+ gnutls_pubkey_t *key_ptr);
diff --git a/src/libdnssec/key/ds.c b/src/libdnssec/key/ds.c
new file mode 100644
index 0000000..ad580ad
--- /dev/null
+++ b/src/libdnssec/key/ds.c
@@ -0,0 +1,128 @@
+/* Copyright (C) 2020 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include "libdnssec/binary.h"
+#include "libdnssec/error.h"
+#include "libdnssec/key.h"
+#include "libdnssec/key/internal.h"
+#include "libdnssec/shared/dname.h"
+#include "libdnssec/shared/shared.h"
+#include "libdnssec/shared/binary_wire.h"
+
+#include <gnutls/gnutls.h>
+#include <gnutls/crypto.h>
+
+/*!
+ * Convert DNSSEC DS digest algorithm to GnuTLS digest algorithm.
+ */
+static gnutls_digest_algorithm_t lookup_algorithm(dnssec_key_digest_t algorithm)
+{
+ switch (algorithm) {
+ case DNSSEC_KEY_DIGEST_SHA1: return GNUTLS_DIG_SHA1;
+ case DNSSEC_KEY_DIGEST_SHA256: return GNUTLS_DIG_SHA256;
+ case DNSSEC_KEY_DIGEST_SHA384: return GNUTLS_DIG_SHA384;
+ default:
+ return GNUTLS_DIG_UNKNOWN;
+ };
+}
+
+_public_
+bool dnssec_algorithm_digest_support(dnssec_key_digest_t algorithm)
+{
+ /* GnuTLS docs:
+ * > It is not possible to query for insecure hash algorithms directly
+ * > (only indirectly through the signature API).
+ * So let's query combining the hash with RSA.
+ */
+ gnutls_sign_algorithm_t rsa;
+ switch (algorithm) {
+ case DNSSEC_KEY_DIGEST_SHA1: rsa = GNUTLS_SIGN_RSA_SHA1; break;
+ case DNSSEC_KEY_DIGEST_SHA256: rsa = GNUTLS_SIGN_RSA_SHA256; break;
+ case DNSSEC_KEY_DIGEST_SHA384: rsa = GNUTLS_SIGN_RSA_SHA384; break;
+ default:
+ return false;
+ };
+ return gnutls_sign_is_secure(rsa);
+}
+
+static void wire_write_digest(wire_ctx_t *wire,
+ gnutls_hash_hd_t digest, int digest_size)
+{
+ assert(wire_ctx_available(wire) >= digest_size);
+ gnutls_hash_output(digest, wire->position);
+ wire->position += digest_size;
+}
+
+_public_
+int dnssec_key_create_ds(const dnssec_key_t *key,
+ dnssec_key_digest_t ds_algorithm,
+ dnssec_binary_t *rdata_ptr)
+{
+ if (!key || !rdata_ptr) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (!key->dname) {
+ return DNSSEC_INVALID_KEY_NAME;
+ }
+
+ if (!key->public_key){
+ return DNSSEC_INVALID_PUBLIC_KEY;
+ }
+
+ gnutls_digest_algorithm_t algorithm = lookup_algorithm(ds_algorithm);
+ if (algorithm == GNUTLS_DIG_UNKNOWN) {
+ return DNSSEC_INVALID_DS_ALGORITHM;
+ }
+
+ // compute DS hash
+
+ _cleanup_hash_ gnutls_hash_hd_t digest = NULL;
+ int r = gnutls_hash_init(&digest, algorithm);
+ if (r < 0) {
+ return DNSSEC_DS_HASHING_ERROR;
+ }
+
+ if (gnutls_hash(digest, key->dname, dname_length(key->dname)) != 0 ||
+ gnutls_hash(digest, key->rdata.data, key->rdata.size) != 0
+ ) {
+ return DNSSEC_DS_HASHING_ERROR;
+ }
+
+ // build DS RDATA
+
+ int digest_size = gnutls_hash_get_len(algorithm);
+ if (digest_size == 0) {
+ return DNSSEC_DS_HASHING_ERROR;
+ }
+
+ dnssec_binary_t rdata = { 0 };
+ r = dnssec_binary_alloc(&rdata, 4 + digest_size);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ wire_ctx_t wire = binary_init(&rdata);
+ wire_ctx_write_u16(&wire, dnssec_key_get_keytag(key));
+ wire_ctx_write_u8(&wire, dnssec_key_get_algorithm(key));
+ wire_ctx_write_u8(&wire, ds_algorithm);
+ wire_write_digest(&wire, digest, digest_size);
+ assert(wire_ctx_offset(&wire) == wire.size);
+
+ *rdata_ptr = rdata;
+
+ return DNSSEC_EOK;
+}
diff --git a/src/libdnssec/key/internal.h b/src/libdnssec/key/internal.h
new file mode 100644
index 0000000..550e454
--- /dev/null
+++ b/src/libdnssec/key/internal.h
@@ -0,0 +1,35 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gnutls/abstract.h>
+#include <stdint.h>
+
+#include "libdnssec/key.h"
+#include "libdnssec/shared/dname.h"
+
+/*!
+ * DNSSEC key.
+ */
+struct dnssec_key {
+ uint8_t *dname;
+ dnssec_binary_t rdata;
+
+ gnutls_pubkey_t public_key;
+ gnutls_privkey_t private_key;
+ unsigned bits;
+};
diff --git a/src/libdnssec/key/key.c b/src/libdnssec/key/key.c
new file mode 100644
index 0000000..f363167
--- /dev/null
+++ b/src/libdnssec/key/key.c
@@ -0,0 +1,439 @@
+/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <gnutls/abstract.h>
+#include <stdbool.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "libdnssec/binary.h"
+#include "libdnssec/error.h"
+#include "libdnssec/key.h"
+#include "libdnssec/key/algorithm.h"
+#include "libdnssec/key/convert.h"
+#include "libdnssec/key/dnskey.h"
+#include "libdnssec/key/internal.h"
+#include "libdnssec/shared/keyid_gnutls.h"
+#include "libdnssec/keystore.h"
+#include "libdnssec/keytag.h"
+#include "libdnssec/shared/shared.h"
+#include "libdnssec/shared/binary_wire.h"
+#include "contrib/wire_ctx.h"
+
+/*!
+ * Minimal size of DNSKEY RDATA.
+ */
+#define DNSKEY_RDATA_MIN_SIZE DNSKEY_RDATA_OFFSET_PUBKEY
+
+/*!
+ * RDATA template for newly allocated keys.
+ */
+static const dnssec_binary_t DNSKEY_RDATA_TEMPLATE = {
+ .size = 4,
+ .data = (uint8_t []) { 0x01, 0x00, 0x03, 0x00 }
+};
+
+/* -- key allocation ------------------------------------------------------- */
+
+_public_
+int dnssec_key_new(dnssec_key_t **key_ptr)
+{
+ if (!key_ptr) {
+ return DNSSEC_EINVAL;
+ }
+
+ dnssec_key_t *key = calloc(1, sizeof(*key));
+ if (!key) {
+ return DNSSEC_ENOMEM;
+ }
+
+ int r = dnssec_binary_dup(&DNSKEY_RDATA_TEMPLATE, &key->rdata);
+ if (r != DNSSEC_EOK) {
+ free(key);
+ return DNSSEC_ENOMEM;
+ }
+
+ *key_ptr = key;
+
+ return DNSSEC_EOK;
+}
+
+/*!
+ * Clear allocated fields inside the key structure, except RDATA.
+ */
+static void key_free_internals(dnssec_key_t *key)
+{
+ assert(key);
+
+ free(key->dname);
+ key->dname = NULL;
+
+ gnutls_privkey_deinit(key->private_key);
+ key->private_key = NULL;
+
+ gnutls_pubkey_deinit(key->public_key);
+ key->public_key = NULL;
+}
+
+_public_
+void dnssec_key_clear(dnssec_key_t *key)
+{
+ if (!key) {
+ return;
+ }
+
+ // reuse RDATA
+ dnssec_binary_t rdata = key->rdata;
+
+ // clear the structure
+ key_free_internals(key);
+ clear_struct(key);
+
+ // restore template RDATA (downsize, no need to realloc)
+ assert(rdata.size >= DNSKEY_RDATA_MIN_SIZE);
+ rdata.size = DNSKEY_RDATA_MIN_SIZE;
+ memmove(rdata.data, DNSKEY_RDATA_TEMPLATE.data, rdata.size);
+
+ key->rdata = rdata;
+}
+
+_public_
+void dnssec_key_free(dnssec_key_t *key)
+{
+ if (!key) {
+ return;
+ }
+
+ key_free_internals(key);
+ dnssec_binary_free(&key->rdata);
+
+ free(key);
+}
+
+_public_
+dnssec_key_t *dnssec_key_dup(const dnssec_key_t *key)
+{
+ if (!key) {
+ return NULL;
+ }
+
+ dnssec_key_t *dup = NULL;
+
+ if (dnssec_key_new(&dup) != DNSSEC_EOK ||
+ dnssec_key_set_dname(dup, key->dname) != DNSSEC_EOK ||
+ dnssec_key_set_rdata(dup, &key->rdata) != DNSSEC_EOK
+ ) {
+ dnssec_key_free(dup);
+ return NULL;
+ }
+
+ return dup;
+}
+
+/* -- freely modifiable attributes ----------------------------------------- */
+
+_public_
+const uint8_t *dnssec_key_get_dname(const dnssec_key_t *key)
+{
+ if (!key) {
+ return NULL;
+ }
+
+ return key->dname;
+}
+
+_public_
+int dnssec_key_set_dname(dnssec_key_t *key, const uint8_t *dname)
+{
+ if (!key) {
+ return DNSSEC_EINVAL;
+ }
+
+ uint8_t *copy = NULL;
+ if (dname) {
+ copy = dname_copy(dname);
+ if (!copy) {
+ return DNSSEC_ENOMEM;
+ }
+
+ dname_normalize(copy);
+ }
+
+ free(key->dname);
+ key->dname = copy;
+
+ return DNSSEC_EOK;
+}
+
+_public_
+uint16_t dnssec_key_get_flags(const dnssec_key_t *key)
+{
+ if (!key) {
+ return 0;
+ }
+
+ wire_ctx_t wire = binary_init(&key->rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_FLAGS);
+ return wire_ctx_read_u16(&wire);
+}
+
+_public_
+int dnssec_key_set_flags(dnssec_key_t *key, uint16_t flags)
+{
+ if (!key) {
+ return DNSSEC_EINVAL;
+ }
+
+ wire_ctx_t wire = binary_init(&key->rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_FLAGS);
+ wire_ctx_write_u16(&wire, flags);
+
+ return DNSSEC_EOK;
+}
+
+_public_
+uint8_t dnssec_key_get_protocol(const dnssec_key_t *key)
+{
+ if (!key) {
+ return 0;
+ }
+
+ wire_ctx_t wire = binary_init(&key->rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PROTOCOL);
+ return wire_ctx_read_u8(&wire);
+}
+
+_public_
+int dnssec_key_set_protocol(dnssec_key_t *key, uint8_t protocol)
+{
+ if (!key) {
+ return DNSSEC_EINVAL;
+ }
+
+ wire_ctx_t wire = binary_init(&key->rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PROTOCOL);
+ wire_ctx_write_u8(&wire, protocol);
+
+ return DNSSEC_EOK;
+}
+
+/* -- restricted attributes ------------------------------------------------ */
+
+_public_
+uint16_t dnssec_key_get_keytag(const dnssec_key_t *key)
+{
+ uint16_t keytag = 0;
+ if (dnssec_key_can_verify(key)) {
+ dnssec_keytag(&key->rdata, &keytag);
+ }
+
+ return keytag;
+}
+
+/*!
+ * Check if current public key algorithm matches with the new algorithm.
+ */
+static bool can_change_algorithm(dnssec_key_t *key, uint8_t algorithm)
+{
+ assert(key);
+
+ if (!key->public_key) {
+ return true;
+ }
+
+ gnutls_pk_algorithm_t update = algorithm_to_gnutls(algorithm);
+ if (update == GNUTLS_PK_UNKNOWN) {
+ return false;
+ }
+
+ int current = gnutls_pubkey_get_pk_algorithm(key->public_key, NULL);
+ assert(current >= 0);
+
+ return current == update;
+}
+
+_public_
+uint8_t dnssec_key_get_algorithm(const dnssec_key_t *key)
+{
+ if (!key) {
+ return 0;
+ }
+
+ wire_ctx_t wire = binary_init(&key->rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_ALGORITHM);
+ return wire_ctx_read_u8(&wire);
+}
+
+_public_
+int dnssec_key_set_algorithm(dnssec_key_t *key, uint8_t algorithm)
+{
+ if (!key) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (!can_change_algorithm(key, algorithm)) {
+ return DNSSEC_INVALID_KEY_ALGORITHM;
+ }
+
+ wire_ctx_t wire = binary_init(&key->rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_ALGORITHM);
+ wire_ctx_write_u8(&wire, algorithm);
+
+ return DNSSEC_EOK;
+}
+
+_public_
+int dnssec_key_get_pubkey(const dnssec_key_t *key, dnssec_binary_t *pubkey)
+{
+ if (!key || !pubkey) {
+ return DNSSEC_EINVAL;
+ }
+
+ wire_ctx_t wire = binary_init(&key->rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PUBKEY);
+ binary_available(&wire, pubkey);
+
+ return DNSSEC_EOK;
+}
+
+_public_
+int dnssec_key_set_pubkey(dnssec_key_t *key, const dnssec_binary_t *pubkey)
+{
+ if (!key || !pubkey || !pubkey->data) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (key->public_key) {
+ return DNSSEC_KEY_ALREADY_PRESENT;
+ }
+
+ if (dnssec_key_get_algorithm(key) == 0) {
+ return DNSSEC_INVALID_KEY_ALGORITHM;
+ }
+
+ int result = dnskey_rdata_set_pubkey(&key->rdata, pubkey);
+ if (result != DNSSEC_EOK) {
+ return result;
+ }
+
+ result = dnskey_rdata_to_crypto_key(&key->rdata, &key->public_key);
+ if (result != DNSSEC_EOK) {
+ key->rdata.size = DNSKEY_RDATA_OFFSET_PUBKEY; // downsize
+ return result;
+ }
+
+ return DNSSEC_EOK;
+}
+
+_public_
+unsigned dnssec_key_get_size(const dnssec_key_t *key)
+{
+ if (!key || !key->public_key) {
+ return 0;
+ }
+
+ unsigned bits = 0;
+ uint8_t algorithm = dnssec_key_get_algorithm(key);
+ switch (algorithm) {
+ case 13:
+ bits = 256;
+ break;
+ case 14:
+ bits = 384;
+ break;
+ case 15:
+ bits = 256;
+ break;
+ case 16:
+ bits = 456;
+ break;
+ default:
+ gnutls_pubkey_get_pk_algorithm(key->public_key, &bits);
+ }
+
+ return bits;
+}
+
+_public_
+int dnssec_key_get_keyid(const dnssec_key_t *key, char **id)
+{
+ if (!key || !id) {
+ return DNSSEC_EINVAL;
+ }
+
+ return keyid_pubkey_hex(key->public_key, id);
+}
+
+_public_
+int dnssec_key_get_rdata(const dnssec_key_t *key, dnssec_binary_t *rdata)
+{
+ if (!key || !rdata) {
+ return DNSSEC_EINVAL;
+ }
+
+ *rdata = key->rdata;
+
+ return DNSSEC_EOK;
+}
+
+_public_
+int dnssec_key_set_rdata(dnssec_key_t *key, const dnssec_binary_t *rdata)
+{
+ if (!key || !rdata || !rdata->data) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (rdata->size < DNSKEY_RDATA_MIN_SIZE) {
+ return DNSSEC_MALFORMED_DATA;
+ }
+
+ if (key->public_key) {
+ return DNSSEC_KEY_ALREADY_PRESENT;
+ }
+
+ gnutls_pubkey_t new_pubkey = NULL;
+ int result = dnskey_rdata_to_crypto_key(rdata, &new_pubkey);
+ if (result != DNSSEC_EOK) {
+ return result;
+ }
+
+ result = dnssec_binary_resize(&key->rdata, rdata->size);
+ if (result != DNSSEC_EOK) {
+ gnutls_pubkey_deinit(new_pubkey);
+ return result;
+ }
+
+ // commit result
+ memmove(key->rdata.data, rdata->data, rdata->size);
+ key->public_key = new_pubkey;
+
+ return DNSSEC_EOK;
+}
+
+/* -- key presence checking ------------------------------------------------ */
+
+_public_
+bool dnssec_key_can_sign(const dnssec_key_t *key)
+{
+ return key && key->private_key;
+}
+
+_public_
+bool dnssec_key_can_verify(const dnssec_key_t *key)
+{
+ return key && key->public_key;
+}
diff --git a/src/libdnssec/key/keytag.c b/src/libdnssec/key/keytag.c
new file mode 100644
index 0000000..a14353e
--- /dev/null
+++ b/src/libdnssec/key/keytag.c
@@ -0,0 +1,88 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <stdint.h>
+#include <string.h>
+
+#include "libdnssec/binary.h"
+#include "libdnssec/error.h"
+#include "libdnssec/key/dnskey.h"
+#include "libdnssec/shared/shared.h"
+
+/*!
+ * Compute keytag for RSA/MD5 key.
+ *
+ * \see RFC 2537 (section 2), RFC 4034 (appendix B.1)
+ */
+static uint16_t keytag_compat(const dnssec_binary_t *rdata)
+{
+ assert(rdata);
+ assert(rdata->data);
+
+ if (rdata->size < 9) { // in fact, the condition could be stricter
+ return 0;
+ }
+
+ uint8_t msb = rdata->data[rdata->size - 3];
+ uint8_t lsb = rdata->data[rdata->size - 2];
+
+ return (msb << 8) + lsb;
+}
+
+/*!
+ * Compute keytag for other than RSA/MD5 key.
+ *
+ * \see RFC 4034 (appendix B)
+ */
+static uint16_t keytag_current(const dnssec_binary_t *rdata)
+{
+ assert(rdata);
+ assert(rdata->data);
+
+ uint32_t ac = 0;
+ for (int i = 0; i < rdata->size; i++) {
+ ac += (i & 1) ? rdata->data[i] : rdata->data[i] << 8;
+ }
+
+ return (ac >> 16) + ac;
+}
+
+/* -- public API ----------------------------------------------------------- */
+
+/*!
+ * Compute keytag for a DNSSEC key.
+ */
+_public_
+int dnssec_keytag(const dnssec_binary_t *rdata, uint16_t *keytag)
+{
+ if (!rdata || !keytag) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (!rdata->data || rdata->size < DNSKEY_RDATA_OFFSET_PUBKEY) {
+ return DNSSEC_MALFORMED_DATA;
+ }
+
+ uint8_t algorithm = rdata->data[DNSKEY_RDATA_OFFSET_ALGORITHM];
+ if (algorithm == 1) {
+ *keytag = keytag_compat(rdata);
+ } else {
+ *keytag = keytag_current(rdata);
+ }
+
+ return DNSSEC_EOK;
+}
diff --git a/src/libdnssec/key/privkey.c b/src/libdnssec/key/privkey.c
new file mode 100644
index 0000000..abe968a
--- /dev/null
+++ b/src/libdnssec/key/privkey.c
@@ -0,0 +1,140 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <gnutls/abstract.h>
+#include <gnutls/gnutls.h>
+
+#include "libdnssec/binary.h"
+#include "libdnssec/error.h"
+#include "libdnssec/key/algorithm.h"
+#include "libdnssec/key/convert.h"
+#include "libdnssec/key/dnskey.h"
+#include "libdnssec/key/internal.h"
+#include "libdnssec/key/privkey.h"
+#include "libdnssec/shared/shared.h"
+#include "libdnssec/shared/binary_wire.h"
+
+/* -- internal functions --------------------------------------------------- */
+
+/*!
+ * Check if the algorithm number is valid for given DNSKEY.
+ */
+static bool valid_algorithm(dnssec_key_t *key, gnutls_privkey_t privkey)
+{
+ uint8_t current = dnssec_key_get_algorithm(key);
+ int gnu_algorithm = gnutls_privkey_get_pk_algorithm(privkey, NULL);
+
+ return (gnu_algorithm == algorithm_to_gnutls(current));
+}
+
+/*!
+ * Create GnuTLS public key from private key.
+ */
+static int public_from_private(gnutls_privkey_t privkey, gnutls_pubkey_t *pubkey)
+{
+ assert(privkey);
+ assert(pubkey);
+
+ gnutls_pubkey_t new_key = NULL;
+ int result = gnutls_pubkey_init(&new_key);
+ if (result != GNUTLS_E_SUCCESS) {
+ return DNSSEC_ENOMEM;
+ }
+
+ result = gnutls_pubkey_import_privkey(new_key, privkey, 0, 0);
+ if (result != GNUTLS_E_SUCCESS) {
+ gnutls_pubkey_deinit(new_key);
+ return DNSSEC_KEY_IMPORT_ERROR;
+ }
+
+ *pubkey = new_key;
+
+ return DNSSEC_EOK;
+}
+
+/*!
+ * Create public key (GnuTLS and DNSKEY RDATA) from a private key.
+ */
+static int create_public_key(gnutls_privkey_t privkey,
+ gnutls_pubkey_t *pubkey_ptr,
+ dnssec_binary_t *rdata)
+{
+ assert(privkey);
+ assert(pubkey_ptr);
+ assert(rdata);
+
+ // crypto public key
+
+ gnutls_pubkey_t pubkey = NULL;
+ int result = public_from_private(privkey, &pubkey);
+ if (result != DNSSEC_EOK) {
+ return result;
+ }
+
+ // dnssec public key
+
+ _cleanup_binary_ dnssec_binary_t rdata_pubkey = { 0 };
+ result = convert_pubkey_to_dnskey(pubkey, &rdata_pubkey);
+ if (result != DNSSEC_EOK) {
+ gnutls_pubkey_deinit(pubkey);
+ return result;
+ }
+
+ size_t rdata_size = DNSKEY_RDATA_OFFSET_PUBKEY + rdata_pubkey.size;
+ result = dnssec_binary_resize(rdata, rdata_size);
+ if (result != DNSSEC_EOK) {
+ gnutls_pubkey_deinit(pubkey);
+ return result;
+ }
+
+ // updated RDATA
+
+ wire_ctx_t wire = binary_init(rdata);
+ wire_ctx_set_offset(&wire, DNSKEY_RDATA_OFFSET_PUBKEY);
+ binary_write(&wire, &rdata_pubkey);
+ assert(wire_ctx_offset(&wire) == rdata->size);
+
+ *pubkey_ptr = pubkey;
+
+ return DNSSEC_EOK;
+}
+
+/* -- internal API --------------------------------------------------------- */
+
+/*!
+ * Load a private key into a DNSSEC key, create a public part if necessary.
+ */
+int key_set_private_key(dnssec_key_t *key, gnutls_privkey_t privkey)
+{
+ assert(key);
+ assert(privkey);
+ assert(key->private_key == NULL);
+
+ if (!valid_algorithm(key, privkey)) {
+ return DNSSEC_INVALID_KEY_ALGORITHM;
+ }
+
+ if (!key->public_key) {
+ int r = create_public_key(privkey, &key->public_key, &key->rdata);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+ }
+
+ key->private_key = privkey;
+
+ return DNSSEC_EOK;
+}
diff --git a/src/libdnssec/key/privkey.h b/src/libdnssec/key/privkey.h
new file mode 100644
index 0000000..8afe7c9
--- /dev/null
+++ b/src/libdnssec/key/privkey.h
@@ -0,0 +1,35 @@
+/* Copyright (C) 2018 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gnutls/abstract.h>
+
+#include "libdnssec/key.h"
+
+/*!
+ * Load a private key into a DNSSEC key, create a public part if necessary.
+ *
+ * If the public key is not loaded, at least an algorithm must be set.
+ *
+ * Updates private key, public key, RDATA, and key identifiers.
+ *
+ * \param key DNSSEC key to be updated.
+ * \param privkey Private key to be set.
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int key_set_private_key(dnssec_key_t *key, gnutls_privkey_t privkey);
diff --git a/src/libdnssec/key/simple.c b/src/libdnssec/key/simple.c
new file mode 100644
index 0000000..10126cc
--- /dev/null
+++ b/src/libdnssec/key/simple.c
@@ -0,0 +1,55 @@
+/* Copyright (C) 2019 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <gnutls/abstract.h>
+#include <gnutls/gnutls.h>
+
+#include "libdnssec/binary.h"
+#include "libdnssec/error.h"
+#include "libdnssec/key.h"
+#include "libdnssec/key/dnskey.h"
+#include "libdnssec/key/internal.h"
+#include "libdnssec/key/privkey.h"
+#include "libdnssec/pem.h"
+#include "libdnssec/shared/shared.h"
+
+/* -- public API ----------------------------------------------------------- */
+
+_public_
+int dnssec_key_load_pkcs8(dnssec_key_t *key, const dnssec_binary_t *pem)
+{
+ if (!key || !pem || !pem->data) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (dnssec_key_get_algorithm(key) == 0) {
+ return DNSSEC_INVALID_KEY_ALGORITHM;
+ }
+
+ gnutls_privkey_t privkey = NULL;
+ int r = dnssec_pem_to_privkey(pem, &privkey);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ r = key_set_private_key(key, privkey);
+ if (r != DNSSEC_EOK) {
+ gnutls_privkey_deinit(privkey);
+ return r;
+ }
+
+ return DNSSEC_EOK;
+}