summaryrefslogtreecommitdiffstats
path: root/src/libdnssec/keystore
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/libdnssec/keystore.h155
-rw-r--r--src/libdnssec/keystore/internal.h50
-rw-r--r--src/libdnssec/keystore/keystore.c186
-rw-r--r--src/libdnssec/keystore/pkcs11.c397
-rw-r--r--src/libdnssec/keystore/pkcs8.c497
5 files changed, 1285 insertions, 0 deletions
diff --git a/src/libdnssec/keystore.h b/src/libdnssec/keystore.h
new file mode 100644
index 0000000..8697935
--- /dev/null
+++ b/src/libdnssec/keystore.h
@@ -0,0 +1,155 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+/*!
+ * \file
+ *
+ * \addtogroup keystore
+ *
+ * \brief Private key store access.
+ *
+ * The module provides abstraction for private key store. Basically, PKCS #8
+ * and PKCS #11 interfaces are supported.
+ *
+ * PKCS #8 uses unencrypted PEM.
+ *
+ * PKCS #11 provides access Hardware Security Modules.
+ *
+ * @{
+ */
+
+#pragma once
+
+#include <libdnssec/binary.h>
+#include <libdnssec/key.h>
+
+struct dnssec_keystore;
+
+/*!
+ * DNSSEC private keys store.
+ */
+typedef struct dnssec_keystore dnssec_keystore_t;
+
+/*!
+ * Create default PKCS #8 private key store context.
+ *
+ * The default store maintains the private keys in one directory on the file
+ * system. The private keys are stored in unencrypted PEM format, named
+ * key-id.pem. The configuration string is a path to the directory.
+ *
+ * \param[out] store Opened key store.
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int dnssec_keystore_init_pkcs8(dnssec_keystore_t **store);
+
+/*!
+ * Crate new PKCS #11 private key store context.
+ *
+ * \param[out] store Opened key store.
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int dnssec_keystore_init_pkcs11(dnssec_keystore_t **store);
+
+/*!
+ * Deinitialize private key store context.
+ *
+ * \param store Key store to be deinitialized.
+ */
+int dnssec_keystore_deinit(dnssec_keystore_t *store);
+
+/*!
+ * Initialize new private key store.
+ */
+int dnssec_keystore_init(dnssec_keystore_t *store, const char *config);
+
+/*!
+ * Open private key store.
+ */
+int dnssec_keystore_open(dnssec_keystore_t *store, const char *config);
+
+/*!
+ * Close private key store.
+ *
+ * \param store Key store to be closed.
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int dnssec_keystore_close(dnssec_keystore_t *store);
+
+/*!
+ * Generate a new key in the key store.
+ *
+ * \param[in] store Key store.
+ * \param[in] algorithm Algorithm.
+ * \param[in] bits Bit length of the key to be generated.
+ * \param[in] label Optional key label for PKCS #11.
+ * \param[out] id_ptr ID of the generated key. Must be freed by the caller.
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int dnssec_keystore_generate(dnssec_keystore_t *store,
+ dnssec_key_algorithm_t algorithm,
+ unsigned bits, const char *label, char **id_ptr);
+
+/*!
+ * Import an existing key into the key store.
+ *
+ * \param[in] store Key store.
+ * \param[in] pem Private key material in PEM format.
+ * \param[out] id_ptr ID of the imported key. Must be freed by the caller.
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int dnssec_keystore_import(dnssec_keystore_t *store, const dnssec_binary_t *pem,
+ char **id_ptr);
+
+/*!
+ * Remove a private key from the key store.
+ *
+ * \param store Key store.
+ * \param id ID of the private key to be deleted.
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int dnssec_keystore_remove(dnssec_keystore_t *store, const char *id);
+
+/*!
+ * Export private key from the key store into a DNSSEC key.
+ *
+ * The key algorithm has to be set before calling this function.
+ *
+ * \param store Private key store.
+ * \param id ID of the key.
+ * \param key DNSSEC key to be initialized.
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int dnssec_keystore_get_private(dnssec_keystore_t *store, const char *id,
+ dnssec_key_t *key);
+
+/*!
+ * Import a DNSSEC private key into key store.
+ *
+ * \param store Key store.
+ * \param key DNSSEC key with a private key.
+ *
+ * \return Error code, DNSSEC_EOK if successful.
+ */
+int dnssec_keystore_set_private(dnssec_keystore_t *store, dnssec_key_t *key);
+
+/*! @} */
diff --git a/src/libdnssec/keystore/internal.h b/src/libdnssec/keystore/internal.h
new file mode 100644
index 0000000..5afa8ce
--- /dev/null
+++ b/src/libdnssec/keystore/internal.h
@@ -0,0 +1,50 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#pragma once
+
+#include <gnutls/gnutls.h>
+#include <gnutls/abstract.h>
+
+#include "libdnssec/binary.h"
+#include "libdnssec/key.h"
+#include "libdnssec/keystore.h"
+
+typedef struct keystore_functions {
+ // construction of internal context
+ int (*ctx_new)(void **ctx_ptr);
+ void (*ctx_free)(void *ctx);
+ // keystore init/open/close
+ int (*init)(void *ctx, const char *config);
+ int (*open)(void *ctx, const char *config);
+ int (*close)(void *ctx);
+ // keystore access
+ int (*generate_key)(void *ctx, gnutls_pk_algorithm_t algorithm,
+ unsigned bits, const char *label, char **id_ptr);
+ int (*import_key)(void *ctx, const dnssec_binary_t *pem, char **id_ptr);
+ int (*remove_key)(void *ctx, const char *id);
+ // private key access
+ int (*get_private)(void *ctx, const char *id, gnutls_privkey_t *key_ptr);
+ int (*set_private)(void *ctx, gnutls_privkey_t key);
+} keystore_functions_t;
+
+struct dnssec_keystore {
+ const keystore_functions_t *functions;
+ void *ctx;
+};
+
+int keystore_create(dnssec_keystore_t **store_ptr,
+ const keystore_functions_t *functions);
diff --git a/src/libdnssec/keystore/keystore.c b/src/libdnssec/keystore/keystore.c
new file mode 100644
index 0000000..f88dd14
--- /dev/null
+++ b/src/libdnssec/keystore/keystore.c
@@ -0,0 +1,186 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include "libdnssec/error.h"
+#include "libdnssec/key.h"
+#include "libdnssec/key/algorithm.h"
+#include "libdnssec/key/dnskey.h"
+#include "libdnssec/key/internal.h"
+#include "libdnssec/key/privkey.h"
+#include "libdnssec/keyid.h"
+#include "libdnssec/keystore.h"
+#include "libdnssec/keystore/internal.h"
+#include "libdnssec/shared/shared.h"
+
+/* -- internal API --------------------------------------------------------- */
+
+int keystore_create(dnssec_keystore_t **store_ptr,
+ const keystore_functions_t *functions)
+{
+ assert(store_ptr);
+ assert(functions);
+
+ dnssec_keystore_t *store = calloc(1, sizeof(*store));
+ if (!store) {
+ return DNSSEC_ENOMEM;
+ }
+
+ store->functions = functions;
+
+ int result = functions->ctx_new(&store->ctx);
+ if (result != DNSSEC_EOK) {
+ free(store);
+ return DNSSEC_ENOMEM;
+ }
+
+ *store_ptr = store;
+ return DNSSEC_EOK;
+}
+
+/* -- public API ----------------------------------------------------------- */
+
+_public_
+int dnssec_keystore_deinit(dnssec_keystore_t *store)
+{
+ if (!store) {
+ return DNSSEC_EINVAL;
+ }
+
+ dnssec_keystore_close(store);
+ store->functions->ctx_free(store->ctx);
+
+ free(store);
+
+ return DNSSEC_EOK;
+}
+
+_public_
+int dnssec_keystore_init(dnssec_keystore_t *store, const char *config)
+{
+ if (!store) {
+ return DNSSEC_EINVAL;
+ }
+
+ return store->functions->init(store->ctx, config);
+}
+
+_public_
+int dnssec_keystore_open(dnssec_keystore_t *store, const char *config)
+{
+ if (!store) {
+ return DNSSEC_EINVAL;
+ }
+
+ return store->functions->open(store->ctx, config);
+}
+
+_public_
+int dnssec_keystore_close(dnssec_keystore_t *store)
+{
+ if (!store) {
+ return DNSSEC_EINVAL;
+ }
+
+ return store->functions->close(store->ctx);
+}
+
+_public_
+int dnssec_keystore_generate(dnssec_keystore_t *store,
+ dnssec_key_algorithm_t _algorithm,
+ unsigned bits, const char *label, char **id_ptr)
+{
+ if (!store || !_algorithm || !id_ptr) {
+ return DNSSEC_EINVAL;
+ }
+
+ // prepare parameters
+
+ gnutls_pk_algorithm_t algorithm = algorithm_to_gnutls(_algorithm);
+ if (algorithm == GNUTLS_PK_UNKNOWN) {
+ return DNSSEC_INVALID_KEY_ALGORITHM;
+ }
+
+ if (!dnssec_algorithm_key_size_check(_algorithm, bits)) {
+ return DNSSEC_INVALID_KEY_SIZE;
+ }
+
+ return store->functions->generate_key(store->ctx, algorithm, bits, label, id_ptr);
+}
+
+_public_
+int dnssec_keystore_import(dnssec_keystore_t *store, const dnssec_binary_t *pem,
+ char **id_ptr)
+{
+ if (!store || !pem || !id_ptr) {
+ return DNSSEC_EINVAL;
+ }
+
+ return store->functions->import_key(store->ctx, pem, id_ptr);
+}
+
+_public_
+int dnssec_keystore_remove(dnssec_keystore_t *store, const char *id)
+{
+ if (!store || !id) {
+ return DNSSEC_EINVAL;
+ }
+
+ return store->functions->remove_key(store->ctx, id);
+}
+
+_public_
+int dnssec_keystore_get_private(dnssec_keystore_t *store, const char *id,
+ dnssec_key_t *key)
+{
+ if (!store || !id || dnssec_key_get_algorithm(key) == 0 || !key) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (key->private_key) {
+ return DNSSEC_KEY_ALREADY_PRESENT;
+ }
+
+ gnutls_privkey_t privkey = NULL;
+ int r = store->functions->get_private(store->ctx, id, &privkey);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ r = key_set_private_key(key, privkey);
+ if (r != DNSSEC_EOK) {
+ gnutls_privkey_deinit(privkey);
+ return r;
+ }
+
+ return DNSSEC_EOK;
+}
+
+_public_
+int dnssec_keystore_set_private(dnssec_keystore_t *store, dnssec_key_t *key)
+{
+ if (!store || !key) {
+ return DNSSEC_EINVAL;
+ }
+
+ if (!key->private_key) {
+ return DNSSEC_NO_PRIVATE_KEY;
+ }
+
+ return store->functions->set_private(store->ctx, key->private_key);
+}
diff --git a/src/libdnssec/keystore/pkcs11.c b/src/libdnssec/keystore/pkcs11.c
new file mode 100644
index 0000000..2b8b6d5
--- /dev/null
+++ b/src/libdnssec/keystore/pkcs11.c
@@ -0,0 +1,397 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <gnutls/gnutls.h>
+#include <pthread.h>
+
+#include "contrib/string.h"
+#include "libdnssec/error.h"
+#include "libdnssec/keyid.h"
+#include "libdnssec/shared/keyid_gnutls.h"
+#include "libdnssec/keystore.h"
+#include "libdnssec/keystore/internal.h"
+#include "libdnssec/p11/p11.h"
+#include "libdnssec/pem.h"
+#include "libdnssec/shared/shared.h"
+
+#ifdef ENABLE_PKCS11
+
+struct pkcs11_ctx {
+ char *url;
+};
+
+typedef struct pkcs11_ctx pkcs11_ctx_t;
+
+/*!
+ * Flags used when generating/import key into the token.
+ */
+static const int TOKEN_ADD_FLAGS = GNUTLS_PKCS11_OBJ_FLAG_MARK_SENSITIVE
+ | GNUTLS_PKCS11_OBJ_FLAG_MARK_PRIVATE;
+
+static int key_url(const char *token_uri, const char *key_id, char **url_ptr)
+{
+ assert(token_uri);
+ assert(key_id);
+ assert(url_ptr);
+
+ if (!dnssec_keyid_is_valid(key_id)) {
+ return DNSSEC_INVALID_KEY_ID;
+ }
+
+ size_t token_len = strlen(token_uri);
+ size_t id_len = strlen(key_id);
+
+ // url: <token-url>;id=%aa%bb%cc..
+
+ size_t len = token_len + 4 + (id_len / 2 * 3);
+ char *url = malloc(len + 1);
+ if (!url) {
+ return DNSSEC_ENOMEM;
+ }
+
+ size_t prefix = snprintf(url, len, "%s;id=", token_uri);
+ if (prefix != token_len + 4) {
+ free(url);
+ return DNSSEC_ENOMEM;
+ }
+
+ assert(id_len % 2 == 0);
+ char *pos = url + prefix;
+ for (int i = 0; i < id_len; i += 2, pos += 3) {
+ pos[0] = '%';
+ pos[1] = key_id[i];
+ pos[2] = key_id[i+1];
+ }
+ assert(url + len == pos);
+ url[len] = '\0';
+
+ *url_ptr = url;
+ return DNSSEC_EOK;
+}
+
+/*!
+ * Parse configuration string. Accepted format: "<pkcs11-url> <module-path>"
+ */
+static int parse_config(const char *config, char **uri_ptr, char **module_ptr)
+{
+ const char *space = strchr(config, ' ');
+ if (!space) {
+ return DNSSEC_KEYSTORE_INVALID_CONFIG;
+ }
+
+ char *url = strndup(config, space - config);
+ char *module = strdup(space + 1);
+
+ if (!url || !module) {
+ free(url);
+ free(module);
+ return DNSSEC_ENOMEM;
+ }
+
+ *uri_ptr = url;
+ *module_ptr = module;
+
+ return DNSSEC_EOK;
+}
+
+/*!
+ * Load PKCS #11 module and check if the token is available.
+ */
+static int safe_open(const char *config, char **url_ptr)
+{
+ char *url = NULL;
+ char *module = NULL;
+
+ int r = parse_config(config, &url, &module);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ r = p11_load_module(module);
+ free(module);
+ if (r != GNUTLS_E_SUCCESS) {
+ free(url);
+ return DNSSEC_P11_FAILED_TO_LOAD_MODULE;
+ }
+
+ unsigned int flags = 0;
+ r = gnutls_pkcs11_token_get_flags(url, &flags);
+ if (r != GNUTLS_E_SUCCESS) {
+ free(url);
+ return DNSSEC_P11_TOKEN_NOT_AVAILABLE;
+ }
+
+ *url_ptr = url;
+
+ return DNSSEC_EOK;
+}
+
+/* -- internal API --------------------------------------------------------- */
+
+static void disable_pkcs11_callbacks(void)
+{
+ gnutls_pkcs11_set_pin_function(NULL, NULL);
+ gnutls_pkcs11_set_token_function(NULL, NULL);
+}
+
+static int pkcs11_ctx_new(void **ctx_ptr)
+{
+ static pthread_once_t once = PTHREAD_ONCE_INIT;
+ pthread_once(&once, disable_pkcs11_callbacks);
+
+ pkcs11_ctx_t *ctx = calloc(1, sizeof(*ctx));
+ if (!ctx) {
+ return DNSSEC_ENOMEM;
+ }
+
+ *ctx_ptr = ctx;
+
+ return DNSSEC_EOK;
+}
+
+static void pkcs11_ctx_free(void *ctx)
+{
+ free(ctx);
+}
+
+static int pkcs11_init(void *ctx, const char *config)
+{
+ /*
+ * Current keystore initialization is idempotent. We don't really
+ * initialize the token because don't want to wipe the data. We just
+ * check that the token is available the same way pkcs11_open() does.
+ */
+
+ _cleanup_free_ char *url = NULL;
+
+ return safe_open(config, &url);
+}
+
+static int pkcs11_open(void *_ctx, const char *config)
+{
+ pkcs11_ctx_t *ctx = _ctx;
+
+ return safe_open(config, &ctx->url);
+}
+
+static int pkcs11_close(void *_ctx)
+{
+ pkcs11_ctx_t *ctx = _ctx;
+
+ free(ctx->url);
+ clear_struct(ctx);
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs11_generate_key(void *_ctx, gnutls_pk_algorithm_t algorithm,
+ unsigned bits, const char *label, char **id_ptr)
+{
+ pkcs11_ctx_t *ctx = _ctx;
+
+ uint8_t buf[20] = { 0 };
+ gnutls_rnd(GNUTLS_RND_RANDOM, buf, sizeof(buf));
+ dnssec_binary_t cka_id = { .data = buf, .size = sizeof(buf) };
+
+ int flags = TOKEN_ADD_FLAGS | GNUTLS_PKCS11_OBJ_FLAG_LOGIN;
+ gnutls_datum_t gt_cka_id = binary_to_datum(&cka_id);
+ int r = gnutls_pkcs11_privkey_generate3(ctx->url, algorithm, bits, label,
+ &gt_cka_id, 0, NULL, 0, flags);
+ if (r != GNUTLS_E_SUCCESS) {
+ return DNSSEC_KEY_GENERATE_ERROR;
+ }
+
+ char *id = bin_to_hex(cka_id.data, cka_id.size, false);
+ if (id == NULL) {
+ return DNSSEC_ENOMEM;
+ }
+
+ *id_ptr = id;
+
+ return DNSSEC_EOK;
+}
+
+static int import_pem(const dnssec_binary_t *pem,
+ gnutls_x509_privkey_t *key_ptr,
+ gnutls_pubkey_t *pubkey_ptr)
+{
+ gnutls_x509_privkey_t x509_key = NULL;
+ gnutls_privkey_t key = NULL;
+ gnutls_pubkey_t pubkey = NULL;
+
+ int r = dnssec_pem_to_x509(pem, &x509_key);
+ if (r != DNSSEC_EOK) {
+ goto fail;
+ }
+
+ if (gnutls_privkey_init(&key) != GNUTLS_E_SUCCESS ||
+ gnutls_pubkey_init(&pubkey) != GNUTLS_E_SUCCESS
+ ) {
+ r = DNSSEC_ENOMEM;
+ goto fail;
+ }
+
+ if (gnutls_privkey_import_x509(key, x509_key, 0) != GNUTLS_E_SUCCESS ||
+ gnutls_pubkey_import_privkey(pubkey, key, 0, 0) != GNUTLS_E_SUCCESS
+ ) {
+ r = DNSSEC_KEY_IMPORT_ERROR;
+ goto fail;
+ }
+
+fail:
+ gnutls_privkey_deinit(key);
+
+ if (r == DNSSEC_EOK) {
+ *key_ptr = x509_key;
+ *pubkey_ptr = pubkey;
+ } else {
+ gnutls_x509_privkey_deinit(x509_key);
+ gnutls_pubkey_deinit(pubkey);
+ }
+
+ return r;
+}
+
+static int pkcs11_import_key(void *_ctx, const dnssec_binary_t *pem, char **id_ptr)
+{
+ pkcs11_ctx_t *ctx = _ctx;
+
+ _cleanup_x509_privkey_ gnutls_x509_privkey_t key = NULL;
+ _cleanup_pubkey_ gnutls_pubkey_t pubkey = NULL;
+
+ int r = import_pem(pem, &key, &pubkey);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ _cleanup_binary_ dnssec_binary_t id = { 0 };
+ r = keyid_x509(key, &id);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ int flags = TOKEN_ADD_FLAGS | GNUTLS_PKCS11_OBJ_FLAG_LOGIN;
+ gnutls_datum_t gid = binary_to_datum(&id);
+
+ r = gnutls_pkcs11_copy_x509_privkey2(ctx->url, key, NULL, &gid, 0, flags);
+ if (r != GNUTLS_E_SUCCESS) {
+ return DNSSEC_KEY_IMPORT_ERROR;
+ }
+
+ r = gnutls_pkcs11_copy_pubkey(ctx->url, pubkey, NULL, &gid, 0, flags);
+ if (r != GNUTLS_E_SUCCESS) {
+ // note, we result with dangling private key in the token
+ return DNSSEC_KEY_IMPORT_ERROR;
+ }
+
+ *id_ptr = bin_to_hex(id.data, id.size, false);
+ if (*id_ptr == NULL) {
+ return DNSSEC_ENOMEM;
+ }
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs11_remove_key(void *_ctx, const char *id)
+{
+ pkcs11_ctx_t *ctx = _ctx;
+
+ _cleanup_free_ char *url = NULL;
+ int r = key_url(ctx->url, id, &url);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ r = gnutls_pkcs11_delete_url(url, GNUTLS_PKCS11_OBJ_FLAG_LOGIN);
+ if (r < 0) {
+ return DNSSEC_ERROR;
+ } else if (r == 0) {
+ return DNSSEC_ENOENT;
+ }
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs11_get_private(void *_ctx, const char *id, gnutls_privkey_t *key_ptr)
+{
+ pkcs11_ctx_t *ctx = _ctx;
+
+ _cleanup_free_ char *url = NULL;
+ int r = key_url(ctx->url, id, &url);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ gnutls_privkey_t key = NULL;
+ r = gnutls_privkey_init(&key);
+ if (r != GNUTLS_E_SUCCESS) {
+ return DNSSEC_ENOMEM;
+ }
+
+ r = gnutls_privkey_import_pkcs11_url(key, url);
+ if (r != GNUTLS_E_SUCCESS) {
+ gnutls_privkey_deinit(key);
+ return DNSSEC_NOT_FOUND;
+ }
+
+ *key_ptr = key;
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs11_set_private(void *ctx, gnutls_privkey_t key)
+{
+ _cleanup_binary_ dnssec_binary_t pem = { 0 };
+ int r = dnssec_pem_from_privkey(key, &pem);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ _cleanup_free_ char *keyid = NULL;
+
+ return pkcs11_import_key(ctx, &pem, &keyid);
+}
+
+/* -- public API ----------------------------------------------------------- */
+
+_public_
+int dnssec_keystore_init_pkcs11(dnssec_keystore_t **store_ptr)
+{
+ static const keystore_functions_t IMPLEMENTATION = {
+ .ctx_new = pkcs11_ctx_new,
+ .ctx_free = pkcs11_ctx_free,
+ .init = pkcs11_init,
+ .open = pkcs11_open,
+ .close = pkcs11_close,
+ .generate_key = pkcs11_generate_key,
+ .import_key = pkcs11_import_key,
+ .remove_key = pkcs11_remove_key,
+ .get_private = pkcs11_get_private,
+ .set_private = pkcs11_set_private,
+ };
+
+ return keystore_create(store_ptr, &IMPLEMENTATION);
+}
+
+#else // !ENABLE_PKCS11
+
+_public_
+int dnssec_keystore_init_pkcs11(dnssec_keystore_t **store_ptr)
+{
+ return DNSSEC_NOT_IMPLEMENTED_ERROR;
+}
+
+#endif
diff --git a/src/libdnssec/keystore/pkcs8.c b/src/libdnssec/keystore/pkcs8.c
new file mode 100644
index 0000000..047bde8
--- /dev/null
+++ b/src/libdnssec/keystore/pkcs8.c
@@ -0,0 +1,497 @@
+/* Copyright (C) 2022 CZ.NIC, z.s.p.o. <knot-dns@labs.nic.cz>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <https://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <fcntl.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+
+#include "contrib/files.h"
+#include "libdnssec/binary.h"
+#include "libdnssec/error.h"
+#include "libdnssec/keystore.h"
+#include "libdnssec/keystore/internal.h"
+#include "libdnssec/pem.h"
+#include "libdnssec/shared/shared.h"
+#include "libdnssec/shared/keyid_gnutls.h"
+
+#define DIR_INIT_MODE 0750
+
+/*!
+ * Context for PKCS #8 key directory.
+ */
+typedef struct {
+ char *dir_name;
+} pkcs8_dir_handle_t;
+
+/* -- internal functions --------------------------------------------------- */
+
+/*!
+ * Get path to a private key in PKCS #8 PEM format.
+ */
+static char *key_path(const char *dir, const char *id)
+{
+ char *strp = NULL;
+
+ int ret = asprintf(&strp, "%s/%s.pem", dir, id);
+ if (ret < 0) {
+ return NULL;
+ }
+ return strp;
+}
+
+/*!
+ * Get size of the file and reset the position to the beginning of the file.
+ */
+static int file_size(int fd, size_t *size)
+{
+ off_t offset = lseek(fd, 0, SEEK_END);
+ if (offset == -1) {
+ return dnssec_errno_to_error(errno);
+ }
+
+ if (lseek(fd, 0, SEEK_SET) == -1) {
+ return dnssec_errno_to_error(errno);
+ }
+
+ assert(offset >= 0);
+ *size = offset;
+
+ return DNSSEC_EOK;
+}
+
+/*!
+ * Open a key file and get the descriptor.
+ */
+static int key_open(const char *dir_name, const char *id, int flags,
+ mode_t mode, int *fd_ptr)
+{
+ assert(dir_name);
+ assert(id);
+ assert(fd_ptr);
+
+ _cleanup_free_ char *filename = key_path(dir_name, id);
+ if (!filename) {
+ return DNSSEC_ENOMEM;
+ }
+
+ int fd = open(filename, flags, mode);
+ if (fd == -1) {
+ return dnssec_errno_to_error(errno);
+ }
+
+ *fd_ptr = fd;
+
+ return DNSSEC_EOK;
+}
+
+static int key_open_read(const char *dir_name, const char *id, int *fd_ptr)
+{
+ return key_open(dir_name, id, O_RDONLY, 0, fd_ptr);
+}
+
+static int key_open_write(const char *dir_name, const char *id, int *fd_ptr)
+{
+ return key_open(dir_name, id, O_WRONLY|O_CREAT|O_EXCL,
+ S_IRUSR|S_IWUSR|S_IRGRP, fd_ptr);
+}
+
+static int pkcs8_dir_read(pkcs8_dir_handle_t *handle, const char *id, dnssec_binary_t *pem)
+{
+ if (!handle || !id || !pem) {
+ return DNSSEC_EINVAL;
+ }
+
+ // open file and get it's size
+
+ _cleanup_close_ int file = -1;
+ int result = key_open_read(handle->dir_name, id, &file);
+ if (result != DNSSEC_EOK) {
+ return result;
+ }
+
+ size_t size = 0;
+ result = file_size(file, &size);
+ if (result != DNSSEC_EOK) {
+ return result;
+ }
+
+ if (size == 0) {
+ return DNSSEC_MALFORMED_DATA;
+ }
+
+ // read the stored data
+
+ dnssec_binary_t read_pem = { 0 };
+ result = dnssec_binary_alloc(&read_pem, size);
+ if (result != DNSSEC_EOK) {
+ return result;
+ }
+
+ ssize_t read_count = read(file, read_pem.data, read_pem.size);
+ if (read_count == -1) {
+ dnssec_binary_free(&read_pem);
+ return dnssec_errno_to_error(errno);
+ }
+
+ assert(read_count == read_pem.size);
+ *pem = read_pem;
+
+ return DNSSEC_EOK;
+}
+
+static bool key_is_duplicate(int open_error, pkcs8_dir_handle_t *handle,
+ const char *id, const dnssec_binary_t *pem)
+{
+ assert(handle);
+ assert(id);
+ assert(pem);
+
+ if (open_error != dnssec_errno_to_error(EEXIST)) {
+ return false;
+ }
+
+ _cleanup_binary_ dnssec_binary_t old = { 0 };
+ int r = pkcs8_dir_read(handle, id, &old);
+ if (r != DNSSEC_EOK) {
+ return false;
+ }
+
+ return dnssec_binary_cmp(&old, pem) == 0;
+}
+
+static int pem_generate(gnutls_pk_algorithm_t algorithm, unsigned bits,
+ dnssec_binary_t *pem, char **id)
+{
+ assert(pem);
+ assert(id);
+
+ // generate key
+
+ _cleanup_x509_privkey_ gnutls_x509_privkey_t key = NULL;
+ int r = gnutls_x509_privkey_init(&key);
+ if (r != GNUTLS_E_SUCCESS) {
+ return DNSSEC_ENOMEM;
+ }
+
+ r = gnutls_x509_privkey_generate(key, algorithm, bits, 0);
+ if (r != GNUTLS_E_SUCCESS) {
+ return DNSSEC_KEY_GENERATE_ERROR;
+ }
+
+ // convert to PEM and export the ID
+
+ dnssec_binary_t _pem = { 0 };
+ r = dnssec_pem_from_x509(key, &_pem);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ // export key ID
+
+ char *_id = NULL;
+ r = keyid_x509_hex(key, &_id);
+ if (r != DNSSEC_EOK) {
+ dnssec_binary_free(&_pem);
+ return r;
+ }
+
+ *id = _id;
+ *pem = _pem;
+
+ return DNSSEC_EOK;
+}
+
+/* -- internal API --------------------------------------------------------- */
+
+static int pkcs8_ctx_new(void **ctx_ptr)
+{
+ if (!ctx_ptr) {
+ return DNSSEC_EINVAL;
+ }
+
+ pkcs8_dir_handle_t *ctx = calloc(1, sizeof(*ctx));
+ if (!ctx) {
+ return DNSSEC_ENOMEM;
+ }
+
+ *ctx_ptr = ctx;
+
+ return DNSSEC_EOK;
+}
+
+static void pkcs8_ctx_free(void *ctx)
+{
+ free(ctx);
+}
+
+static int pkcs8_init(void *ctx, const char *config)
+{
+ if (!ctx || !config) {
+ return DNSSEC_EINVAL;
+ }
+
+ return make_dir(config, DIR_INIT_MODE, true);
+}
+
+static int pkcs8_open(void *ctx, const char *config)
+{
+ if (!ctx || !config) {
+ return DNSSEC_EINVAL;
+ }
+
+ pkcs8_dir_handle_t *handle = ctx;
+
+ char *path = realpath(config, NULL);
+ if (!path) {
+ return DNSSEC_NOT_FOUND;
+ }
+
+ handle->dir_name = path;
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs8_close(void *ctx)
+{
+ if (!ctx) {
+ return DNSSEC_EINVAL;
+ }
+
+ pkcs8_dir_handle_t *handle = ctx;
+
+ free(handle->dir_name);
+ memset(handle, 0, sizeof(*handle));
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs8_generate_key(void *ctx, gnutls_pk_algorithm_t algorithm,
+ unsigned bits, const char *label, char **id_ptr)
+{
+ if (!ctx || !id_ptr) {
+ return DNSSEC_EINVAL;
+ }
+
+ (void)label;
+
+ pkcs8_dir_handle_t *handle = ctx;
+
+ // generate key
+
+ char *id = NULL;
+ _cleanup_binary_ dnssec_binary_t pem = { 0 };
+ int r = pem_generate(algorithm, bits, &pem, &id);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ // create the file
+
+ _cleanup_close_ int file = -1;
+ r = key_open_write(handle->dir_name, id, &file);
+ if (r != DNSSEC_EOK) {
+ if (key_is_duplicate(r, handle, id, &pem)) {
+ return DNSSEC_EOK;
+ }
+ return r;
+ }
+
+ // write the data
+
+ ssize_t wrote_count = write(file, pem.data, pem.size);
+ if (wrote_count == -1) {
+ return dnssec_errno_to_error(errno);
+ }
+
+ assert(wrote_count == pem.size);
+
+ // finish
+
+ *id_ptr = id;
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs8_import_key(void *ctx, const dnssec_binary_t *pem, char **id_ptr)
+{
+ if (!ctx || !pem || !id_ptr) {
+ return DNSSEC_EINVAL;
+ }
+
+ pkcs8_dir_handle_t *handle = ctx;
+
+ // retrieve key ID
+
+ char *id = NULL;
+ _cleanup_x509_privkey_ gnutls_x509_privkey_t key = NULL;
+ int r = dnssec_pem_to_x509(pem, &key);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ r = keyid_x509_hex(key, &id);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ // create the file
+
+ _cleanup_close_ int file = -1;
+ r = key_open_write(handle->dir_name, id, &file);
+ if (r != DNSSEC_EOK) {
+ if (key_is_duplicate(r, handle, id, pem)) {
+ *id_ptr = id;
+ return DNSSEC_EOK;
+ }
+ free(id);
+ return r;
+ }
+
+ // write the data
+
+ ssize_t wrote_count = write(file, pem->data, pem->size);
+ if (wrote_count == -1) {
+ free(id);
+ return dnssec_errno_to_error(errno);
+ }
+
+ assert(wrote_count == pem->size);
+
+ // finish
+
+ *id_ptr = id;
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs8_remove_key(void *ctx, const char *id)
+{
+ if (!ctx || !id) {
+ return DNSSEC_EINVAL;
+ }
+
+ pkcs8_dir_handle_t *handle = ctx;
+
+ _cleanup_free_ char *filename = key_path(handle->dir_name, id);
+ if (!filename) {
+ return DNSSEC_ENOMEM;
+ }
+
+ if (unlink(filename) == -1) {
+ return dnssec_errno_to_error(errno);
+ }
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs8_get_private(void *ctx, const char *id, gnutls_privkey_t *key_ptr)
+{
+ if (!ctx || !id || !key_ptr) {
+ return DNSSEC_EINVAL;
+ }
+
+ pkcs8_dir_handle_t *handle = ctx;
+
+ // load private key data
+
+ _cleanup_close_ int file = -1;
+ int r = key_open_read(handle->dir_name, id, &file);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ size_t size = 0;
+ r = file_size(file, &size);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ if (size == 0) {
+ return DNSSEC_MALFORMED_DATA;
+ }
+
+ // read the stored data
+
+ _cleanup_binary_ dnssec_binary_t pem = { 0 };
+ r = dnssec_binary_alloc(&pem, size);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ ssize_t read_count = read(file, pem.data, pem.size);
+ if (read_count == -1) {
+ dnssec_binary_free(&pem);
+ return dnssec_errno_to_error(errno);
+ }
+
+ assert(read_count == pem.size);
+
+ // construct the key
+
+ gnutls_privkey_t key = NULL;
+ r = dnssec_pem_to_privkey(&pem, &key);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ // finish
+
+ *key_ptr = key;
+
+ return DNSSEC_EOK;
+}
+
+static int pkcs8_set_private(void *ctx, gnutls_privkey_t key)
+{
+ if (!ctx) {
+ return DNSSEC_EINVAL;
+ }
+
+ _cleanup_binary_ dnssec_binary_t pem = { 0 };
+ int r = dnssec_pem_from_privkey(key, &pem);
+ if (r != DNSSEC_EOK) {
+ return r;
+ }
+
+ _cleanup_free_ char *keyid = NULL;
+
+ return pkcs8_import_key(ctx, &pem, &keyid);
+}
+
+/* -- public API ----------------------------------------------------------- */
+
+_public_
+int dnssec_keystore_init_pkcs8(dnssec_keystore_t **store_ptr)
+{
+ static const keystore_functions_t IMPLEMENTATION = {
+ .ctx_new = pkcs8_ctx_new,
+ .ctx_free = pkcs8_ctx_free,
+ .init = pkcs8_init,
+ .open = pkcs8_open,
+ .close = pkcs8_close,
+ .generate_key = pkcs8_generate_key,
+ .import_key = pkcs8_import_key,
+ .remove_key = pkcs8_remove_key,
+ .get_private = pkcs8_get_private,
+ .set_private = pkcs8_set_private,
+ };
+
+ return keystore_create(store_ptr, &IMPLEMENTATION);
+}