Skip to content

Commit

Permalink
Add support for importing TPM2 keys with PKCS11 vendor attributes
Browse files Browse the repository at this point in the history
- Add support for importing TPM2 keys (as persistent handle or key objects) using PKCS11 vendor-specific attributes
- Add a new CLI tool: key_import
- Add integration test
- Add docs/KEY_IMPORT_TOOL.md

Signed-off-by: wenxin.leong <[email protected]>
  • Loading branch information
wxleong committed Jun 19, 2024
1 parent eb3897b commit f333474
Show file tree
Hide file tree
Showing 29 changed files with 1,706 additions and 80 deletions.
6 changes: 4 additions & 2 deletions Makefile-integration.am
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,17 @@ integration_scripts = \
test/integration/pkcs11-javarunner.sh.java \
test/integration/nss-tests.sh \
test/integration/ptool-link.sh.nosetup \
test/integration/python-pkcs11.sh
test/integration/python-pkcs11.sh \
test/integration/key_import-link.sh.nosetup

# Note that -fapi.sh.fapi is symlinked to .sh.nosetup
# If we'd use the .fapi extension then .nosetup and .fapi overwrite each others .log
# thus we use -fapi.sh.fapi as suffix.
if HAVE_FAPI
integration_scripts += \
test/integration/p11-tool-fapi.sh.fapi \
test/integration/pkcs11-tool-init-fapi.sh.fapi
test/integration/pkcs11-tool-init-fapi.sh.fapi \
test/integration/key_import-link-fapi.sh.fapi
endif

EXTRA_DIST += \
Expand Down
14 changes: 12 additions & 2 deletions Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,16 @@ endif

AM_DISTCHECK_CONFIGURE_FLAGS = --with-p11kitconfigdir='$$(datarootdir)/p11kitconfigdir' --with-p11kitmoduledir='$$(libdir)'

# The key_import tool
bin_PROGRAMS = tools/key_import/key_import
if ENABLE_ASAN
tools_key_import_key_import_LDFLAGS = $(AM_LDFLAGS) -shared-libasan
else
tools_key_import_key_import_LDFLAGS = $(AM_LDFLAGS)
endif
tools_key_import_key_import_LDADD = $(libtpm2_pkcs11)
tools_key_import_key_import_SOURCES = tools/key_import/import.c

#
# Due to limitations in how cmocka works, we build a separate library here so we
# can have a PKCS11 shared object with undefined calls into the rest of the lib
Expand Down Expand Up @@ -113,8 +123,8 @@ AM_TESTS_ENVIRONMENT = \
PYTHON_INTERPRETER=@PYTHON_INTERPRETER@ \
TEST_FUNC_LIB=$(srcdir)/test/integration/scripts/int-test-funcs.sh \
TEST_FIXTURES=$(abs_top_srcdir)/test/integration/fixtures \
PATH=$(abs_top_srcdir)/tools:./src:$(PATH) \
PYTHONPATH=$(abs_top_srcdir)/tools:$(PYTHONPATH) \
PATH=$(abs_top_srcdir)/tools/tpm2_ptool:$(abs_builddir)/tools/key_import:./src:$(PATH) \
PYTHONPATH=$(abs_top_srcdir)/tools/tpm2_ptool:$(PYTHONPATH) \
TPM2_PKCS11_MODULE=$(abs_builddir)/src/.libs/libtpm2_pkcs11.so \
TEST_JAVA_ROOT=$(JAVAROOT) \
PACKAGE_URL=$(PACKAGE_URL) \
Expand Down
10 changes: 10 additions & 0 deletions docs/KEY_IMPORT_TOOL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# The key_import Tool

The `key_import` tool in this project is a C program that serves as an example for importing TPM keys into a tpm2-pkcs11 token. The key import mechanism uses PKCS #11 vendor-specific attributes and works with both FAPI and ESYSDB backends.

Supported modes:
- Primary key with or without an auth value, which must be the same primary key used for PKCS #11 token initialization.
- Ordinary key with or without an auth value, which are the key to be imported into the PKCS #11 token.
- Key can be imported as persistent handle or TSS key objects obtained from `tpm2 create` (`TPM2B_PUBLIC` and `TPM2B_PRIVATE` blobs).

For complete examples, please refer to `test/integration/key_import-link.sh.nosetup`.
2 changes: 2 additions & 0 deletions src/lib/attrs.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,10 +167,12 @@ static attr_handler2 attr_handlers[] = {
ADD_ATTR_HANDLER(CKA_WRAP_TEMPLATE, TYPE_BYTE_TEMP_SEQ),
ADD_ATTR_HANDLER(CKA_UNWRAP_TEMPLATE, TYPE_BYTE_TEMP_SEQ),
ADD_ATTR_HANDLER(CKA_ALLOWED_MECHANISMS, TYPE_BYTE_INT_SEQ),
ADD_ATTR_HANDLER(CKA_TPM2_OBJAUTH, TYPE_BYTE_HEX_STR),
ADD_ATTR_HANDLER(CKA_TPM2_OBJAUTH_ENC, TYPE_BYTE_HEX_STR),
ADD_ATTR_HANDLER(CKA_TPM2_PUB_BLOB, TYPE_BYTE_HEX_STR),
ADD_ATTR_HANDLER(CKA_TPM2_PRIV_BLOB, TYPE_BYTE_HEX_STR),
ADD_ATTR_HANDLER(CKA_TPM2_ENC_BLOB, TYPE_BYTE_HEX_STR),
ADD_ATTR_HANDLER(CKA_TPM2_PERSISTENT_HANDLE, TYPE_BYTE_INT),
};

static attr_handler2 default_handler = { .memtype = 0, .name="UNKNOWN" };
Expand Down
12 changes: 7 additions & 5 deletions src/lib/attrs.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,13 @@
/*
* We will allow these to be accessed, but the values are not stable
*/
#define CKA_VENDOR_TPM2_DEFINED 0x0F000000UL
#define CKA_TPM2_OBJAUTH_ENC (CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x1UL)
#define CKA_TPM2_PUB_BLOB (CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x2UL)
#define CKA_TPM2_PRIV_BLOB (CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x3UL)
#define CKA_TPM2_ENC_BLOB (CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x4UL)
#define CKA_VENDOR_TPM2_DEFINED 0x0F000000UL
#define CKA_TPM2_OBJAUTH_ENC (CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x1UL)
#define CKA_TPM2_PUB_BLOB (CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x2UL)
#define CKA_TPM2_PRIV_BLOB (CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x3UL)
#define CKA_TPM2_ENC_BLOB (CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x4UL)
#define CKA_TPM2_OBJAUTH (CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x5UL)
#define CKA_TPM2_PERSISTENT_HANDLE (CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x6UL)

/* Invalid values for error detection */
#define CK_OBJECT_CLASS_BAD (~(CK_OBJECT_CLASS)0)
Expand Down
248 changes: 202 additions & 46 deletions src/lib/key.c
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,12 @@
#include "session_ctx.h"
#include "utils.h"

enum keygen_mode {
keygen_mode_normal,
keygen_mode_kobjs,
keygen_mode_phandle
};

typedef struct sanity_check_data sanity_check_data;
struct sanity_check_data {
bool is_extractable;
Expand Down Expand Up @@ -137,6 +143,36 @@ static CK_RV check_specific_attrs(CK_MECHANISM_TYPE mech,
}
}

static CK_RV check_tpm_vendor_attrs(
attr_list *pubkey_templ_w_types, attr_list *privkey_templ_w_types,
enum keygen_mode *keygen_mode) {

/* From pPublicKeyTemplate */
CK_ATTRIBUTE_PTR a_pub_blob = attr_get_attribute_by_type(pubkey_templ_w_types, CKA_TPM2_PUB_BLOB);
CK_ATTRIBUTE_PTR a_pub_handle = attr_get_attribute_by_type(pubkey_templ_w_types, CKA_TPM2_PERSISTENT_HANDLE);

/* From pPrivateKeyTemplate */
CK_ATTRIBUTE_PTR a_priv_auth = attr_get_attribute_by_type(privkey_templ_w_types, CKA_TPM2_OBJAUTH);
CK_ATTRIBUTE_PTR a_priv_blob = attr_get_attribute_by_type(privkey_templ_w_types, CKA_TPM2_PRIV_BLOB);
CK_ATTRIBUTE_PTR a_priv_handle = attr_get_attribute_by_type(privkey_templ_w_types, CKA_TPM2_PERSISTENT_HANDLE);

if (a_pub_handle && a_priv_handle && !(a_pub_blob || a_priv_blob)) {
/* Persistent key found */
*keygen_mode = keygen_mode_phandle;
} else if (a_pub_blob && a_priv_blob && !(a_pub_handle || a_priv_handle)) {
/* TPM key objects found */
*keygen_mode = keygen_mode_kobjs;
} else if (!(a_pub_blob || a_pub_handle || a_priv_auth || a_priv_blob || a_priv_handle)) {
*keygen_mode = keygen_mode_normal;
} else {
/* Invalid combination */
LOGE("Key import request detected, but the attribute combination is invalid or missing");
return CKR_ATTRIBUTE_TYPE_INVALID;
}

return CKR_OK;
}

CK_RV key_gen (
session_ctx *ctx,

Expand All @@ -153,9 +189,14 @@ CK_RV key_gen (

CK_RV rv = CKR_GENERAL_ERROR;

enum keygen_mode keygen_mode = keygen_mode_normal;

twist newauthhex = NULL;
twist newwrapped_auth = NULL;

twist pub_blob = NULL;
twist priv_blob = NULL;

attr_list *pubkey_templ_w_types = NULL;
attr_list *privkey_templ_w_types = NULL;

Expand All @@ -164,6 +205,12 @@ CK_RV key_gen (

tpm_object_data objdata = { 0 };

CK_ATTRIBUTE_PTR attr_ptr = NULL;

uint32_t priv_esys_tr = 0, pub_esys_tr = 0;
CK_ULONG priv_persistent_handle = 0;
CK_ULONG pub_persistent_handle = 0;

token *tok = session_ctx_get_token(ctx);
assert(tok);

Expand Down Expand Up @@ -223,66 +270,173 @@ CK_RV key_gen (
goto out;
}

rv = utils_new_random_object_auth(&newauthhex);
if (rv != CKR_OK) {
LOGE("Failed to create new object auth");
goto out;
}

rv = utils_ctx_wrap_objauth(tok->wrappingkey, newauthhex, &newwrapped_auth);
if (rv != CKR_OK) {
LOGE("Failed to wrap new object auth");
goto out;
}

rv = tpm2_generate_key(
tok->tctx,
tok->pobject.handle,
tok->pobject.objauth,
newauthhex,
mechanism,
pubkey_templ_w_types,
privkey_templ_w_types,
&objdata);
if (rv != CKR_OK) {
LOGE("Failed to generate key");
goto out;
}

/* set the tpm object handles */
tobject_set_handle(new_private_tobj, objdata.privhandle);
tobject_set_handle(new_public_tobj, objdata.pubhandle);

/* Initialize the objects attributes */
new_public_tobj->attrs = pubkey_templ_w_types;
new_private_tobj->attrs = privkey_templ_w_types;

/* make it clear that tobj now owns these */
pubkey_templ_w_types = privkey_templ_w_types = NULL;

/*
* objects have default required attributes, add them if not present.
*/
rv = attr_add_missing_attrs(&new_public_tobj->attrs, &new_private_tobj->attrs,
objdata.attrs, mechanism->mechanism);
if (rv != CKR_OK) {
LOGE("Failed to add missing rsa attrs");
/* Check if the import of an existing TPM key (persistent handle or key objects) is requested */
rv = check_tpm_vendor_attrs(
new_public_tobj->attrs, new_private_tobj->attrs,
&keygen_mode);
if (rv) {
goto out;
}

/* populate blob data */
rv = tobject_set_blob_data(new_private_tobj, objdata.pubblob, objdata.privblob);
if (rv != CKR_OK) {
goto out;
if (keygen_mode == keygen_mode_normal) { /* Generate a new TPM key */

rv = utils_new_random_object_auth(&newauthhex);
if (rv != CKR_OK) {
LOGE("Failed to create new object auth");
goto out;
}

rv = tpm2_generate_key(
tok->tctx,
tok->pobject.handle,
tok->pobject.objauth,
newauthhex,
mechanism,
new_public_tobj->attrs,
new_private_tobj->attrs,
&objdata);
if (rv != CKR_OK) {
LOGE("Failed to generate key");
goto out;
}

/* set the tpm object handles */
tobject_set_esys_tr(new_private_tobj, objdata.privhandle);
tobject_set_esys_tr(new_public_tobj, objdata.pubhandle);

/* populate blob data */
rv = tobject_set_blob_data(new_private_tobj, objdata.pubblob, objdata.privblob);
if (rv != CKR_OK) {
goto out;
}

rv = tobject_set_blob_data(new_public_tobj, objdata.pubblob, NULL);
if (rv != CKR_OK) {
goto out;
}

} else { /* Import an existing TPM key */

/* Read the CKA_TPM2_OBJAUTH */
attr_ptr = attr_get_attribute_by_type(new_private_tobj->attrs, CKA_TPM2_OBJAUTH);
if (attr_ptr) {
newauthhex = twistbin_new(attr_ptr->pValue, attr_ptr->ulValueLen);

/* Secure erase the CKA_TPM2_OBJAUTH field in the privkey template */
memset(attr_ptr->pValue, 0, attr_ptr->ulValueLen);
attr_pfree_cleanse(attr_ptr);

/* [To-do] delete the CKA_TPM2_OBJAUTH from the privkey template to save storage space */
}

if (keygen_mode == keygen_mode_phandle) { /* The key to import is a persistent handle */

attr_ptr = attr_get_attribute_by_type(new_private_tobj->attrs, CKA_TPM2_PERSISTENT_HANDLE);
rv = attr_CK_ULONG(attr_ptr, &priv_persistent_handle);
if (rv != CKR_OK) {
LOGE("Failed to get private key persistent handle");
goto out;
}

attr_ptr = attr_get_attribute_by_type(new_public_tobj->attrs, CKA_TPM2_PERSISTENT_HANDLE);
rv = attr_CK_ULONG(attr_ptr, &pub_persistent_handle);
if (rv != CKR_OK) {
LOGE("Failed to get public key persistent handle");
goto out;
}

if (priv_persistent_handle != pub_persistent_handle) {
LOGE("The public and private key persistent handles are not the same");
return CKR_ATTRIBUTE_TYPE_INVALID;
}

rv = tpm_get_esys_tr(tok->tctx, (uint32_t)priv_persistent_handle,
&priv_esys_tr, &pub_esys_tr);
if (rv != CKR_OK) {
LOGE("Failed to get ESYS_TR of privkey");
goto out;
}

tobject_set_persistent_handle(new_private_tobj, priv_persistent_handle);
tobject_set_persistent_handle(new_public_tobj, pub_persistent_handle);
tobject_set_esys_tr(new_private_tobj, priv_esys_tr);
tobject_set_esys_tr(new_public_tobj, pub_esys_tr);

} else if (keygen_mode == keygen_mode_kobjs) { /* The key to import includes both public and private blobs */

attr_ptr = attr_get_attribute_by_type(new_public_tobj->attrs, CKA_TPM2_PUB_BLOB);
pub_blob = twistbin_new(attr_ptr->pValue, attr_ptr->ulValueLen);

attr_ptr = attr_get_attribute_by_type(new_private_tobj->attrs, CKA_TPM2_PRIV_BLOB);
priv_blob = twistbin_new(attr_ptr->pValue, attr_ptr->ulValueLen);

rv = tpm_loadobj(tok->tctx, tok->pobject.handle, tok->pobject.objauth,
pub_blob, NULL, &pub_esys_tr);
if (rv != CKR_OK) {
LOGE("Failed to load key objects");
goto out;
}

rv = tpm_loadobj(tok->tctx, tok->pobject.handle, tok->pobject.objauth,
pub_blob, priv_blob, &priv_esys_tr);
if (rv != CKR_OK) {
LOGE("Failed to load key objects");
goto out;
}

tobject_set_esys_tr(new_private_tobj, priv_esys_tr);
tobject_set_esys_tr(new_public_tobj, pub_esys_tr);

rv = tobject_set_blob_data(new_private_tobj, pub_blob, priv_blob);
if (rv != CKR_OK) {
goto out;
}

rv = tobject_set_blob_data(new_public_tobj, pub_blob, NULL);
if (rv != CKR_OK) {
goto out;
}
} else { /* Should not reach here */
assert(0);
}

/* Populate the mandatory attributes based on the TPM key */
rv = tpm_parse_key_to_attrs(tok->tctx, priv_esys_tr, mechanism,
new_public_tobj->attrs, new_private_tobj->attrs,
&objdata);
if (rv != CKR_OK) {
goto out;
}
}

rv = tobject_set_blob_data(new_public_tobj, objdata.pubblob, NULL);
if (rv != CKR_OK) {
goto out;
if (newauthhex) {
rv = utils_ctx_wrap_objauth(tok->wrappingkey, newauthhex, &newwrapped_auth);
if (rv != CKR_OK) {
LOGE("Failed to wrap new object auth");
goto out;
}

/* populate auth data, public objects do not need an auth */
rv = tobject_set_auth(new_private_tobj, newauthhex, newwrapped_auth);
if (rv != CKR_OK) {
goto out;
}
}

/* populate auth data, public objects do not need an auth */
rv = tobject_set_auth(new_private_tobj, newauthhex, newwrapped_auth);
/*
* objects have default required attributes, add them if not present.
*/
rv = attr_add_missing_attrs(&new_public_tobj->attrs, &new_private_tobj->attrs,
objdata.attrs, mechanism->mechanism);
if (rv != CKR_OK) {
LOGE("Failed to add missing rsa attrs");
goto out;
}

Expand Down Expand Up @@ -317,6 +471,8 @@ CK_RV key_gen (
tpm_objdata_free(&objdata);
twist_free(newauthhex);
twist_free(newwrapped_auth);
twist_free(pub_blob);
twist_free(priv_blob);
attr_list_free(pubkey_templ_w_types);
attr_list_free(privkey_templ_w_types);

Expand Down
Loading

0 comments on commit f333474

Please sign in to comment.