summaryrefslogtreecommitdiffstats
path: root/tests/libdnssec/test_keystore_pkcs11.c
diff options
context:
space:
mode:
Diffstat (limited to 'tests/libdnssec/test_keystore_pkcs11.c')
-rw-r--r--tests/libdnssec/test_keystore_pkcs11.c421
1 files changed, 421 insertions, 0 deletions
diff --git a/tests/libdnssec/test_keystore_pkcs11.c b/tests/libdnssec/test_keystore_pkcs11.c
new file mode 100644
index 0000000..9828fce
--- /dev/null
+++ b/tests/libdnssec/test_keystore_pkcs11.c
@@ -0,0 +1,421 @@
+/* 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 <tap/basic.h>
+#include <tap/files.h>
+
+#include <assert.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "libdnssec/crypto.h"
+#include "libdnssec/error.h"
+#include "libdnssec/keystore.h"
+#include "libdnssec/sign.h"
+
+#include "sample_keys.h"
+
+#define ENV_SOFTHSM_DSO "KNOT_SOFTHSM2_DSO"
+#define ENV_SOFTHSM_UTIL "KNOT_SOFTHSM2_UTIL"
+
+#define SOFTHSM_DSO "libsofthsm2.so"
+#define SOFTHSM_CONF "softhsm2.conf"
+#define SOFTHSM_CONF_ENV "SOFTHSM2_CONF"
+#define SOFTHSM_UTIL "softhsm2-util"
+
+#define TOKEN_LABEL "libdnssec-test"
+#define TOKEN_PIN "1234"
+#define TOKEN_SOPIN "123456"
+
+#define EXIT_EXEC_FAILED 127
+
+#ifndef LIBDIR
+# include <bits/wordsize.h>
+# if __WORDSIZE == 32
+# define LIBDIR "/usr/lib32"
+# elif __WORDSIZE == 64
+# define LIBDIR "/usr/lib64"
+# endif
+#endif
+
+#define MSG_SOFTWARE "soft -"
+#define MSG_PKCS11 "p11 -"
+
+/*!
+ * Get SoftHSM DSO path.
+ */
+static char *libsofthsm_dso(void)
+{
+ // prefer environment variable
+
+ const char *env = getenv(ENV_SOFTHSM_DSO);
+ if (env) {
+ return (env[0] != '\0' ? strdup(env) : NULL);
+ }
+
+ // autodetection
+
+ static const char *paths[] = {
+ LIBDIR "/pkcs11/" SOFTHSM_DSO,
+ LIBDIR "/softhsm/" SOFTHSM_DSO,
+ LIBDIR "/" SOFTHSM_DSO,
+ NULL
+ };
+
+ for (const char **path_ptr = paths; *path_ptr; path_ptr += 1) {
+ const char *path = *path_ptr;
+ if (access(path, R_OK|X_OK) == 0) {
+ return strdup(path);
+ }
+ }
+
+ return NULL;
+}
+
+/*!
+ * Get SoftHSM utility path.
+ */
+static char *libsofthsm_util(void)
+{
+ // prefer environment variable
+
+ const char *env = getenv(ENV_SOFTHSM_UTIL);
+ if (env && env[0] != '\0') {
+ return strdup(env);
+ }
+
+ // fallback, will rely on PATH
+
+ return strdup(SOFTHSM_UTIL);
+}
+
+/*!
+ * Path to temporary token data.
+ */
+static char *token_path = NULL;
+
+/*!
+ * Cleanup token test data.
+ */
+static void token_cleanup(void)
+{
+ test_rm_rf(token_path);
+ free(token_path);
+}
+
+/*!
+ * Initialize token using the support tool.
+ */
+static bool token_init_exec(const char *util)
+{
+ pid_t child = fork();
+ if (child == -1) {
+ return false;
+ }
+
+ // child
+
+ if (child == 0) {
+ fclose(stdin);
+ fclose(stdout);
+ fclose(stderr);
+
+ const char *basename = strrchr(util, '/');
+ if (basename) {
+ basename += 1;
+ } else {
+ basename = util;
+ }
+
+ execlp(util, basename,
+ "--init-token", "--slot=0", "--label=" TOKEN_LABEL,
+ "--pin=" TOKEN_PIN, "--so-pin=" TOKEN_SOPIN,
+ NULL);
+
+ exit(EXIT_EXEC_FAILED);
+ }
+
+ // parent
+
+ int status = 0;
+ if (waitpid(child, &status, 0) == -1) {
+ return false;
+ }
+
+ int exit_code = WIFEXITED(status) ? WEXITSTATUS(status) : -1;
+ if (exit_code != 0) {
+ diag("%s exit status %d", util, exit_code);
+ if (exit_code == EXIT_EXEC_FAILED) {
+ diag("set %s environment variable to adjust the path",
+ ENV_SOFTHSM_UTIL);
+ }
+ }
+
+ return exit_code == 0;
+}
+
+/*!
+ * Initialize environment and token for testing.
+ */
+static bool token_init(void)
+{
+ token_path = test_mkdtemp();
+ if (!token_path) {
+ return false;
+ }
+
+ // generate configuration file for unit test
+
+ char config[4096] = { 0 };
+ int r = snprintf(config, sizeof(config), "%s/%s", token_path, SOFTHSM_CONF);
+ if (r <= 0 || r >= sizeof(config)) {
+ return false;
+ }
+
+ FILE *file = fopen(config, "w");
+ if (!file) {
+ return false;
+ }
+
+ fprintf(file, "directories.tokendir = %s\n", token_path);
+ fprintf(file, "objectstore.backend = file\n");
+ fprintf(file, "log.level = INFO\n");
+ fprintf(file, "slots.removable = false\n");
+
+ fclose(file);
+
+ // update environment to use the config
+
+ if (setenv(SOFTHSM_CONF_ENV, config, 1) != 0) {
+ return false;
+ }
+
+ // initialize token
+
+ char *util = libsofthsm_util();
+ if (!util) {
+ return false;
+ }
+
+ bool inited = token_init_exec(util);
+ free(util);
+
+ return inited;
+}
+
+static void create_dnskeys(dnssec_keystore_t *keystore,
+ dnssec_key_algorithm_t algorithm, const char *id,
+ dnssec_key_t **p11_key_ptr, dnssec_key_t **soft_key_ptr)
+{
+ int r;
+
+ // construct PKCS #11 privkey-pubkey key pair
+
+ dnssec_key_t *p11_key = NULL;
+ r = dnssec_key_new(&p11_key);
+ ok(r == DNSSEC_EOK && p11_key != NULL, MSG_PKCS11 " dnssec_key_new()");
+
+ r = dnssec_key_set_algorithm(p11_key, algorithm);
+ ok(r == DNSSEC_EOK, MSG_PKCS11 " dnssec_key_set_algorithm()");
+
+ r = dnssec_keystore_get_private(keystore, id, p11_key);
+ ok(r == DNSSEC_EOK, MSG_PKCS11 " dnssec_keystore_get_private()");
+
+ // construct software public key
+
+ dnssec_key_t *soft_key = NULL;
+ r = dnssec_key_new(&soft_key);
+ ok(r == DNSSEC_EOK && soft_key != NULL, MSG_SOFTWARE " dnssec_key_new()");
+
+ dnssec_binary_t rdata = { 0 };
+ dnssec_key_get_rdata(p11_key, &rdata);
+ r = dnssec_key_set_rdata(soft_key, &rdata);
+ ok(r == DNSSEC_EOK, MSG_SOFTWARE " dnssec_key_set_rdata()");
+
+ *p11_key_ptr = p11_key;
+ *soft_key_ptr = soft_key;
+}
+
+static void test_sign(dnssec_key_t *p11_key, dnssec_key_t *soft_key)
+{
+ int r;
+
+ static const dnssec_binary_t input = {
+ .data = (uint8_t *)"So Long, and Thanks for All the Fish.",
+ .size = 37
+ };
+
+ dnssec_binary_t sign = { 0 };
+
+ // usage constraints
+
+ ok(dnssec_key_can_sign(p11_key), MSG_PKCS11 " dnssec_key_can_sign()");
+ ok(dnssec_key_can_verify(p11_key), MSG_PKCS11 " dnssec_key_can_verify()");
+
+ ok(!dnssec_key_can_sign(soft_key), MSG_SOFTWARE " dnssec_key_can_sign()");
+ ok(dnssec_key_can_verify(soft_key), MSG_SOFTWARE " dnssec_key_can_verify()");
+
+ // PKCS #11 key signature
+
+ dnssec_sign_ctx_t *ctx = NULL;
+ r = dnssec_sign_new(&ctx, p11_key);
+ ok(r == DNSSEC_EOK, MSG_PKCS11 " dnssec_sign_init() ");
+
+ r = dnssec_sign_add(ctx, &input);
+ ok(r == DNSSEC_EOK, MSG_PKCS11 " dnssec_sign_add()");
+
+ r = dnssec_sign_write(ctx, DNSSEC_SIGN_NORMAL, &sign);
+ ok(r == DNSSEC_EOK, MSG_PKCS11 " dnssec_sign_write()");
+
+ // PKCS #11 key verification
+
+ r = dnssec_sign_init(ctx);
+ ok(r == DNSSEC_EOK, MSG_PKCS11 " dnssec_sign_init()");
+
+ r = dnssec_sign_add(ctx, &input);
+ ok(r == DNSSEC_EOK, MSG_PKCS11 " dnssec_sign_add()");
+
+ r = dnssec_sign_verify(ctx, false, &sign);
+ ok(r == DNSSEC_EOK, MSG_PKCS11 " dnssec_sign_verify()");
+
+ // software verification
+
+ dnssec_sign_free(ctx);
+ ctx = NULL;
+
+ r = dnssec_sign_new(&ctx, soft_key);
+ ok(r == DNSSEC_EOK, MSG_SOFTWARE " dnssec_sign_init()");
+
+ r = dnssec_sign_add(ctx, &input);
+ ok(r == DNSSEC_EOK, MSG_SOFTWARE " dnssec_sign_add()");
+
+ r = dnssec_sign_verify(ctx, false, &sign);
+ ok(r == DNSSEC_EOK, MSG_SOFTWARE " dnssec_sign_verify()");
+
+ dnssec_binary_free(&sign);
+ dnssec_sign_free(ctx);
+}
+
+static void test_key_use(dnssec_keystore_t *store,
+ dnssec_key_algorithm_t algorithm,
+ const char *keyid)
+{
+ dnssec_key_t *p11_key = NULL;
+ dnssec_key_t *soft_key = NULL;
+
+ create_dnskeys(store, algorithm, keyid, &p11_key, &soft_key);
+ test_sign(p11_key, soft_key);
+
+ dnssec_key_free(p11_key);
+ dnssec_key_free(soft_key);
+}
+
+static void test_algorithm(dnssec_keystore_t *store,
+ const key_parameters_t *params)
+{
+ char *id_generate = NULL;
+ char *id_import = NULL;
+
+ int r;
+
+ diag("algorithm %d, generated key", params->algorithm);
+
+ r = dnssec_keystore_generate(store, params->algorithm, params->bit_size,
+ NULL, &id_generate);
+ ok(r == DNSSEC_EOK && id_generate != NULL, "dnssec_keystore_generate()");
+ test_key_use(store, params->algorithm, id_generate);
+
+ diag("algorithm %d, imported key", params->algorithm);
+
+ r = dnssec_keystore_import(store, &params->pem, &id_import);
+ ok(r == DNSSEC_EOK && id_import != NULL, "dnssec_keystore_import()");
+ test_key_use(store, params->algorithm, id_import);
+
+ free(id_generate);
+ free(id_import);
+}
+
+int main(int argc, char *argv[])
+{
+ plan_lazy();
+
+ dnssec_crypto_init();
+
+ // PKCS #11 initialization
+
+ dnssec_keystore_t *store = NULL;
+ int r = dnssec_keystore_init_pkcs11(&store);
+ if (r == DNSSEC_NOT_IMPLEMENTED_ERROR) {
+ skip_all("not supported");
+ goto done;
+ }
+ ok(r == DNSSEC_EOK && store, "dnssec_keystore_init_pkcs11()");
+
+ char *dso_name = libsofthsm_dso();
+ if (!dso_name) {
+ skip_all("%s not found, set %s environment variable",
+ SOFTHSM_DSO, ENV_SOFTHSM_DSO);
+ goto done;
+ }
+ ok(dso_name != NULL, "find token DSO");
+
+ bool success = token_init();
+ if (!success) {
+ skip_all("failed to configure and initialize the token");
+ goto done;
+ }
+ ok(success, "initialize the token");
+
+ char config[4096] = { 0 };
+ r = snprintf(config, sizeof(config), "pkcs11:token=%s;pin-value=%s %s",
+ TOKEN_LABEL, TOKEN_PIN, dso_name);
+ free(dso_name);
+ ok(r > 0 && r < sizeof(config), "build configuration");
+
+ // key store access
+
+ r = dnssec_keystore_init(store, config);
+ ok(r == DNSSEC_EOK, "dnssec_keystore_init()");
+
+ r = dnssec_keystore_open(store, config);
+ ok(r == DNSSEC_EOK, "dnssec_keystore_open()");
+
+ // key manipulation
+
+ static const int KEYS_COUNT = 2;
+ static const key_parameters_t *KEYS[] = {
+ &SAMPLE_RSA_KEY,
+ &SAMPLE_ECDSA_KEY,
+ };
+ assert(KEYS_COUNT == sizeof(KEYS) / sizeof(*KEYS));
+
+ for (int i = 0; i < KEYS_COUNT; i++) {
+ test_algorithm(store, KEYS[i]);
+ }
+
+ r = dnssec_keystore_close(store);
+ ok(r == DNSSEC_EOK, "dnssec_keystore_close()");
+done:
+ dnssec_keystore_deinit(store);
+ dnssec_crypto_cleanup();
+ token_cleanup();
+
+ return 0;
+}