From 54b3c52901a08716c4bc1df367a885f920444b19 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 | 538 ++++++++++++++++++++++++++++++++++ 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, 660 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..589564b4 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='list-mechanisms generate-keypair import-object 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..c14a3d65 --- /dev/null +++ b/p11-kit/import-object.c @@ -0,0 +1,538 @@ +/* + * 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 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 (info, "subjectPublicKey", &pubkey_len); + if (pubkey == NULL) { + p11_message (_("failed to obtain subject 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 info, + p11_dict *defs) +{ + unsigned char *ec_point = NULL; + size_t ec_point_len = 0; + unsigned char ec_point_tl[ASN1_MAX_TL_SIZE]; + unsigned int ec_point_tl_len = sizeof (ec_point_tl); + CK_ATTRIBUTE *result = NULL; + CK_KEY_TYPE key_type = CKK_EC; + CK_ATTRIBUTE attr_key_type = { CKA_KEY_TYPE, &key_type, sizeof (key_type) }; + CK_ATTRIBUTE attr_ec_params = { CKA_EC_PARAMS, }; + CK_ATTRIBUTE attr_ec_point = { CKA_EC_POINT, }; + + attr_ec_params.pValue = p11_asn1_read (info, "algorithm.parameters", &attr_ec_params.ulValueLen); + if (attr_ec_params.pValue == NULL) { + p11_message (_("failed to obtain EC parameters")); + goto cleanup; + } + + /* subjectPublicKey is read as BIT STRING value which contains + * EC point data. We need to DER encode this data as OCTET STRING. + */ + ec_point = p11_asn1_read (info, "subjectPublicKey", &ec_point_len); + if (ec_point == NULL) { + p11_message (_("failed to obtain EC point")); + goto cleanup; + } + + /* Length of a BIT STRING value is represented in bits. + * As the EC point is an OCTET STRING it has to be divisible by 8 + */ + if (ec_point_len % 8 != 0) { + p11_message (_("corrupted EC point value")); + goto cleanup; + } + ec_point_len /= 8; + + if (asn1_encode_simple_der (ASN1_ETYPE_OCTET_STRING, ec_point, ec_point_len, + ec_point_tl, &ec_point_tl_len) != ASN1_SUCCESS) { + p11_message (_("failed to DER encode EC point")); + goto cleanup; + } + + attr_ec_point.ulValueLen = ec_point_tl_len + ec_point_len; + attr_ec_point.pValue = malloc (attr_ec_point.ulValueLen); + if (attr_ec_point.pValue == NULL) { + p11_message (_("failed to allocate memory")); + goto cleanup; + } + memcpy (attr_ec_point.pValue, ec_point_tl, ec_point_tl_len); + memcpy ((char *)attr_ec_point.pValue + ec_point_tl_len, ec_point, ec_point_len); + + result = p11_attrs_build (attrs, &attr_key_type, &attr_ec_params, &attr_ec_point, NULL); + if (result == NULL) { + p11_message (_("failed to allocate memory")); + goto cleanup; + } + +cleanup: + free (attr_ec_params.pValue); + free (attr_ec_point.pValue); + free (ec_point); + + return result; +} + +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)); + goto cleanup; + } + +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 label: %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 --file=" + " [--label=