diff --git a/docs/OPENSSL.md b/docs/OPENSSL.md new file mode 100644 index 00000000..c6fa8468 --- /dev/null +++ b/docs/OPENSSL.md @@ -0,0 +1,110 @@ +# Using TPM2 PKCS11 with OpenSSL + +## Introduction + +OpenSSL has the ability to load dynamic engines to control where the underlying cryptographic +operations occur. Additionally, [OpenSC LibP11](https://github.com/OpenSC/libp11) has an engine +that can load arbitrary PKCS11 libraries. Thus, through a few layer of indirections, you can use +OpenSSL with the tpm2-pkcs11 library. This is a less complete tutorial, and assumes you have +been through the [pkcs11-tool](PKCS11_TOOL.md) tutorial. + +## Setup + +Yubico has very complete instructions for configuring, see: + - https://developers.yubico.com/YubiHSM2/Usage_Guides/OpenSSL_with_pkcs11_engine.html + +These instructions are mostly applicable, the only thing that needs to change is the +MODULE_PATH needs to point to the tpm2-pkcs11 library. + +A sample OSSL config file is provided at [openssl.conf](../misc/tpm2-pkcs11.openssl.sample.conf). + +Note: + - The Yubico instructions use rand. Rand will not hit the tpm because the OpenSC engine does + not support it. + - Systems with PKCS11 kit installed may need additional tweaks to get it to work. + +For the examples below, I followed Yubico's advice and set up an alias in my `~/.bashrc` file +called tpm2ssl, like so: +```bash +alias tpm2ssl='OPENSSL_CONF=$HOME/tpm2-pkcs11.openssl.conf openssl' +``` + +You'll also need to set up a `TPM2_PKCS11_STORE` and have an assymetric keypair generated. This is all +shown in the [pkcs11-tool](PKCS11_TOOL.md) tutorial. + + +## Generating a Certificate + +First thing first, identify the private key to use to generate the certificate or CSR from. With pkcs11-tool, one can +do: +```bash +pkcs11-tool --module /path/to/libtpm2_pkcs11.so --slot 1 --login -O +``` + +Note: You need to login to see private key objects. + +You should see output like so: +```bash +Private Key Object; EC + label: 12 + ID: 61366566303531343635636138663035 + Usage: decrypt, sign +``` + +You're key could be RSA, thats fine. The imporant thing is to look at the label and pass it via the -key argument +in openssl below. Note that this is key 12 in slot 1. + +To generate a self signed certificate, one can simply use the `req` command with openssl. + +``` bash +tpm2ssl req -new -x509 -days 365 -subj '/CN=my key/' -sha256 -engine pkcs11 -keyform engine -key slot_1-label_12 -out cert.pem +engine "pkcs11" set. +Enter PKCS#11 token PIN for label: +``` + +## Generating a CSR + +Most users will likely require certificates signed by a CA. To do this, we can generate a CSR that gets uploaded to a CA. The CA +then returns the certificate. The steps to upload the CSR and retrieve it from the CA are CA dependent and will not be covered +here. + +```bash +tpm2ssl req -new -subj '/CN=my key/' -sha256 -engine pkcs11 -keyform engine -key slot_1-label_12 -out csr.pem +``` + +The next steps after generating the CSR would be to go to a CA, get a certificate generated and signed using it. + +Note: + - OpenSSL config file can be used for specifying default values when using the req command. + [See the man page](https://www.openssl.org/docs/man1.1.1/man1/openssl-req.html). + + +## Inserting a Certificate + +At this point, you have a certificate, either self signed or signed via a CA. The certificate may be in many forms, +if you followed the steps above, it's in the PEM format. You need to convert any version you have to PEM, information +on how to do this can be found at: + - https://knowledge.digicert.com/solution/SO26449.html + +Once you obtain a PEM certificate, you can use the ptool command. + +```bash +tpm2_ptool addcert --label=todo --id=todo cert.pem +``` + +## Listing a Certficiate + +After this, veiw your cert object, you can again just list the objects with pkcs11-tool. + +```bash +pkcs11-tool --module /path/to/libtpm2_pkcs11.so --slot 1 --login -O +``` + +You should see output like: + +```bash +Certificate Object; type = X.509 cert + label: 12 + subject: DN: CN=my key + ID: 3132 +``` \ No newline at end of file diff --git a/misc/tpm2-pkcs11.openssl.sample.conf b/misc/tpm2-pkcs11.openssl.sample.conf new file mode 100644 index 00000000..ab865401 --- /dev/null +++ b/misc/tpm2-pkcs11.openssl.sample.conf @@ -0,0 +1,23 @@ +openssl_conf = openssl_init + +[openssl_init] +engines = engine_section + +[engine_section] +pkcs11 = pkcs11_section + +[pkcs11_section] +engine_id = pkcs11 +# This is the path to engine that openssl loads +dynamic_path = /path/to/pkcs11.so +# this is the path to the pkcs11 implementation that the pkcs11 engine loads +MODULE_PATH = path/to/libtpm2_pkcs11.so +init = 0 + +[ req ] +distinguished_name = req_dn +string_mask = utf8only +utf8 = yes + +[ req_dn ] +commonName = Mr. Sample Config diff --git a/src/lib/db.c b/src/lib/db.c index 617c21d0..25cc9b09 100644 --- a/src/lib/db.c +++ b/src/lib/db.c @@ -212,6 +212,7 @@ static bool parse_attrs(const char *key, const char *value, size_t index, void * } a->type = type; + bool allow_empty_value = false; switch(a->type) { /* CK_BBOOLs */ @@ -277,6 +278,15 @@ static bool parse_attrs(const char *key, const char *value, size_t index, void * } break; /* native endianess CK_ULONGs */ + case CKA_NAME_HASH_ALGORITHM: + allow_empty_value = true; + /* falls through */ + case CKA_JAVA_MIDP_SECURITY_DOMAIN: + /* falls through */ + case CKA_CERTIFICATE_CATEGORY: + /* falls through */ + case CKA_CERTIFICATE_TYPE: + /* falls through */ case CKA_MODULUS_BITS: /* falls through */ case CKA_KEY_TYPE: @@ -285,6 +295,15 @@ static bool parse_attrs(const char *key, const char *value, size_t index, void * /* falls through */ case CKA_CLASS: { + if (!strlen(value)) { + if (allow_empty_value) { + LOGE("Value for key \"%s\" must be specified", key); + return false; + } + a->ulValueLen = 0; + break; + } + size_t val; rc = str_to_ul(value, &val); if (rc) { @@ -354,6 +373,22 @@ static bool parse_attrs(const char *key, const char *value, size_t index, void * } break; /* hex-strings */ + case CKA_PUBLIC_KEY_INFO: + /* falls-thru */ + case CKA_HASH_OF_ISSUER_PUBLIC_KEY: + /* falls-thru */ + case CKA_HASH_OF_SUBJECT_PUBLIC_KEY: + /* falls-thru */ + case CKA_CHECK_VALUE: + /* falls-thru */ + case CKA_SERIAL_NUMBER: + /* falls-thru */ + case CKA_ISSUER: + /* falls-thru */ + case CKA_VALUE: + /* falls-thru */ + case CKA_SUBJECT: + /* falls-thru */ case CKA_ID: /* falls-thru */ case CKA_LABEL: { @@ -378,6 +413,35 @@ static bool parse_attrs(const char *key, const char *value, size_t index, void * a->ulValueLen = len; a->pValue = label; } break; + + /* strings */ + case CKA_URL: { + assert(value); + a->ulValueLen = strlen(value); + a->pValue = calloc(1, a->ulValueLen + 1); + if (!a->pValue) { + LOGE("oom"); + return false; + } + } break; + + /* CK_DATE's */ + case CKA_START_DATE: + /* falls through */ + case CKA_END_DATE: + /* falls through */ + if (strlen(value)) { + LOGE("Cannot handle specified CK_DATE types"); + return false; + } + + a->ulValueLen = strlen(value); + a->pValue = calloc(1, a->ulValueLen + 1); + if (!a->pValue) { + LOGE("oom"); + return false; + } + break; default: LOGE("Unknown key, got: \"%s\"", key); return false; @@ -627,11 +691,14 @@ tobject *db_tobject_new(sqlite3_stmt *stmt) { goto_error(get_blob_null(stmt, i, &tobj->priv), error); } else if (!strcmp(name, "pub")) { - goto_error(get_blob(stmt, i, &tobj->pub), error); + goto_error(get_blob_null(stmt, i, &tobj->pub), error); } else if (!strcmp(name, "objauth")) { - tobj->objauth = twist_new((char *)sqlite3_column_text(stmt, i)); - goto_oom(tobj->objauth, error); + char *x = (char *)sqlite3_column_text(stmt, i); + if (x) { + tobj->objauth = twist_new(x); + goto_oom(tobj->objauth, error); + } } else if (!strcmp(name, "attrs")) { const char *attrs = (const char *)sqlite3_column_text(stmt, i); diff --git a/src/lib/token.c b/src/lib/token.c index 31fe5882..a5ff8df7 100644 --- a/src/lib/token.c +++ b/src/lib/token.c @@ -367,6 +367,8 @@ CK_RV token_initpin(token *tok, CK_UTF8CHAR_PTR newpin, CK_ULONG newlen) { return rv; } +UTILS_GENERIC_ATTR_TYPE_CONVERT(CK_OBJECT_CLASS); + CK_RV token_load_object(token *tok, CK_OBJECT_HANDLE key, tobject **loaded_tobj) { tpm_ctx *tpm = tok->tctx; @@ -388,6 +390,28 @@ CK_RV token_load_object(token *tok, CK_OBJECT_HANDLE key, tobject **loaded_tobj) return rv; } + /* this might not be the best place for this check */ + CK_ATTRIBUTE_PTR a = util_get_attribute_by_type(CKA_CLASS, + tobj->attrs.attrs, tobj->attrs.count); + if (!a) { + LOGE("All objects expected to have CKO_CERTIFICATE, missing" + " for tobj id: %u", tobj->id); + return CKR_GENERAL_ERROR; + } + + CK_OBJECT_CLASS v; + rv = generic_CK_OBJECT_CLASS(a, &v); + if (rv != CKR_OK) { + return rv; + } + + if (v != CKO_PRIVATE_KEY + && v != CKO_PUBLIC_KEY + && v != CKO_SECRET_KEY) { + LOGE("Cannot use tobj id %u in a crypto operation: %u", tobj->id); + return CKR_KEY_HANDLE_INVALID; + } + // Already loaded, ignored. if (tobj->handle) { *loaded_tobj = tobj; diff --git a/src/lib/utils.c b/src/lib/utils.c index 8964a725..375940aa 100644 --- a/src/lib/utils.c +++ b/src/lib/utils.c @@ -598,6 +598,21 @@ CK_RV utils_attr_free(CK_ATTRIBUTE_PTR attrs, CK_ULONG attr_count) { { CKA_UNWRAP, generic_attr_free }, { CKA_WRAP_WITH_TRUSTED, generic_attr_free }, { CKA_DERIVE, generic_attr_free }, + { CKA_VALUE, generic_attr_free }, + { CKA_CERTIFICATE_TYPE, generic_attr_free }, + { CKA_ISSUER, generic_attr_free }, + { CKA_SERIAL_NUMBER, generic_attr_free }, + { CKA_CERTIFICATE_CATEGORY, generic_attr_free }, + { CKA_JAVA_MIDP_SECURITY_DOMAIN, generic_attr_free }, + { CKA_URL, generic_attr_free }, + { CKA_CHECK_VALUE, generic_attr_free }, + { CKA_HASH_OF_SUBJECT_PUBLIC_KEY, generic_attr_free }, + { CKA_HASH_OF_ISSUER_PUBLIC_KEY, generic_attr_free }, + { CKA_NAME_HASH_ALGORITHM, generic_attr_free }, + { CKA_SUBJECT, generic_attr_free }, + { CKA_START_DATE, generic_attr_free }, + { CKA_END_DATE, generic_attr_free }, + { CKA_PUBLIC_KEY_INFO, generic_attr_free }, }; return utils_handle_attrs(free_attr_handlers, ARRAY_LEN(free_attr_handlers), attrs, attr_count, NULL); diff --git a/test/integration/pkcs-crypt.int.c b/test/integration/pkcs-crypt.int.c index 9b4538c3..6d65c4b3 100644 --- a/test/integration/pkcs-crypt.int.c +++ b/test/integration/pkcs-crypt.int.c @@ -590,6 +590,49 @@ static void test_aes_always_authenticate(void **state) { assert_memory_equal(plaintext, plaintext2, sizeof(plaintext2)); } +static void test_cert_no_good(void **state) { + + test_info *ti = test_info_from_state(state); + CK_SESSION_HANDLE session = ti->handle; + + CK_OBJECT_CLASS obj_class = CKO_CERTIFICATE; + CK_KEY_TYPE cert_type = CKC_X_509; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &obj_class, sizeof(obj_class) }, + { CKA_CERTIFICATE_TYPE, &cert_type, sizeof(cert_type) }, + }; + + CK_RV rv = C_FindObjectsInit(session, tmpl, ARRAY_LEN(tmpl)); + assert_int_equal(rv, CKR_OK); + + /* Find a cert */ + CK_ULONG count; + CK_OBJECT_HANDLE objhandles[1]; + rv = C_FindObjects(session, objhandles, ARRAY_LEN(objhandles), &count); + assert_int_equal(rv, CKR_OK); + assert_int_equal(count, 1); + + rv = C_FindObjectsFinal(session); + assert_int_equal(rv, CKR_OK); + + CK_BYTE iv[16] = { + 0xDE, 0xAD, 0xBE, 0xEF, + 0xDE, 0xAD, 0xBE, 0xEF, + 0xDE, 0xAD, 0xBE, 0xEF, + 0xDE, 0xAD, 0xBE, 0xEF, + }; + + CK_MECHANISM mechanism = { + CKM_AES_CBC, iv, sizeof(iv) + }; + + rv = C_EncryptInit(session, &mechanism, objhandles[0]); + assert_int_equal(rv, CKR_KEY_HANDLE_INVALID); + + rv = C_DecryptInit(session, &mechanism, objhandles[0]); + assert_int_equal(rv, CKR_KEY_HANDLE_INVALID); +} + int main() { const struct CMUnitTest tests[] = { @@ -605,6 +648,8 @@ int main() { test_setup, test_teardown), cmocka_unit_test_setup_teardown(test_rsa_oaep_encrypt_decrypt_oneshot_good, test_setup, test_teardown), + cmocka_unit_test_setup_teardown(test_cert_no_good, + test_setup, test_teardown), }; return cmocka_run_group_tests(tests, group_setup, group_teardown); diff --git a/test/integration/pkcs-get-attribute-value.int.c b/test/integration/pkcs-get-attribute-value.int.c index 35329421..727cd822 100644 --- a/test/integration/pkcs-get-attribute-value.int.c +++ b/test/integration/pkcs-get-attribute-value.int.c @@ -5,6 +5,8 @@ * All rights reserved. ***********************************************************************/ +#include + #include "test.h" struct test_info { @@ -121,7 +123,7 @@ static void test_get_attribute_value_multiple_okay(void **state) { pLabel = malloc(template[0].ulValueLen+1); //+1 for \0 memset(pLabel, '\0', template[0].ulValueLen+1); template[0].pValue = pLabel; - + pId = malloc(template[1].ulValueLen); template[1].pValue = pId; @@ -151,7 +153,7 @@ static void test_get_attribute_value_buffer_too_small(void **state) { //make buffer length too small template[0].ulValueLen--; - + rv = C_GetAttributeValue(ti->hSession, ti->hObject, template, 1); assert_int_equal(rv, CKR_BUFFER_TOO_SMALL); free(pLabel); @@ -199,13 +201,13 @@ static void test_get_attribute_value_multiple_fail(void **state) { pId = malloc(template[0].ulValueLen); template[0].pValue = pId; template[0].ulValueLen--; // make buffer too small - + // this one will be okay pLabel = malloc(template[1].ulValueLen+1); //+1 for \0 memset(pLabel, '\0', template[1].ulValueLen+1); template[1].pValue = pLabel; - - // invalid + + // invalid pInvalid = malloc(10); template[2].pValue = pInvalid; template[2].ulValueLen = 10; @@ -365,9 +367,192 @@ static void test_all_priv_rsa_obj_attrs(void **state) { } } +#define ADD_ATTR(a, b, index) .type = a, .ulValueLen = sizeof(b[0]), .pValue= b[index] + +static void verify_cert_attrs(CK_SESSION_HANDLE s, CK_OBJECT_HANDLE h) { + + CK_BYTE _buf[256][1024] = { 0 }; + CK_ATTRIBUTE attrs[] = { + { ADD_ATTR(CKA_CLASS, _buf, 0) }, + { ADD_ATTR(CKA_CERTIFICATE_TYPE, _buf, 1) }, + { ADD_ATTR(CKA_TRUSTED, _buf, 2) }, + { ADD_ATTR(CKA_CERTIFICATE_CATEGORY, _buf, 3) }, + { ADD_ATTR(CKA_CHECK_VALUE, _buf, 4) }, + { ADD_ATTR(CKA_START_DATE, _buf, 5) }, + { ADD_ATTR(CKA_END_DATE, _buf, 6) }, + { ADD_ATTR(CKA_PUBLIC_KEY_INFO, _buf, 7) }, + { ADD_ATTR(CKA_SUBJECT, _buf, 8) }, + { ADD_ATTR(CKA_LABEL, _buf, 9) }, + { ADD_ATTR(CKA_ISSUER, _buf, 10) }, + { ADD_ATTR(CKA_SERIAL_NUMBER, _buf, 11) }, + { ADD_ATTR(CKA_VALUE, _buf, 12) }, + { ADD_ATTR(CKA_URL, _buf, 13) }, + { ADD_ATTR(CKA_HASH_OF_SUBJECT_PUBLIC_KEY, _buf, 14) }, + { ADD_ATTR(CKA_HASH_OF_ISSUER_PUBLIC_KEY, _buf, 15) }, + { ADD_ATTR(CKA_JAVA_MIDP_SECURITY_DOMAIN, _buf, 16) }, + { ADD_ATTR(CKA_NAME_HASH_ALGORITHM, _buf, 17) }, + }; + + assert_true(ARRAY_LEN(attrs) <= ARRAY_LEN(_buf)); + + CK_RV rv = C_GetAttributeValue(s, h, attrs, ARRAY_LEN(attrs)); + assert_int_equal(rv, CKR_OK); + + CK_ATTRIBUTE_PTR value = NULL; + CK_ATTRIBUTE_PTR checkvalue = NULL; + CK_ATTRIBUTE_PTR subject = NULL; + + CK_ULONG i = 0; + for (i=0; i < ARRAY_LEN(attrs); i++) { + CK_ATTRIBUTE_PTR a = &attrs[i]; + + switch (a->type) { + case CKA_CLASS: { + assert_int_equal(a->ulValueLen, sizeof(CK_OBJECT_CLASS)); + CK_OBJECT_CLASS *v = (CK_OBJECT_CLASS *)a->pValue; + assert_int_equal(*v, CKO_CERTIFICATE); + } break; + case CKA_CERTIFICATE_TYPE: { + assert_int_equal(a->ulValueLen, sizeof(CK_CERTIFICATE_TYPE)); + CK_CERTIFICATE_TYPE *v = (CK_CERTIFICATE_TYPE *)a->pValue; + assert_int_equal(*v, CKC_X_509); + } break; + case CKA_TRUSTED: { + assert_int_equal(a->ulValueLen, sizeof(CK_BBOOL)); + CK_BBOOL *v = (CK_BBOOL *)a->pValue; + assert_int_equal(*v, CK_FALSE); + } break; + case CKA_CERTIFICATE_CATEGORY: { + assert_int_equal(a->ulValueLen, sizeof(CK_ULONG)); + CK_ULONG *v = (CK_ULONG *)a->pValue; + assert_int_equal(*v, CK_CERTIFICATE_CATEGORY_UNSPECIFIED); + } break; + case CKA_CHECK_VALUE: { + // check value is first three bytes of sha1 hash + assert_int_equal(a->ulValueLen, 3); + checkvalue = a; + } break; + case CKA_SUBJECT: + subject = a; + break; + case CKA_LABEL: + /* a label should be set ? */ + assert_true(a->ulValueLen > 0); + break; + case CKA_VALUE: + value = a; + break; + case CKA_JAVA_MIDP_SECURITY_DOMAIN: { + assert_int_equal(a->ulValueLen, sizeof(CK_JAVA_MIDP_SECURITY_DOMAIN)); + CK_JAVA_MIDP_SECURITY_DOMAIN *v = (CK_ULONG *)a->pValue; + assert_int_equal(*v, CK_SECURITY_DOMAIN_UNSPECIFIED); + } break; + case CKA_NAME_HASH_ALGORITHM: { + assert_int_equal(a->ulValueLen, sizeof(CK_MECHANISM_TYPE)); + CK_MECHANISM_TYPE *v = (CK_MECHANISM_TYPE *)a->pValue; + assert_int_equal(*v, CKM_SHA_1); + } break; + /* expected empty */ + case CKA_URL: + // falls-thru + case CKA_HASH_OF_SUBJECT_PUBLIC_KEY: + // falls-thru + case CKA_HASH_OF_ISSUER_PUBLIC_KEY: + // falls-thru + case CKA_PUBLIC_KEY_INFO: + // falls-thru + case CKA_ISSUER: + // falls-thru + case CKA_SERIAL_NUMBER: + // falls-thru + case CKA_START_DATE: + // falls-thru + case CKA_END_DATE: + assert_int_equal(a->ulValueLen, 0); + break; + default: + assert_true(0); + } + } + + assert_non_null(subject); + + assert_non_null(value); + assert_non_null(checkvalue); + + /* verify subject is an ASN1 encoded string */ + const unsigned char *tmp = subject->pValue; + X509_NAME *ss = d2i_X509_NAME(NULL, &tmp, subject->ulValueLen); + assert_non_null(ss); + X509_NAME_free(ss); + + /* verify check value */ + EVP_MD_CTX *mdctx = EVP_MD_CTX_create(); + assert_non_null(mdctx); + + int rc = EVP_DigestInit_ex(mdctx, EVP_sha1(), NULL); + assert_int_equal(rc, 1); + + rc = EVP_DigestUpdate(mdctx, value->pValue, value->ulValueLen); + assert_int_equal(rc, 1); + + unsigned char md[SHA_DIGEST_LENGTH]; + unsigned int len = sizeof(md); + rc = EVP_DigestFinal_ex(mdctx, md, &len); + assert_int_equal(rc, 1); + + EVP_MD_CTX_destroy(mdctx); + + assert_memory_equal(md, checkvalue->pValue, checkvalue->ulValueLen); +} + +static void test_all_cert_attrs(void **state) { + + test_info *ti = test_info_from_state(state); + CK_SESSION_HANDLE session = ti->hSession; + + CK_OBJECT_CLASS obj_class = CKO_CERTIFICATE; + CK_KEY_TYPE cert_type = CKC_X_509; + CK_ATTRIBUTE attrs[] = { + { CKA_CLASS, &obj_class, sizeof(obj_class) }, + { CKA_CERTIFICATE_TYPE, &cert_type, sizeof(cert_type) }, + }; + + /* verify we can find it via pub templ */ + CK_RV rv = C_FindObjectsInit(session, attrs, ARRAY_LEN(attrs)); + assert_int_equal(rv, CKR_OK); + + CK_OBJECT_HANDLE handles[255]; + CK_ULONG count = ARRAY_LEN(handles); + rv = C_FindObjects(session, handles, count, &count); + assert_int_equal(rv, CKR_OK); + assert_true(count > 0); + + /* make sure we got all of them */ + CK_ULONG left = 1; + CK_OBJECT_HANDLE dummy; + rv = C_FindObjects(session, &dummy, 1, &left); + assert_int_equal(rv, CKR_OK); + assert_int_equal(left, 0); + + rv = C_FindObjectsFinal(session); + assert_int_equal(rv, CKR_OK); + + CK_ULONG i; + for (i=0; i < count; i++) { + + CK_OBJECT_HANDLE h = handles[i]; + + /* verify missing attrs */ + verify_cert_attrs(session, h); + } +} + int main() { const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_all_cert_attrs, + test_setup, test_teardown), cmocka_unit_test_setup_teardown(test_all_pub_ecc_obj_attrs, test_setup, test_teardown), cmocka_unit_test_setup_teardown(test_all_priv_ecc_obj_attrs, diff --git a/test/integration/pkcs-sign-verify.int.c b/test/integration/pkcs-sign-verify.int.c index b86ab04d..9e9b9e07 100644 --- a/test/integration/pkcs-sign-verify.int.c +++ b/test/integration/pkcs-sign-verify.int.c @@ -921,6 +921,43 @@ static void test_sign_verify_context_specific_good(void **state) { assert_int_equal(rv, CKR_OK); } +static void test_cert_no_good(void **state) { + + test_info *ti = test_info_from_state(state); + CK_SESSION_HANDLE session = ti->handle; + + CK_OBJECT_CLASS obj_class = CKO_CERTIFICATE; + CK_KEY_TYPE cert_type = CKC_X_509; + CK_ATTRIBUTE tmpl[] = { + { CKA_CLASS, &obj_class, sizeof(obj_class) }, + { CKA_CERTIFICATE_TYPE, &cert_type, sizeof(cert_type) }, + }; + + CK_RV rv = C_FindObjectsInit(session, tmpl, ARRAY_LEN(tmpl)); + assert_int_equal(rv, CKR_OK); + + /* Find a cert */ + CK_ULONG count; + CK_OBJECT_HANDLE objhandles[1]; + rv = C_FindObjects(session, objhandles, ARRAY_LEN(objhandles), &count); + assert_int_equal(rv, CKR_OK); + assert_int_equal(count, 1); + + rv = C_FindObjectsFinal(session); + assert_int_equal(rv, CKR_OK); + + /* you cant do a sign without logging in */ + user_login(session); + + CK_MECHANISM mech = { .mechanism = CKM_SHA256_RSA_PKCS }; + + rv = C_SignInit(session, &mech, objhandles[0]); + assert_int_equal(rv, CKR_KEY_HANDLE_INVALID); + + rv = C_Logout(session); + assert_int_equal(rv, CKR_OK); +} + int main() { const struct CMUnitTest tests[] = { @@ -946,6 +983,8 @@ int main() { test_setup, test_teardown), cmocka_unit_test_setup_teardown(test_sign_verify_CKM_ECDSA, test_setup, test_teardown), + cmocka_unit_test_setup_teardown(test_cert_no_good, + test_setup, test_teardown), }; return cmocka_run_group_tests(tests, group_setup, group_teardown); diff --git a/test/integration/scripts/create_pkcs_store.sh b/test/integration/scripts/create_pkcs_store.sh index 60122172..bc615ed8 100755 --- a/test/integration/scripts/create_pkcs_store.sh +++ b/test/integration/scripts/create_pkcs_store.sh @@ -4,6 +4,30 @@ echo "SETUP SCRIPT - DBUS_SESSION_BUS_ADDRESS: $DBUS_SESSION_BUS_ADDRESS" echo "SETUP SCRIPT - TPM2TOOLS_TCTI: $TPM2TOOLS_TCTI" echo "SETUP SCRIPT - PYTHONPATH: $PYTHONPATH" +setup_asan() +{ + if [ "$ASAN_ENABLED" = "true" ]; then + # To get line numbers set up the asan symbolizer + clang_version=`$CC --version | head -n 1 | cut -d\ -f 3-3 | cut -d\. -f 1-3 | cut -d- -f 1-1` + # Sometimes the version string has an Ubuntu on the front of it and the field + # location changes + if [ $clang_version == "version" ]; then + clang_version=`$CC --version | head -n 1 | cut -d\ -f 4-4 | cut -d\. -f 1-3` + fi + echo "Detected clang version: $clang_version" + minor_maj=`echo "$clang_version" | cut -d\. -f 1-2` + export LD_PRELOAD=/usr/lib/llvm-$minor_maj/lib/clang/$clang_version/lib/linux/libclang_rt.asan-$(arch).so + echo "export LD_PRELOAD=\"$LD_PRELOAD\"" + export ASAN_OPTIONS=detect_leaks=0 + echo "turning off asan detection for running commands..." + fi +} + +clear_asan() { + unset LD_PRELOAD + unset ASAN_OPTIONS +} + usage_error () { echo "$0: $*" >&1 @@ -113,6 +137,58 @@ for i in `seq 0 1`; do done; echo "Added EC Keys" +echo "Adding 1 x509 Certificate under token \"label\"" + +# +# Build an OpenSSL config file +# +osslconf="$TPM2_PKCS11_STORE/ossl.cnf" +cat << EOF > "$osslconf" +openssl_conf = openssl_init + +[openssl_init] +engines = engine_section + +[engine_section] +pkcs11 = pkcs11_section + +[pkcs11_section] +engine_id = pkcs11 +MODULE_PATH = /workspace/tpm2-pkcs11/src/.libs/libtpm2_pkcs11.so +PIN=myuserpin +init = 0 + +[ req ] +distinguished_name = req_dn +string_mask = utf8only +utf8 = yes + +[ req_dn ] +commonName = Mr Test Harness +EOF + +export OPENSSL_CONF="$osslconf" + +# +# generate cert +# +cert="$TPM2_PKCS11_STORE/cert.pem" + +# since we use the shared lib in a non-asan executable via dlopen() we need to set up +# asan so we have defined symbols and we don't worry about leaks (since the tools are +# often silly and leak. +setup_asan +TPM2_PKCS11_STORE="$TPM2_PKCS11_STORE" openssl \ + req -new -x509 -days 365 -subj '/CN=my key/' -sha256 -engine pkcs11 -keyform engine -key slot_1-label_12 -out "$cert" +clear_asan + +# +# insert cert to token +# +tpm2_ptool addcert --label=label --key-label=12 --path=$TPM2_PKCS11_STORE "$cert" + +echo "added x509 Certificate" + # add 1 aes key under label "import-keys" tpm2_ptool addkey --algorithm=aes128 --label="import-keys" --userpin=anotheruserpin --path=$TPM2_PKCS11_STORE diff --git a/tools/tpm2_pkcs11/commandlets_keys.py b/tools/tpm2_pkcs11/commandlets_keys.py index b006379b..505409f2 100644 --- a/tools/tpm2_pkcs11/commandlets_keys.py +++ b/tools/tpm2_pkcs11/commandlets_keys.py @@ -1,9 +1,15 @@ # python stdlib dependencies import binascii +import hashlib import os import sys import yaml +from pyasn1_modules import pem, rfc2459, rfc5480, rfc4055 +from pyasn1.codec.der import decoder +from pyasn1.codec.ber import encoder as berenc +from pyasn1.codec.der import encoder as derenc + # local imports from .command import Command from .command import commandlet @@ -15,10 +21,12 @@ from .utils import rand_hex_str from .utils import get_ec_params from .utils import asn1_format_ec_point_uncompressed +from .utils import list_dict_from_kvp from .tpm2 import Tpm2 from .pkcs11t import * # noqa +from pyasn1_modules.rfc2459 import KeyIdentifier class NewKeyCommandBase(Command): @@ -486,3 +494,132 @@ def new_key_create(self, pobj, objauth, tpm2, path, alg, privkey, d): def __call__(self, args): keylabel = super(AddKeyCommand, self).__call__(args) print('Added key as label: "{keylabel}"'.format(keylabel=keylabel)) + + +@commandlet("addcert") +class AddCert(Command): + ''' + Adds a certificate object + ''' + + # adhere to an interface + # pylint: disable=no-self-use + def generate_options(self, group_parser): + group_parser.add_argument( + '--label', help='The profile label to remove.\n', required=True) + + group_parser.add_argument( + '--key-label', + help='The associated private key label.\n', + required=True) + + group_parser.add_argument( + 'cert', help='The x509 PEM certificate to add.\n') + + def __call__(self, args): + + path = args['path'] + label = args['label'] + keylabel = args['key_label'] + certpath = args['cert'] + + # rather than use pycryptography x509 parser, which gives native type access to certficiate + # fields use pyASN1 to get raw ASN1 encoded values for the fields as the spec requires them + with open(certpath, "rb") as f: + substrate = pem.readPemFromFile(f) + cert = decoder.decode(substrate, asn1Spec=rfc2459.Certificate())[0] + + c = cert['tbsCertificate'] + + # print(cert.prettyPrint()) + + h = binascii.hexlify + b = berenc.encode + d = derenc.encode + + bercert = b(cert) + + # the CKA_CHECKSUM value is the first 3 bytes of a sha1hash + m = hashlib.sha1() + m.update(bercert) + bercertchecksum = m.digest()[0:3] + bercertchecksum = h(bercertchecksum) + + attrs = [ + { CKA_CLASS : CKO_CERTIFICATE }, + { CKA_CERTIFICATE_TYPE : CKC_X_509 }, + { CKA_TRUSTED : False }, + { CKA_CERTIFICATE_CATEGORY: CK_CERTIFICATE_CATEGORY_UNSPECIFIED }, + # The value of this attribute is derived by taking the first 3 bytes of the CKA_VALUE + # field. + { CKA_CHECK_VALUE: bercertchecksum }, + # Start date for the certificate (default empty) + { CKA_START_DATE : "" }, + # End date for the certificate (default empty) + { CKA_END_DATE : "" }, + # DER-encoding of the SubjectPublicKeyInfo for the public key + # contained in this certificate (default empty) + { CKA_PUBLIC_KEY_INFO : "" }, + # DER encoded subject + { CKA_SUBJECT : h(d(c['subject'])) }, + # "label of keypair associated, default empty + { CKA_LABEL : h(keylabel) }, + # der encoding of issuer, default empty + { CKA_ISSUER : '' }, + # der encoding of the cert serial, default empty + { CKA_SERIAL_NUMBER : '' }, + # BER encoding of the certificate + { CKA_VALUE : h(bercert) }, + # RFC2279 string to URL where cert can be found, default empty + { CKA_URL : '' }, + # hash of pub key subj, default empty + { CKA_HASH_OF_SUBJECT_PUBLIC_KEY : '' }, + # Hash of pub key, default empty + { CKA_HASH_OF_ISSUER_PUBLIC_KEY : '' }, + # Java security domain, default CK_SECURITY_DOMAIN_UNSPECIFIED + { CKA_JAVA_MIDP_SECURITY_DOMAIN : CK_SECURITY_DOMAIN_UNSPECIFIED }, + # Name hash algorithm, defaults to SHA1 + { CKA_NAME_HASH_ALGORITHM : CKM_SHA_1 } + ] + + with Db(path) as db: + + # get token to add to + token = db.gettoken(label) + + # verify that key is existing + # XXX we should be verifying that it's expected, but I guess one could always load up a cert + # not associated with a key. + tobjs = db.gettertiary(token['id']) + + # look up the id by object label + id = None + for t in tobjs: + id = AddCert.get_id_by_label(t, keylabel) + if id is not None: + break + + if id is None: + raise RuntimeError('Cannot find key with id "%s"' % keylabel) + + attrs.append({CKA_ID: id}) + # TODO verify that cert is cryptographically bound to key found + + # add the cert + db.addtertiary(token['id'], None, None, None, None, attrs) + + print('Added cert as label: "{keylabel}"'.format(keylabel=keylabel)) + + + @staticmethod + def get_id_by_label(tobj, keylabel): + + attrs = list_dict_from_kvp(tobj['attrs']) + + for a in attrs: + if str(CKA_LABEL) in a: + x = binascii.unhexlify(a[str(CKA_LABEL)]) + if x == keylabel: + return a[str(CKA_LABEL)] + + return None \ No newline at end of file diff --git a/tools/tpm2_pkcs11/db.py b/tools/tpm2_pkcs11/db.py index 46f7b7ff..41624272 100644 --- a/tools/tpm2_pkcs11/db.py +++ b/tools/tpm2_pkcs11/db.py @@ -149,15 +149,23 @@ def addprimary(self, tr_handle, objauth, hierarchy='o'): def addtertiary(self, tokid, priv, pub, objauth, mech, attrs): tobject = { 'tokid': tokid, - 'pub': Db._blobify(pub), - 'objauth': objauth, - 'mech': list_dict_to_kvp(mech), 'attrs': list_dict_to_kvp(attrs), } if priv != None: tobject['priv'] = Db._blobify(priv) + if pub != None: + tobject['pub'] = Db._blobify(pub) + + if objauth != None: + tobject['objauth'] = objauth + + if mech is None: + mech = [] + + tobject['mech'] = list_dict_to_kvp(mech) + columns = ', '.join(tobject.keys()) placeholders = ', '.join('?' * len(tobject)) sql = 'INSERT INTO tobjects ({}) VALUES ({})'.format(columns, @@ -258,9 +266,9 @@ def create(self): CREATE TABLE IF NOT EXISTS tobjects( id INTEGER PRIMARY KEY, tokid INTEGER NOT NULL, - pub BLOB NOT NULL, + pub BLOB, priv BLOB, - objauth TEXT NOT NULL, + objauth TEXT, mech TEXT NOT NULL, attrs TEXT NOT NULL, FOREIGN KEY (tokid) REFERENCES tokens(id) ON DELETE CASCADE diff --git a/tools/tpm2_pkcs11/pkcs11t.py b/tools/tpm2_pkcs11/pkcs11t.py index 5eb37d57..e4ab11de 100644 --- a/tools/tpm2_pkcs11/pkcs11t.py +++ b/tools/tpm2_pkcs11/pkcs11t.py @@ -2,6 +2,7 @@ CKA_KEY_TYPE = 0x100 CKA_CLASS = 0 +CKO_CERTIFICATE = 1 CKO_PUBLIC_KEY = 2 CKO_PRIVATE_KEY = 3 CKO_SECRET_KEY = 4 @@ -23,8 +24,22 @@ CKA_TOKEN = 0x1 CKA_LABEL = 0x3 +CKA_VALUE = 0x11 +CKA_CERTIFICATE_TYPE = 0x80 +CKA_ISSUER = 0x81 +CKA_SERIAL_NUMBER = 0x82 CKA_TRUSTED = 0x86 +CKA_CERTIFICATE_CATEGORY = 0x87 +CKA_JAVA_MIDP_SECURITY_DOMAIN = 0x88 +CKA_URL = 0x89 +CKA_CHECK_VALUE = 0x90 +CKA_HASH_OF_SUBJECT_PUBLIC_KEY = 0x8A +CKA_HASH_OF_ISSUER_PUBLIC_KEY = 0x8B +CKA_NAME_HASH_ALGORITHM = 0x8C +CKA_SUBJECT = 0x101 CKA_ID = 0x102 +CKA_START_DATE = 0x110 +CKA_END_DATE = 0x111 CKA_DERIVE = 0x10C CKA_MODULUS = 0x120 CKA_MODULUS_BITS = 0x121 @@ -47,3 +62,9 @@ CKA_ALWAYS_SENSITIVE = 0x165 CKA_ALWAYS_AUTHENTICATE = 0x202 CKA_WRAP_WITH_TRUSTED = 0x210 + +CKC_X_509 = 0 + +CK_SECURITY_DOMAIN_UNSPECIFIED = 0x0 + +CK_CERTIFICATE_CATEGORY_UNSPECIFIED = 0x0 diff --git a/tools/tpm2_pkcs11/tpm2.py b/tools/tpm2_pkcs11/tpm2.py index 12260bc8..0e606023 100644 --- a/tools/tpm2_pkcs11/tpm2.py +++ b/tools/tpm2_pkcs11/tpm2.py @@ -219,6 +219,7 @@ def importkey(self, if (rc != 0): os.remove(pub) os.remove(priv) + print("command: %s" % str(" ".join(cmd))) raise RuntimeError("Could not execute tpm2_import: %s" % str(stderr)) diff --git a/tools/tpm2_pkcs11/utils.py b/tools/tpm2_pkcs11/utils.py index 7542de80..20a6ce7f 100644 --- a/tools/tpm2_pkcs11/utils.py +++ b/tools/tpm2_pkcs11/utils.py @@ -41,6 +41,9 @@ def bytes_to_file(bites, tmpdir): def dict_from_kvp(kvp): return dict(x.split('=') for x in kvp.split('\t ')) +def list_dict_from_kvp(lines): + return [ dict_from_kvp(x) for x in lines.split("\n") ] + def rand_hex_str(num=32): if num % 2: raise RuntimeError("Expected even number of bytes, got: %u", num)