diff --git a/.ci/docker.run b/.ci/docker.run index 54335c8b..4c625115 100755 --- a/.ci/docker.run +++ b/.ci/docker.run @@ -64,7 +64,7 @@ fi # avoid error checking or cause make distcheck to fail. So run a # pure check with gcc before adding those flags. if [[ "$CC" != clang* ]]; then - ./configure --enable-esapi-session-manage-flags --disable-fapi --enable-unit --enable-integration + ./configure --enable-esapi-session-manage-flags --without-fapi --enable-unit --enable-integration make distcheck TESTS= make distclean fi @@ -84,7 +84,7 @@ if [[ "$CC" != clang* ]]; then # rebuild after running scan-build. fi -../configure --enable-unit --enable-integration --enable-esapi-session-manage-flags --enable-fapi $config_flags +../configure --enable-unit --enable-integration --enable-esapi-session-manage-flags --with-fapi $config_flags make -j$(nproc) make -j check diff --git a/Makefile.am b/Makefile.am index 9fa6cd0f..c1ecb866 100644 --- a/Makefile.am +++ b/Makefile.am @@ -5,11 +5,13 @@ ACLOCAL_AMFLAGS = -I m4 --install AM_CFLAGS = $(INCLUDE_DIRS) $(EXTRA_CFLAGS) $(CODE_COVERAGE_CFLAGS) \ $(TSS2_ESYS_CFLAGS) $(TSS2_MU_CFLAGS) $(TSS2_TCTILDR_CFLAGS) \ $(TSS2_RC_CFLAGS) $(SQLITE3_CFLAGS) $(PTHREAD_CFLAGS) \ - $(CRYPTO_CFLAGS) $(YAML_CFLAGS) $(TSS2_FAPI_CFLAGS) + $(CRYPTO_CFLAGS) $(YAML_CFLAGS) $(TSS2_FAPI_CFLAGS) \ + $(TSS2_POLICY_CFLAGS) AM_LDFLAGS = $(EXTRA_LDFLAGS) $(CODE_COVERAGE_LIBS) $(TSS2_ESYS_LIBS) \ $(TSS2_MU_LIBS) $(TSS2_TCTILDR_LIBS) $(TSS2_RC_LIBS) \ - $(SQLITE3_LIBS) $(PTHREAD_LIBS) $(CRYPTO_LIBS) $(YAML_LIBS) $(TSS2_FAPI_LIBS) + $(SQLITE3_LIBS) $(PTHREAD_LIBS) $(CRYPTO_LIBS) $(YAML_LIBS) \ + $(TSS2_FAPI_LIBS) $(TSS2_POLICY_LIBS) check-programs: $(check_PROGRAMS) diff --git a/configure.ac b/configure.ac index 771004b8..756884f0 100644 --- a/configure.ac +++ b/configure.ac @@ -56,6 +56,29 @@ PKG_CHECK_MODULES([TSS2_MU], [tss2-mu]) PKG_CHECK_MODULES([TSS2_TCTILDR], [tss2-tctildr]) PKG_CHECK_MODULES([TSS2_RC], [tss2-rc]) +AC_ARG_WITH( + [policy], + [AS_HELP_STRING([--with-policy], + [enable or disable policy support. Default is "auto" to autodetect])], + [enable_policy=$withval], + [enable_policy=auto]) + +AC_DEFUN([do_policy_configure], [ + # tss2-policy was added in tpm2-tss v4.0 + AS_IF([test "x$enable_policy" = "xauto"], + [ PKG_CHECK_MODULES([TSS2_POLICY], [tss2-policy], [have_policy=1], [have_policy=0]) ], + [ PKG_CHECK_MODULES([TSS2_POLICY], [tss2-policy], [have_policy=1]) ] + ) +]) + +AS_IF([test "x$enable_policy" != "xno"], + [do_policy_configure]) + +AS_IF([test "$have_policy" = "1"], [ + AC_DEFINE([HAVE_POLICY], [1], [Should respect policies and libpolicy is found.]) + AC_SUBST(TSS2_POLICY_DEP, [tss2-policy]) +]) + # Macro that checks for existence of a python module AC_DEFUN([AC_PYTHON_MODULE], [AC_MSG_CHECKING([for module $2 in python]) diff --git a/docs/BUILDING.md b/docs/BUILDING.md index 5c725d29..bc68dd16 100644 --- a/docs/BUILDING.md +++ b/docs/BUILDING.md @@ -12,7 +12,7 @@ The project depends on: 1. [gcc](https://www.gnu.org/software/gcc/) 2. [clang](https://clang.llvm.org/) 4. [SQLite3](https://www.sqlite.org/) -5. [tpm2-tss](https://github.com/tpm2-software/tpm2-tss): **Requires >= 2.0, recommended >= 2.3.0** +5. [tpm2-tss](https://github.com/tpm2-software/tpm2-tss): **Requires >= 2.0, recommended >= 4.0.1** 6. A Resource Manager, one of: - [tpm2-abrmd](https://github.com/tpm2-software/tpm2-abrmd): **Requires version >= 2.1.0** - Linux kernel version >= 4.12 for `/dev/tpmrm[0-9]+` nodes. diff --git a/lib/tpm2-pkcs11.pc.in b/lib/tpm2-pkcs11.pc.in index 34bc26dc..b75cfc55 100644 --- a/lib/tpm2-pkcs11.pc.in +++ b/lib/tpm2-pkcs11.pc.in @@ -4,7 +4,7 @@ Name: tpm2-pkcs11 Description: TPM2 PKCS#11 library URL: https://github.com/tpm2-software/tpm2-pkcs11 Version: @VERSION@ -Requires.private: tss2-esys tss2-mu sqlite3 libcrypto +Requires.private: @TSS2_POLICY_DEP@ tss2-esys tss2-mu sqlite3 libcrypto Cflags: @PTHREAD_CFLAGS@ Libs: -L${p11_module_path} -ltpm2_pkcs11 Libs.private: @PTHREAD_LIBS@ diff --git a/src/lib/attrs.c b/src/lib/attrs.c index 0c81e6a4..2ddcd1b1 100644 --- a/src/lib/attrs.c +++ b/src/lib/attrs.c @@ -171,6 +171,7 @@ static attr_handler2 attr_handlers[] = { 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_POLICY_JSON, TYPE_BYTE_HEX_STR), }; static attr_handler2 default_handler = { .memtype = 0, .name="UNKNOWN" }; diff --git a/src/lib/attrs.h b/src/lib/attrs.h index c0c542c5..7cf4e1aa 100644 --- a/src/lib/attrs.h +++ b/src/lib/attrs.h @@ -15,6 +15,7 @@ #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_POLICY_JSON (CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x5UL) /* Invalid values for error detection */ #define CK_OBJECT_CLASS_BAD (~(CK_OBJECT_CLASS)0) diff --git a/src/lib/sign.c b/src/lib/sign.c index 20494eaf..bd91a886 100644 --- a/src/lib/sign.c +++ b/src/lib/sign.c @@ -7,7 +7,12 @@ #include #include #include - #include +#include + +#ifdef HAVE_POLICY +#include +#include +#endif #include "attrs.h" #include "backend.h" @@ -149,6 +154,42 @@ static CK_RV update_pss_sig_state(token *tok, tobject *tobj) { return rv; } +static CK_RV policy_is_satisfied(tpm_ctx *tctx, tobject *tobj, uint32_t handle) { + + CK_ATTRIBUTE_PTR policy_attr = attr_get_attribute_by_type(tobj->attrs, CKA_TPM2_POLICY_JSON); + if (!policy_attr) { + /* nonexistent policy is always satisfied */ + return CKR_OK; + } + +#ifndef HAVE_POLICY + UNUSED(tctx); + UNUSED(handle); + /* the policy is considered to be satisfied, but will warn about it */ + LOGW("Found a policy, but support for enforcing it wasn't compiled in"); + return CKR_OK; +#else + TSS2_RC rc; + + TSS2_POLICY_CTX *policy_ctx = NULL; + rc = Tss2_PolicyInit(policy_attr->pValue, TPM2_ALG_SHA256, &policy_ctx); + if (rc != TSS2_RC_SUCCESS) { + LOGE("Tss2_PolicyInit: %s:", Tss2_RC_Decode(rc)); + return CKR_GENERAL_ERROR; + } + + CK_RV rv = tpm2_execute_policy(tctx, policy_ctx, handle); + if (rv != CKR_OK) { + LOGE("Could not execute policy."); + Tss2_PolicyFinalize(&policy_ctx); + return CKR_GENERAL_ERROR; + } + + Tss2_PolicyFinalize(&policy_ctx); + return CKR_OK; +#endif +} + static CK_RV common_init(operation op, session_ctx *ctx, CK_MECHANISM_PTR mechanism, CK_OBJECT_HANDLE key) { check_pointer(mechanism); @@ -211,6 +252,13 @@ static CK_RV common_init(operation op, session_ctx *ctx, CK_MECHANISM_PTR mechan return rv; } + if (op == operation_sign) { + rv = policy_is_satisfied(tok->tctx, tobj, tok->pobject.handle); + if (rv != CKR_OK) { + return rv; + } + } + tpm_op_data *tpm_opdata = NULL; if (op == operation_sign || is_hmac) { diff --git a/src/lib/tpm.c b/src/lib/tpm.c index 16466240..e8900ea9 100644 --- a/src/lib/tpm.c +++ b/src/lib/tpm.c @@ -24,6 +24,9 @@ #include #include #include +#ifdef HAVE_POLICY +#include +#endif #include "attrs.h" #include "checks.h" @@ -4008,6 +4011,111 @@ CK_RV tpm2_getmechanisms(tpm_ctx *ctx, CK_MECHANISM_TYPE *mechanism_list, CK_ULO return rv; } +#ifdef HAVE_POLICY +static TSS2_RC tpm2_policy_get_pcr(TSS2_POLICY_PCR_SELECTION *selection, + TPML_PCR_SELECTION *out_selection, + TPML_DIGEST *out_digest, + void *userdata) +{ + + TPML_PCR_SELECTION in_pcr_selection = {0}; + if (selection->type == TSS2_POLICY_PCR_SELECTOR_PCR_SELECTION) { + in_pcr_selection = selection->selections.pcr_selection; + } else { + in_pcr_selection.count = 1; + + TPMS_PCR_SELECTION *pcr_bank = &in_pcr_selection.pcrSelections[0]; + TPMS_PCR_SELECT *pcr_select = &selection->selections.pcr_select; + + pcr_bank->hash = TPM2_ALG_SHA256; + pcr_bank->sizeofSelect = pcr_select->sizeofSelect; + memcpy(pcr_bank->pcrSelect, pcr_select->pcrSelect, pcr_bank->sizeofSelect); + } + + ESYS_CONTEXT *esys_ctx = userdata; + + UINT32 pcr_update_counter; + TPML_PCR_SELECTION *pcr_selection = NULL; + TPML_DIGEST *pcr_values = NULL; + + TSS2_RC rc = Esys_PCR_Read(esys_ctx, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + &in_pcr_selection, + &pcr_update_counter, + &pcr_selection, + &pcr_values); + if (rc != TSS2_RC_SUCCESS) { + LOGE("Esys_PCR_Read: %s:", Tss2_RC_Decode(rc)); + free(pcr_selection); + free(pcr_values); + return rc; + } + + *out_selection = *pcr_selection; + *out_digest = *pcr_values; + + free(pcr_selection); + free(pcr_values); + return TSS2_RC_SUCCESS; +} + +CK_RV tpm2_execute_policy(tpm_ctx *ctx, TSS2_POLICY_CTX *policy_ctx, uint32_t handle) +{ + + check_pointer(ctx); + check_pointer(policy_ctx); + check_num(handle); + + TPMT_SYM_DEF symmetric = { + .algorithm = TPM2_ALG_AES, + .keyBits = { .aes = 128 }, + .mode = { .aes = TPM2_ALG_CFB } + }; + + TSS2_POLICY_CALC_CALLBACKS calc_callbacks = {0}; + calc_callbacks.cbpcr = &tpm2_policy_get_pcr; + calc_callbacks.cbpcr_userdata = ctx->esys_ctx; + + TSS2_RC rc; + + rc = Tss2_PolicySetCalcCallbacks(policy_ctx, &calc_callbacks); + if (rc != TSS2_RC_SUCCESS) { + LOGE("Tss2_PolicySetCalcCallbacks: %s:", Tss2_RC_Decode(rc)); + return CKR_GENERAL_ERROR; + } + + /* XXX should we cache the session or running multiple policies is unlikely? */ + ESYS_TR policy_session = ESYS_TR_NONE; + rc = Esys_StartAuthSession(ctx->esys_ctx, + handle, + handle, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + NULL, + TPM2_SE_POLICY, &symmetric, TPM2_ALG_SHA256, + &policy_session); + if (rc != TSS2_RC_SUCCESS) { + LOGE("Esys_StartAuthSession: %s", Tss2_RC_Decode(rc)); + return CKR_GENERAL_ERROR; + } + + TSS2_RC result = Tss2_PolicyExecute(policy_ctx, ctx->esys_ctx, policy_session); + if (result != TSS2_RC_SUCCESS) { + LOGE("Tss2_PolicyExecute: %s:", Tss2_RC_Decode(result)); + /* continue and stop the session */ + } + + rc = Esys_FlushContext(ctx->esys_ctx, policy_session); + if (rc != TSS2_RC_SUCCESS) { + LOGE("Esys_FlushContext: %s", Tss2_RC_Decode(rc)); + return CKR_GENERAL_ERROR; + } + + return result; +} +#endif + void tpm_init(void) { /* pass nothing to do */ } diff --git a/src/lib/tpm.h b/src/lib/tpm.h index 24246082..339f44be 100644 --- a/src/lib/tpm.h +++ b/src/lib/tpm.h @@ -7,6 +7,9 @@ #include #include +#ifdef HAVE_POLICY +#include +#endif #include "attrs.h" #include "debug.h" @@ -189,6 +192,11 @@ CK_RV tpm2_generate_key( CK_RV tpm2_getmechanisms(tpm_ctx *ctx, CK_MECHANISM_TYPE *mechanism_list, CK_ULONG_PTR count); +/* the function can't be defined without the library because of TSS2_POLICY_CTX type */ +#ifdef HAVE_POLICY +CK_RV tpm2_execute_policy(tpm_ctx *ctx, TSS2_POLICY_CTX *policy_ctx, uint32_t handle); +#endif + CK_RV tpm_get_existing_primary(tpm_ctx *tpm, uint32_t *primary_handle, twist *primary_blob); CK_RV tpm_create_persistent_primary(tpm_ctx *tpm, uint32_t *primary_handle, twist *primary_blob); diff --git a/tools/tpm2_pkcs11/commandlets_keys.py b/tools/tpm2_pkcs11/commandlets_keys.py index 86d1ed8b..40cafea5 100644 --- a/tools/tpm2_pkcs11/commandlets_keys.py +++ b/tools/tpm2_pkcs11/commandlets_keys.py @@ -28,6 +28,7 @@ from .utils import dump_blobs from .utils import dump_tsspem from .utils import dump_pubpem +from .utils import validate_policy from .tpm2 import Tpm2 @@ -58,6 +59,9 @@ def generate_options(self, group_parser): '--hierarchy-auth', help='The hierarchyauth, required for transient pobjects.\n', default='') + group_parser.add_argument( + '--policy', + help='Policy to apply on using the key (in JSON format).\n') pinopts = group_parser.add_mutually_exclusive_group() pinopts.add_argument('--sopin', help='The Administrator pin.\n'), pinopts.add_argument('--userpin', help='The User pin.\n'), @@ -173,6 +177,7 @@ def __call__(self, args): key_label = args['key_label'] tid = args['id'] hierarchyauth = args['hierarchy_auth'] + policy = args['policy'] passin = args['passin'] if 'passin' in args else None privkey = None @@ -205,6 +210,9 @@ def __call__(self, args): # handle options that can add additional attributes always_auth = args['attr_always_authenticate'] priv_attrs = {CKA_ALWAYS_AUTHENTICATE : always_auth} + if policy is not None: + validate_policy(policy) + priv_attrs[CKA_TPM2_POLICY_JSON] = binascii.hexlify(policy.encode()).decode() override_keylen = getattr(self, '_override_keylen', None) @@ -551,6 +559,67 @@ def __call__(self, args): ObjDel.delete(path, args['id']) +@commandlet("objpol") +class ObjPol(Command): + ''' + Gets/sets/removes object's policy. + ''' + + @classmethod + def objpol(cls, path, tid, policy, delete): + + with Db(path) as db: + obj = db.getobject(tid) + if obj is None: + sys.exit('Not found, object with id: {}'.format(tid)) + s = obj['attrs'] + obj_attrs = yaml.safe_load(s) + + # print policy when neither --policy nor --delete is specified + if policy is None and not delete: + if CKA_TPM2_POLICY_JSON not in obj_attrs: + sys.exit('The object has no policy set') + + x = obj_attrs[CKA_TPM2_POLICY_JSON] + print(binascii.unhexlify(x).decode()) + sys.exit() + + if delete: + # remove the policy even if it's wasn't yet set + obj_attrs.pop(CKA_TPM2_POLICY_JSON, None) + else: + # set policy if --policy is a well-formed JSON + validate_policy(policy) + obj_attrs[CKA_TPM2_POLICY_JSON] = binascii.hexlify(policy.encode()).decode() + + with Db(path) as db: + db.updatetertiary(obj['id'], obj_attrs) + + # adhere to an interface + def generate_options(self, group_parser): + + group_parser.add_argument( + '--id', + help='The id of the object to use.\n', required=True) + group_parser.add_argument( + '--policy', + help='New policy value as JSON.\n') + group_parser.add_argument( + '--delete', + action='store_true', + help='Removes policy if the object has it.\n') + + def __call__(self, args): + + path = args['path'] + policy = args['policy'] + delete = args['delete'] + + if policy and delete: + sys.exit("Cannot specify --policy with --delete") + + ObjPol.objpol(path, args['id'], policy, delete) + @commandlet("link") class LinkCommand(NewKeyCommandBase): ''' @@ -759,7 +828,7 @@ def export(db, tid, pin, is_so_pin, hierarchyauth, format, output_prefix): def generate_options(self, group_parser): group_parser.add_argument( - '--id', help='The id of the object to add (mutually exclusive with --label and --key-label).\n', + '--id', help='The id of the object to export (mutually exclusive with --label and --key-label).\n', type=int) group_parser.add_argument( diff --git a/tools/tpm2_pkcs11/pkcs11t.py b/tools/tpm2_pkcs11/pkcs11t.py index 36598933..a06cf90e 100644 --- a/tools/tpm2_pkcs11/pkcs11t.py +++ b/tools/tpm2_pkcs11/pkcs11t.py @@ -110,6 +110,8 @@ CKA_TPM2_OBJAUTH_ENC=CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x1 CKA_TPM2_PUB_BLOB=CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x2 CKA_TPM2_PRIV_BLOB=CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x3 +CKA_TPM2_ENC_BLOB=CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x4 +CKA_TPM2_POLICY_JSON=CKA_VENDOR_DEFINED|CKA_VENDOR_TPM2_DEFINED|0x5 CKC_X_509 = 0 diff --git a/tools/tpm2_pkcs11/utils.py b/tools/tpm2_pkcs11/utils.py index ded80335..fb6bfeef 100644 --- a/tools/tpm2_pkcs11/utils.py +++ b/tools/tpm2_pkcs11/utils.py @@ -2,6 +2,7 @@ import binascii import hashlib import io +import json import os import argparse import sys @@ -555,3 +556,9 @@ def dump_pubpem(db, obj, pin, is_sopin, output_prefix): with open(output_prefix + ".pem", "wb") as f: f.write(pub_blob.to_pem()) +def validate_policy(policy): + try: + # discarding result as this is just a JSON sanity check + json.loads(policy) + except json.JSONDecodeError: + sys.exit('Object policy must be a valid JSON')