From 79090fd09de1438274b33b54e61cdd7c41ca5ba9 Mon Sep 17 00:00:00 2001 From: Zoltan Fridrich Date: Mon, 4 Sep 2023 14:47:25 +0200 Subject: [PATCH] Add command for importing objects into a PKCS#11 token Signed-off-by: Zoltan Fridrich --- bash-completion/p11-kit | 2 +- common/compat.c | 2 + p11-kit/Makefile.am | 3 + p11-kit/import-object.c | 475 ++++++++++++++++++++++++++++++++++ p11-kit/meson.build | 5 + p11-kit/p11-kit.c | 4 + p11-kit/test-import-public.sh | 106 ++++++++ po/POTFILES.in | 1 + 8 files changed, 597 insertions(+), 1 deletion(-) create mode 100644 p11-kit/import-object.c create mode 100755 p11-kit/test-import-public.sh diff --git a/bash-completion/p11-kit b/bash-completion/p11-kit index 3ef5858f..1e0a83be 100644 --- a/bash-completion/p11-kit +++ b/bash-completion/p11-kit @@ -10,7 +10,7 @@ _p11-kit() COMPREPLY=( $(compgen -W "$opts" -- "$cur") ) return elif [[ $cword -eq 1 ]]; then - local commands='list-mechanisms generate-keypair export-object delete-object list-objects add-profile delete-profile list-profiles list-modules list-tokens print-config extract server remote' + local commands='import-object list-mechanisms generate-keypair export-object delete-object list-objects add-profile delete-profile list-profiles list-modules list-tokens print-config extract server remote' COMPREPLY=( $(compgen -W "$commands" -- "$cur") ) fi } && diff --git a/common/compat.c b/common/compat.c index 9ad741ff..63f804b4 100644 --- a/common/compat.c +++ b/common/compat.c @@ -308,6 +308,8 @@ p11_mmap_open (const char *path, void p11_mmap_close (p11_mmap *map) { + if (map == NULL) + return; if (map->size) munmap (map->data, map->size); close (map->fd); diff --git a/p11-kit/Makefile.am b/p11-kit/Makefile.am index f8300a26..686c4e33 100644 --- a/p11-kit/Makefile.am +++ b/p11-kit/Makefile.am @@ -266,6 +266,7 @@ p11_kit_p11_kit_SOURCES = \ p11-kit/delete-profile.c \ p11-kit/export-object.c \ p11-kit/generate-keypair.c \ + p11-kit/import-object.c \ p11-kit/list-objects.c \ p11-kit/list-profiles.c \ p11-kit/list-mechanisms.c \ @@ -431,6 +432,7 @@ sh_tests += \ if WITH_ASN1 sh_tests += \ p11-kit/test-export-public.sh \ + p11-kit/test-import-public.sh \ p11-kit/test-profiles.sh \ $(NULL) endif @@ -632,6 +634,7 @@ EXTRA_DIST += \ p11-kit/test-server.sh \ p11-kit/test-list-tokens.sh \ p11-kit/test-export-public.sh \ + p11-kit/test-import-public.sh \ p11-kit/test-list-mechanisms.sh \ p11-kit/test-generate-keypair.sh \ $(NULL) diff --git a/p11-kit/import-object.c b/p11-kit/import-object.c new file mode 100644 index 00000000..52590f88 --- /dev/null +++ b/p11-kit/import-object.c @@ -0,0 +1,475 @@ +/* + * Copyright (c) 2023, Red Hat Inc. + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above + * copyright notice, this list of conditions and the + * following disclaimer. + * * Redistributions in binary form must reproduce the + * above copyright notice, this list of conditions and + * the following disclaimer in the documentation and/or + * other materials provided with the distribution. + * * The names of contributors to this software may not be + * used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS + * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE + * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, + * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS + * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED + * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF + * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH + * DAMAGE. + * + * Author: Zoltan Fridrich + */ + +#include "config.h" + +#include "attrs.h" +#include "compat.h" +#include "debug.h" +#include "dict.h" +#include "iter.h" +#include "message.h" +#include "pem.h" +#include "tool.h" + +#ifdef OS_UNIX +#include "tty.h" +#endif + +#ifdef WITH_ASN1 +#include "asn1.h" +#include "oid.h" +#endif + +#include +#include +#include +#include + +#ifdef ENABLE_NLS +#include +#define _(x) dgettext(PACKAGE_NAME, x) +#else +#define _(x) (x) +#endif + +int +p11_kit_import_object (int argc, + char *argv[]); + +#ifdef WITH_ASN1 + +typedef struct { + CK_FUNCTION_LIST *module; + CK_SESSION_HANDLE session; + const char *label; +} import_data; + +static void +import_certificate (const unsigned char *der, + size_t der_len, + const import_data *data) +{ +} + +static CK_ATTRIBUTE * +init_attrs_pubkey (const unsigned char *info, + size_t info_len, + const import_data *data) +{ + CK_ATTRIBUTE *attrs = NULL, *tmp = NULL; + CK_BBOOL tval = CK_TRUE, fval = CK_FALSE; + CK_OBJECT_CLASS class = CKO_PUBLIC_KEY; + CK_ATTRIBUTE attr_class = { CKA_CLASS, &class, sizeof (class) }; + CK_ATTRIBUTE attr_token = { CKA_TOKEN, &tval, sizeof (tval) }; + CK_ATTRIBUTE attr_private = { CKA_PRIVATE, &fval, sizeof (fval) }; + CK_ATTRIBUTE attr_verify = { CKA_VERIFY, &tval, sizeof (tval) }; + CK_ATTRIBUTE attr_info = { CKA_PUBLIC_KEY_INFO, (void *)info, info_len }; + + return_val_if_fail (data != NULL, NULL); + + attrs = p11_attrs_build (NULL, &attr_class, &attr_token, &attr_private, + &attr_verify, &attr_info, NULL); + if (attrs == NULL) + return NULL; + + if (data->label != NULL) { + CK_ATTRIBUTE attr_label = { CKA_LABEL, (void *)data->label, strlen (data->label) }; + + tmp = p11_attrs_build (attrs, &attr_label, NULL); + if (tmp == NULL) { + p11_attrs_free (attrs); + return NULL; + } + attrs = tmp; + } + + return attrs; +} + +static CK_ATTRIBUTE * +add_attrs_pubkey_rsa (CK_ATTRIBUTE *attrs, + asn1_node pubkey_info, + p11_dict *defs) +{ + unsigned char *pubkey = NULL; + size_t pubkey_len = 0; + asn1_node asn = NULL; + CK_ATTRIBUTE *result = NULL; + CK_BBOOL tval = CK_TRUE; + CK_KEY_TYPE key_type = CKK_RSA; + CK_ATTRIBUTE attr_key_type = { CKA_KEY_TYPE, &key_type, sizeof (key_type) }; + CK_ATTRIBUTE attr_encrypt = { CKA_ENCRYPT, &tval, sizeof (tval) }; + CK_ATTRIBUTE attr_modulus = { CKA_MODULUS, }; + CK_ATTRIBUTE attr_exponent = { CKA_PUBLIC_EXPONENT, }; + + pubkey = p11_asn1_read (pubkey_info, "subjectPublicKey", &pubkey_len); + if (pubkey == NULL) { + p11_message (_("failed to obtain public key data")); + goto cleanup; + } + + pubkey_len = p11_asn1_tlv_length (pubkey, pubkey_len); + if ((ssize_t)pubkey_len == -1) { + p11_message (_("failed to parse ASN.1 structure")); + goto cleanup; + } + + asn = p11_asn1_decode (defs, "PKIX1.RSAPublicKey", pubkey, pubkey_len, NULL); + if (asn == NULL) { + p11_message (_("failed to parse ASN.1 structure")); + goto cleanup; + } + + attr_modulus.pValue = p11_asn1_read (asn, "modulus", &attr_modulus.ulValueLen); + if (attr_modulus.pValue == NULL) { + p11_message (_("failed to obtain modulus")); + goto cleanup; + } + + attr_exponent.pValue = p11_asn1_read (asn, "publicExponent", &attr_exponent.ulValueLen); + if (attr_exponent.pValue == NULL) { + p11_message (_("failed to obtain exponent")); + goto cleanup; + } + + result = p11_attrs_build (attrs, &attr_key_type, &attr_encrypt, &attr_modulus, &attr_exponent, NULL); + if (result == NULL) { + p11_message (_("failed to allocate memory")); + goto cleanup; + } + +cleanup: + free (attr_modulus.pValue); + free (attr_exponent.pValue); + free (pubkey); + p11_asn1_free (asn); + + return result; +} + +static CK_ATTRIBUTE * +add_attrs_pubkey_ec (CK_ATTRIBUTE *attrs, + asn1_node pubkey_info, + p11_dict *defs) +{ + return NULL; +} + +static void +import_pubkey (const unsigned char *der, + size_t der_len, + const import_data *data) +{ + CK_RV rv; + CK_OBJECT_HANDLE object = 0; + CK_ATTRIBUTE *attrs = NULL, *tmp = NULL; + p11_dict *defs = NULL; + asn1_node asn = NULL; + char *oid = NULL; + size_t oid_len = 0; + + return_if_fail (data != NULL); + return_if_fail (data->module != NULL); + return_if_fail (data->session != 0); + + defs = p11_asn1_defs_load (); + if (defs == NULL) { + p11_message (_("failed to load ASN.1 definitions")); + goto cleanup; + } + + asn = p11_asn1_decode (defs, "PKIX1.SubjectPublicKeyInfo", der, der_len, NULL); + if (asn == NULL) { + p11_message (_("failed to parse ASN.1 structure")); + goto cleanup; + } + + oid = p11_asn1_read (asn, "algorithm.algorithm", &oid_len); + if (oid == NULL) { + p11_message (_("failed to obtain algorithm OID")); + goto cleanup; + } + + attrs = init_attrs_pubkey (der, der_len, data); + if (attrs == NULL) { + p11_message (_("failed to allocate memory")); + goto cleanup; + } + + if (strcmp (oid, P11_OID_PKIX1_RSA_STR) == 0) + tmp = add_attrs_pubkey_rsa (attrs, asn, defs); + else if (strcmp (oid, P11_OID_PKIX1_EC_STR) == 0) + tmp = add_attrs_pubkey_ec (attrs, asn, defs); + else { + p11_message (_("unrecognized algorithm OID: %s"), oid); + goto cleanup; + } + if (tmp == NULL) + goto cleanup; + attrs = tmp; + + rv = data->module->C_CreateObject (data->session, attrs, p11_attrs_count (attrs), &object); + if (rv != CKR_OK) + p11_message (_("failed to create object: %s"), p11_kit_strerror (rv)); + +cleanup: + free (oid); + p11_attrs_free (attrs); + p11_asn1_free (asn); + p11_dict_free (defs); +} + +static void +import_pem (const char *type, + const unsigned char *der, + size_t der_len, + void *data) +{ + return_if_fail (type != NULL); + + if (strcmp (type, "CERTIFICATE") == 0) + import_certificate (der, der_len, data); + else if (strcmp (type, "PUBLIC KEY") == 0) + import_pubkey (der, der_len, data); + else + p11_message (_("unrecognized PEM header: %s"), type); +} + +static int +import_object (const char *token_str, + const char *file, + const char *label, + bool login) +{ + int ret = 1; + CK_RV rv; + void *data = NULL; + size_t data_len = 0; + unsigned n_parsed = 0; + p11_mmap *mmap = NULL; + P11KitUri *uri = NULL; + P11KitIter *iter = NULL; + P11KitIterBehavior behavior; + CK_FUNCTION_LIST **modules = NULL; + import_data user_data = { NULL, 0, label }; + + uri = p11_kit_uri_new (); + if (uri == NULL) { + p11_message (_("failed to allocate memory")); + goto cleanup; + } + + if (p11_kit_uri_parse (token_str, P11_KIT_URI_FOR_TOKEN, uri) != P11_KIT_URI_OK) { + p11_message (_("failed to parse URI")); + goto cleanup; + } + + modules = p11_kit_modules_load_and_initialize (0); + if (modules == NULL) { + p11_message (_("failed to load and initialize modules")); + goto cleanup; + } + + behavior = P11_KIT_ITER_WANT_WRITABLE | P11_KIT_ITER_WITH_TOKENS | P11_KIT_ITER_WITHOUT_OBJECTS; + if (login) { + behavior |= P11_KIT_ITER_WITH_LOGIN; +#ifdef OS_UNIX + p11_kit_uri_set_pin_source (uri, "tty"); +#endif + } + + iter = p11_kit_iter_new (uri, behavior); + if (iter == NULL) { + p11_message (_("failed to initialize iterator")); + goto cleanup; + } + + p11_kit_iter_begin (iter, modules); + rv = p11_kit_iter_next (iter); + if (rv != CKR_OK) { + if (rv == CKR_CANCEL) + p11_message (_("no matching token")); + else + p11_message (_("failed to find token: %s"), p11_kit_strerror (rv)); + goto cleanup; + } + + user_data.module = p11_kit_iter_get_module (iter); + return_val_if_fail (user_data.module != NULL, 1); + user_data.session = p11_kit_iter_get_session (iter); + return_val_if_fail (user_data.session != CK_INVALID_HANDLE, 1); + + mmap = p11_mmap_open (file, NULL, &data, &data_len); + if (mmap == NULL) { + p11_message (_("failed to read file: %s"), file); + goto cleanup; + } + + n_parsed = p11_pem_parse (data, data_len, import_pem, &user_data); + if (n_parsed == 0) { + p11_message (_("no object to import")); + goto cleanup; + } + + ret = 0; + +cleanup: + p11_mmap_close (mmap); + p11_kit_iter_free (iter); + p11_kit_uri_free (uri); + if (modules != NULL) + p11_kit_modules_finalize_and_release (modules); + + return ret; +} + +int +p11_kit_import_object (int argc, + char *argv[]) +{ + int opt, ret = 2; + char *label = NULL; + char *file = NULL; + bool login = false; + + enum { + opt_verbose = 'v', + opt_quiet = 'q', + opt_help = 'h', + opt_label = 'L', + opt_file = 'f', + opt_login = 'l', + }; + + struct option options[] = { + { "verbose", no_argument, NULL, opt_verbose }, + { "quiet", no_argument, NULL, opt_quiet }, + { "help", no_argument, NULL, opt_help }, + { "label", required_argument, NULL, opt_label }, + { "file", required_argument, NULL, opt_file }, + { "login", no_argument, NULL, opt_login }, + { 0 }, + }; + + p11_tool_desc usages[] = { + { 0, "usage: p11-kit import-object pkcs11:token" }, + { opt_label, "label to be associated with the imported object" }, + { opt_file, "object data to import" }, + { opt_login, "login to the token" }, + { 0 }, + }; + + while ((opt = p11_tool_getopt (argc, argv, options)) != -1) { + switch (opt) { + case opt_label: + label = strdup (optarg); + if (label == NULL) { + p11_message (_("failed to allocate memory")); + goto cleanup; + } + break; + case opt_file: + file = strdup (optarg); + if (file == NULL) { + p11_message (_("failed to allocate memory")); + goto cleanup; + } + break; + case opt_login: + login = true; + break; + case opt_verbose: + p11_kit_be_loud (); + break; + case opt_quiet: + p11_kit_be_quiet (); + break; + case opt_help: + p11_tool_usage (usages, options); + return 0; + case '?': + return 2; + default: + assert_not_reached (); + break; + } + } + + argc -= optind; + argv += optind; + + if (argc != 1) { + p11_tool_usage (usages, options); + goto cleanup; + } + + if (file == NULL) { + p11_message (_("no file specified")); + goto cleanup; + } + +#ifdef OS_UNIX + /* Register a fallback PIN callback that reads from terminal. + * We don't care whether the registration succeeds as it is a fallback. + */ + (void)p11_kit_pin_register_callback ("tty", p11_pin_tty_callback, NULL, NULL); +#endif + + ret = import_object (*argv, file, label, login); + +#ifdef OS_UNIX + p11_kit_pin_unregister_callback ("tty", p11_pin_tty_callback, NULL); +#endif + +cleanup: + free (label); + free (file); + + return ret; +} + +#else /* WITH_ASN1 */ + +int +p11_kit_import_object (int argc, + char *argv[]) +{ + p11_message (_("ASN.1 support is not compiled in")); + return 2; +} + +#endif /* !WITH_ASN1 */ diff --git a/p11-kit/meson.build b/p11-kit/meson.build index 4d4be110..70583f14 100644 --- a/p11-kit/meson.build +++ b/p11-kit/meson.build @@ -215,6 +215,7 @@ p11_kit_sources = [ 'delete-profile.c', 'export-object.c', 'generate-keypair.c', + 'import-object.c', 'list-objects.c', 'list-profiles.c', 'list-mechanisms.c', @@ -422,6 +423,10 @@ if get_option('test') find_program('test-export-public.sh'), env: p11_kit_tests_env) + test('test-import-public.sh', + find_program('test-import-public.sh'), + env: p11_kit_tests_env) + test('test-profiles.sh', find_program('test-profiles.sh'), env: p11_kit_tests_env) diff --git a/p11-kit/p11-kit.c b/p11-kit/p11-kit.c index eed453d8..e45da313 100644 --- a/p11-kit/p11-kit.c +++ b/p11-kit/p11-kit.c @@ -68,6 +68,9 @@ int p11_kit_list_tokens (int argc, int p11_kit_list_objects (int argc, char *argv[]); +int p11_kit_import_object (int argc, + char *argv[]); + int p11_kit_export_object (int argc, char *argv[]); @@ -102,6 +105,7 @@ static const p11_tool_command commands[] = { { "list-modules", p11_kit_list_modules, N_("List modules and tokens") }, { "list-tokens", p11_kit_list_tokens, N_("List tokens") }, { "list-objects", p11_kit_list_objects, N_("List objects of a token") }, + { "import-object", p11_kit_import_object, N_("Import object into a token") }, { "export-object", p11_kit_export_object, N_("Export object matching PKCS#11 URI") }, { "delete-object", p11_kit_delete_object, N_("Delete objects matching PKCS#11 URI") }, { "generate-keypair", p11_kit_generate_keypair, N_("Generate key-pair on a PKCS#11 token") }, diff --git a/p11-kit/test-import-public.sh b/p11-kit/test-import-public.sh new file mode 100755 index 00000000..067b68c3 --- /dev/null +++ b/p11-kit/test-import-public.sh @@ -0,0 +1,106 @@ +#!/bin/sh + +test "${abs_top_builddir+set}" = set || { + echo "set abs_top_builddir" 1>&2 + exit 1 +} + +. "$abs_top_builddir/common/test-init.sh" + +: ${P11_MODULE_PATH="$abs_top_builddir"/.libs} + +setup() { + testdir=$PWD/test-objects-$$ + test -d "$testdir" || mkdir "$testdir" + cd "$testdir" + mkdir tokens + cat > softhsm2.conf </dev/null; then + skip "softhsm2-util not found" + return + fi + softhsm2-util --init-token --free --label test-import-key --so-pin 12345 --pin 12345 + + : ${PKG_CONFIG=pkg-config} + if ! "$PKG_CONFIG" p11-kit-1 --exists; then + skip "pkgconfig(p11-kit-1) not found" + return + fi + + module_path=$("$PKG_CONFIG" p11-kit-1 --variable=p11_module_path) + if ! test -e "$module_path/libsofthsm2.so"; then + skip "unable to resolve libsofthsm2.so" + return + fi + + ln -sf "$module_path"/libsofthsm2.so "$P11_MODULE_PATH" +} + +teardown() { + unset SOFTHSM2_CONF + rm -rf "$testdir" +} + +test_import_pubkey_rsa() { + # Generated and extracted with: + # p11-kit generate-keypair --type=rsa --bits=2048 --label=RSA 'pkcs11:model=SoftHSM%20v2' + # p11tool --export 'pkcs11:model=SoftHSM%20v2;object=RSA;type=public' + cat > export.exp < export.out; then + assert_fail "unable to run: p11-kit export-object" + fi + + : ${DIFF=diff} + if ! ${DIFF} export.exp export.out > export.diff; then + sed 's/^/# /' export.diff + assert_fail "output contains incorrect result" + fi +} + +test_import_pubkey_ec() { + # Generated and extracted with: + # p11-kit generate-keypair --type=ecdsa --curve=secp256r1 --label=EC 'pkcs11:model=SoftHSM%20v2' + # p11tool --export 'pkcs11:model=SoftHSM%20v2;object=EC;type=public' + cat > export.exp < export.out; then + assert_fail "unable to run: p11-kit export-object" + fi + + : ${DIFF=diff} + if ! ${DIFF} export.exp export.out > export.diff; then + sed 's/^/# /' export.diff + assert_fail "output contains incorrect result" + fi +} + +run test_import_pubkey_rsa test_import_pubkey_ec diff --git a/po/POTFILES.in b/po/POTFILES.in index 20fecee2..fbe25c75 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -8,6 +8,7 @@ p11-kit/delete-profile.c p11-kit/export-object.c p11-kit/filter.c p11-kit/generate-keypair.c +p11-kit/import-object.c p11-kit/iter.c p11-kit/list-objects.c p11-kit/list-profiles.c