diff --git a/.github/workflows/clang-asan-check.yml b/.github/workflows/clang-asan-check.yml index 5a34c86..9d47df4 100644 --- a/.github/workflows/clang-asan-check.yml +++ b/.github/workflows/clang-asan-check.yml @@ -16,7 +16,7 @@ jobs: - uses: actions/checkout@v2 - name: Install dependencies - run: sudo apt-get install -y autoconf-archive libcurl4-gnutls-dev libjson-c-dev libtss2-dev libtss2-tcti-tabrmd0 tpm2-abrmd tpm2-tools + run: sudo apt-get install -y autoconf-archive libcurl4-openssl-dev libjson-c-dev libtss2-dev libtss2-tcti-tabrmd0 tpm2-abrmd tpm2-tools - name: Build TPM2 simulator run: | diff --git a/Makefile.am b/Makefile.am index cca05ec..7073511 100644 --- a/Makefile.am +++ b/Makefile.am @@ -47,6 +47,11 @@ endif tpm2_la_CFLAGS = $(TSS2_ESYS_CFLAGS) $(TSS2_TCTILDR_CFLAGS) $(COMMON_CFLAGS) $(CODE_COVERAGE_CFLAGS) tpm2_la_LIBADD = $(TSS2_ESYS_LIBS) $(TSS2_TCTILDR_LIBS) $(LIBS) $(CODE_COVERAGE_LIBS) +if TSS2_FAPI +tpm2_la_SOURCES += src/tpm2-provider-store-tss2.c +tpm2_la_CFLAGS += $(TSS2_FAPI_CFLAGS) +tpm2_la_LIBADD += $(TSS2_FAPI_LIBS) +endif if TSS2_RC tpm2_la_LIBADD += $(TSS2_RC_LIBS) endif @@ -115,6 +120,9 @@ TESTS_SHELL += test/cipher_aes128_ecb.sh \ test/cipher_aes256_nopad.sh \ test/cipher_camellia128.sh endif +if TSS2_FAPI +TESTS_SHELL += test/rsa_fapi_sign.sh +endif TEST_EXTENSIONS = .sh SH_LOG_COMPILER = $(srcdir)/test/run diff --git a/README.md b/README.md index d5a5b31..02fd5c5 100644 --- a/README.md +++ b/README.md @@ -146,9 +146,9 @@ and [OSSL_DECODER](https://www.openssl.org/docs/manmaster/man3/OSSL_DECODER.html) API to load (TPM2_Load) a private key from a previously generated file, as well as persistent keys generated with the -[tpm2-tools](https://github.com/tpm2-software/tpm2-tools). Both the hexadecimal -key `handle` as well as the serialized `object` file may be used. These URI -prefixes may be used with any openssl command. +[tpm2-tools](https://github.com/tpm2-software/tpm2-tools). The hexadecimal key +`handle`, the serialized `object` file as well as the `tss2` metadata path may +be used. These URI prefixes may be used with any openssl command. The corresponding public key can be stored using the [`openssl pkey`](https://www.openssl.org/docs/manmaster/man1/openssl-pkey.html) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 533f2e9..1385cc1 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -5,7 +5,9 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) ## [1.3.0] - 2023-xx-yy ### Added -- Added support for RSA-OAEP decryption +- Implemented loading keys from the FAPI metadata store using the `tss2:` prefix, + followed by the key path, e.g. `tss2:HS/SRK/testkey`. +- Added support for RSA-OAEP decryption. ## [1.2.0] - 2023-10-14 ### Added diff --git a/docs/keys.md b/docs/keys.md index e1e8684..5501f2b 100644 --- a/docs/keys.md +++ b/docs/keys.md @@ -167,6 +167,7 @@ loaders: * **handle**, to load persistent keys, or data (public keys or certificates) from NV indices; * **object**, to load serialized object representing a persistent handle. + * **tss2**, to load persistent or transient objects from the FAPI metadata store. These are used by the [OSSL_STORE](https://www.openssl.org/docs/manmaster/man7/ossl_store.html) @@ -246,3 +247,17 @@ To load a persistent key using the serialized object, specify the prefix ``` openssl rsa -provider tpm2 -modulus -noout -in object:ak_rsa.obj ``` + +### Using FAPI Metadata Store + +The `tss2_createkey` creates a key inside the TPM and stores it in the FAPI +metadata store: +``` +tss2_createkey --path=HS/SRK/testkey --type="noDa,sign" +``` + +To load a key from the FAPI metadata store, specify the prefix `tss2:` and then +the object path: +``` +openssl rsa -provider tpm2 -modulus -noout -in tss2:HS/SRK/testkey +``` diff --git a/src/tpm2-provider-store-tss2.c b/src/tpm2-provider-store-tss2.c new file mode 100644 index 0000000..6cb3381 --- /dev/null +++ b/src/tpm2-provider-store-tss2.c @@ -0,0 +1,309 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ + +#include + +#include +#include +#include +#include + +#include +#include +#include "tpm2-provider-pkey.h" + +typedef struct tpm2_store_ctx_st TPM2_STORE_CTX; + +struct tpm2_store_ctx_st { + const OSSL_CORE_HANDLE *core; + ESYS_CONTEXT *esys_ctx; + TPM2_CAPABILITY capability; + char *path; + FAPI_CONTEXT *fapi_ctx; + int load_done; +}; + +static OSSL_FUNC_store_open_fn tpm2_store_open; +static OSSL_FUNC_store_settable_ctx_params_fn tpm2_store_settable_params; +static OSSL_FUNC_store_set_ctx_params_fn tpm2_store_set_params; +static OSSL_FUNC_store_load_fn tpm2_store_load; +static OSSL_FUNC_store_eof_fn tpm2_store_eof; +static OSSL_FUNC_store_close_fn tpm2_store_close; + +static void * +tpm2_store_open(void *provctx, const char *uri) +{ + TPM2_PROVIDER_CTX *cprov = provctx; + TPM2_STORE_CTX *sctx; + TSS2_RC r; + + if (!uri || strncmp(uri, "tss2:", 5)) + return NULL; + + DBG("STORE/TSS2 OPEN %s\n", uri+5); + if ((sctx = OPENSSL_zalloc(sizeof(TPM2_STORE_CTX))) == NULL) + return NULL; + + sctx->core = cprov->core; + sctx->esys_ctx = cprov->esys_ctx; + sctx->capability = cprov->capability; + + if (!(sctx->path = strdup(uri+5))) + goto error1; + + r = Fapi_Initialize(&sctx->fapi_ctx, NULL); + TPM2_CHECK_RC(sctx->core, r, TPM2_ERR_CANNOT_LOAD_KEY, goto error2); + + return sctx; +error2: + free(sctx->path); +error1: + OPENSSL_clear_free(sctx, sizeof(TPM2_STORE_CTX)); + return NULL; +} + +static const OSSL_PARAM * +tpm2_store_settable_params(void *provctx) +{ + static const OSSL_PARAM known_settable_ctx_params[] = { + OSSL_PARAM_END + }; + return known_settable_ctx_params; +} + +static int +tpm2_store_set_params(void *loaderctx, const OSSL_PARAM params[]) +{ + TRACE_PARAMS("STORE/TSS2 SET_PARAMS", params); + return 1; +} + +static int +tpm2_store_load_pkey(TPM2_STORE_CTX *sctx, ESYS_TR object, + OSSL_CALLBACK *object_cb, void *object_cbarg) +{ + TPM2B_PUBLIC *out_public = NULL; + TPM2_PKEY *pkey = NULL; + TSS2_RC r; + int ret = 0; + + DBG("STORE/TSS2 LOAD pkey\n"); + pkey = OPENSSL_zalloc(sizeof(TPM2_PKEY)); + if (pkey == NULL) + return 0; + + pkey->core = sctx->core; + pkey->esys_ctx = sctx->esys_ctx; + pkey->capability = sctx->capability; + pkey->object = object; + + r = Esys_ReadPublic(sctx->esys_ctx, object, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + &out_public, NULL, NULL); + TPM2_CHECK_RC(sctx->core, r, TPM2_ERR_CANNOT_LOAD_KEY, goto final); + + pkey->data.pub = *out_public; + pkey->data.privatetype = KEY_TYPE_HANDLE; + Esys_TR_GetTpmHandle(sctx->esys_ctx, object, &pkey->data.handle); + pkey->data.emptyAuth = 1; + + free(out_public); + + OSSL_PARAM params[4]; + int object_type = OSSL_OBJECT_PKEY; + const char *keytype; + + params[0] = OSSL_PARAM_construct_int(OSSL_OBJECT_PARAM_TYPE, &object_type); + + if ((keytype = tpm2_openssl_type(&pkey->data)) == NULL) { + TPM2_ERROR_raise(sctx->core, TPM2_ERR_UNKNOWN_ALGORITHM); + goto final; + } + DBG("STORE/TSS2 LOAD found %s\n", keytype); + params[1] = OSSL_PARAM_construct_utf8_string(OSSL_OBJECT_PARAM_DATA_TYPE, + (char *)keytype, 0); + /* The address of the key becomes the octet string */ + params[2] = OSSL_PARAM_construct_octet_string(OSSL_OBJECT_PARAM_REFERENCE, + &pkey, sizeof(pkey)); + params[3] = OSSL_PARAM_construct_end(); + + ret = object_cb(params, object_cbarg); +final: + OPENSSL_clear_free(pkey, sizeof(TPM2_PKEY)); + return ret; +} + +static int +tpm2_store_load_index(TPM2_STORE_CTX *sctx, ESYS_TR object, + OSSL_CALLBACK *object_cb, void *object_cbarg) +{ + TPM2B_NV_PUBLIC *metadata = NULL; + uint16_t read_len, read_max, data_len = 0; + unsigned char *data = NULL; + BIO *bufio; + TSS2_RC r; + int ret = 0; + + r = Esys_NV_ReadPublic(sctx->esys_ctx, object, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + &metadata, NULL); + TPM2_CHECK_RC(sctx->core, r, TPM2_ERR_CANNOT_LOAD_KEY, goto final); + + read_len = metadata->nvPublic.dataSize; + read_max = tpm2_max_nvindex_buffer(sctx->capability.properties); + DBG("STORE/TSS2 LOAD index %u bytes (buffer %u bytes)\n", read_len, read_max); + + if ((data = malloc(read_len)) == NULL) + goto final; + + while (read_len > 0) { + uint16_t bytes_to_read = read_len < read_max ? read_len : read_max; + TPM2B_MAX_NV_BUFFER *buff = NULL; + + r = Esys_NV_Read(sctx->esys_ctx, object, object, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + bytes_to_read, data_len, &buff); + TPM2_CHECK_RC(sctx->core, r, TPM2_ERR_CANNOT_LOAD_KEY, goto final); + + memcpy(data + data_len, buff->buffer, buff->size); + read_len -= buff->size; + data_len += buff->size; + free(buff); + } + + OSSL_PARAM params[3]; + int object_type = OSSL_OBJECT_UNKNOWN; + char *pem_name = NULL; + char *pem_header = NULL; + unsigned char *der_data = NULL; + long der_len; + + if ((bufio = BIO_new_mem_buf(data, data_len)) == NULL) + goto final; + + /* the ossl_store_handle_load_result() supports DER objects only */ + if (PEM_read_bio(bufio, &pem_name, &pem_header, &der_data, &der_len) > 0) { + if (pem_name != NULL) { + DBG("STORE/TSS2 LOAD(PEM) %s %li bytes\n", pem_name, der_len); + + if (!strcmp(pem_name, TSSPRIVKEY_PEM_STRING)) + object_type = OSSL_OBJECT_PKEY; + else if (!strcmp(pem_name, PEM_STRING_X509)) + object_type = OSSL_OBJECT_CERT; + else if (!strcmp(pem_name, PEM_STRING_X509_CRL)) + object_type = OSSL_OBJECT_CRL; + } + + /* pass the data to ossl_store_handle_load_result(), + which will call the TPM2_PKEY decoder or read the certificate */ + params[0] = OSSL_PARAM_construct_int(OSSL_OBJECT_PARAM_TYPE, &object_type); + + params[1] = OSSL_PARAM_construct_octet_string(OSSL_OBJECT_PARAM_DATA, + der_data, der_len); + } else { + params[0] = OSSL_PARAM_construct_int(OSSL_OBJECT_PARAM_TYPE, &object_type); + + params[1] = OSSL_PARAM_construct_octet_string(OSSL_OBJECT_PARAM_DATA, + data, data_len); + } + + params[2] = OSSL_PARAM_construct_end(); + + ret = object_cb(params, object_cbarg); + + OPENSSL_free(pem_name); + OPENSSL_free(pem_header); + OPENSSL_free(der_data); + BIO_free(bufio); +final: + free(data); + free(metadata); + return ret; +} + +static int +tpm2_store_load(void *ctx, + OSSL_CALLBACK *object_cb, void *object_cbarg, + OSSL_PASSPHRASE_CALLBACK *pw_cb, void *pw_cbarg) +{ + TPM2_STORE_CTX *sctx = ctx; + uint8_t type; + uint8_t *data = NULL; + size_t length; + TPMS_CONTEXT blob; + ESYS_TR object; + TSS2_RC r; + int ret = 0; + + DBG("STORE/TSS2 LOAD\n"); + r = Fapi_GetEsysBlob(sctx->fapi_ctx, sctx->path, &type, &data, &length); + sctx->load_done = 1; + TPM2_CHECK_RC(sctx->core, r, TPM2_ERR_CANNOT_LOAD_KEY, return 0); + + switch(type) { + case FAPI_ESYSBLOB_DESERIALIZE: + r = Esys_TR_Deserialize(sctx->esys_ctx, data, length, &object); + TPM2_CHECK_RC(sctx->core, r, TPM2_ERR_CANNOT_LOAD_KEY, goto error1); + break; + case FAPI_ESYSBLOB_CONTEXTLOAD: + r = Tss2_MU_TPMS_CONTEXT_Unmarshal(data, length, NULL, &blob); + TPM2_CHECK_RC(sctx->core, r, TPM2_ERR_CANNOT_LOAD_KEY, goto error1); + + r = Esys_ContextLoad(sctx->esys_ctx, &blob, &object); + TPM2_CHECK_RC(sctx->core, r, TPM2_ERR_CANNOT_LOAD_KEY, goto error1); + break; + default: + TPM2_ERROR_raise(sctx->core, TPM2_ERR_CANNOT_LOAD_KEY); + goto error1; + } + Fapi_Free(data); + + if (!strncmp(sctx->path, "/nv/", 4)) { + ret = tpm2_store_load_index(sctx, object, object_cb, object_cbarg); + Esys_TR_Close(sctx->esys_ctx, &object); + } else { + ret = tpm2_store_load_pkey(sctx, object, object_cb, object_cbarg); + if (!ret) + Esys_TR_Close(sctx->esys_ctx, &object); + } + + return ret; +error2: + Esys_TR_Close(sctx->esys_ctx, &object); +error1: + Fapi_Free(data); + return 0; +} + +static int +tpm2_store_eof(void *ctx) +{ + TPM2_STORE_CTX *sctx = ctx; + return sctx->load_done; +} + +static int +tpm2_store_close(void *ctx) +{ + TPM2_STORE_CTX *sctx = ctx; + + if (sctx == NULL) + return 0; + + DBG("STORE/TSS2 CLOSE\n"); + Fapi_Finalize(&sctx->fapi_ctx); + free(sctx->path); + + OPENSSL_clear_free(ctx, sizeof(TPM2_STORE_CTX)); + return 1; +} + +const OSSL_DISPATCH tpm2_tss2_store_functions[] = { + { OSSL_FUNC_STORE_OPEN, (void(*)(void))tpm2_store_open }, + { OSSL_FUNC_STORE_SETTABLE_CTX_PARAMS, (void(*)(void))tpm2_store_settable_params }, + { OSSL_FUNC_STORE_SET_CTX_PARAMS, (void(*)(void))tpm2_store_set_params }, + { OSSL_FUNC_STORE_LOAD, (void(*)(void))tpm2_store_load }, + { OSSL_FUNC_STORE_EOF, (void(*)(void))tpm2_store_eof }, + { OSSL_FUNC_STORE_CLOSE, (void(*)(void))tpm2_store_close }, + { 0, NULL } +}; + diff --git a/src/tpm2-provider.c b/src/tpm2-provider.c index c5a4892..2178bce 100644 --- a/src/tpm2-provider.c +++ b/src/tpm2-provider.c @@ -234,10 +234,16 @@ static const OSSL_ALGORITHM tpm2_decoders[] = { }; extern const OSSL_DISPATCH tpm2_handle_store_functions[]; +#if WITH_TSS2_FAPI +extern const OSSL_DISPATCH tpm2_tss2_store_functions[]; +#endif static const OSSL_ALGORITHM tpm2_stores[] = { { "handle", TPM2_PROPS(store), tpm2_handle_store_functions }, { "object", TPM2_PROPS(store), tpm2_handle_store_functions }, +#if WITH_TSS2_FAPI + { "tss2", TPM2_PROPS(store), tpm2_tss2_store_functions }, +#endif { NULL, NULL, NULL } }; diff --git a/test/rsa_fapi_sign.sh b/test/rsa_fapi_sign.sh new file mode 100755 index 0000000..bc881b1 --- /dev/null +++ b/test/rsa_fapi_sign.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env bash +# SPDX-License-Identifier: BSD-3-Clause +set -eufx +# skip when tss2 is not available +tss2 createkey --help | grep Usage: || exit 77 + +fapidir=`pwd`/testfapi +mkdir -p $fapidir +cat > $fapidir/fapi_config.json << EOF +{ + "profile_name": "P_RSA2048SHA256", + "profile_dir": "$fapidir/", + "user_dir": "$fapidir/keystore_user", + "system_dir": "$fapidir/keystore_system", + "tcti": "", + "system_pcrs": [], + "ek_cert_less": "yes", + "log_dir" : "$fapidir/log", +} +EOF +export TSS2_FAPICONF=$fapidir/fapi_config.json + +cat > $fapidir/P_RSA2048SHA256.json << EOF +{ + "type": "TPM2_ALG_RSA", + "nameAlg": "TPM2_ALG_SHA256", + "srk_template": "system,restricted,decrypt,0x81000001", + "ek_template": "system,restricted,decrypt", + "rsa_signing_scheme": { + "scheme": "TPM2_ALG_RSAPSS", + "details": { + "hashAlg": "TPM2_ALG_SHA256" + } + }, + "rsa_decrypt_scheme": { + "scheme": "TPM2_ALG_OAEP", + "details": { + "hashAlg": "TPM2_ALG_SHA256" + } + }, + "sym_mode": "TPM2_ALG_CFB", + "sym_parameters": { + "algorithm": "TPM2_ALG_AES", + "keyBits": "128", + "mode": "TPM2_ALG_CFB" + }, + "sym_block_size": 16, + "pcr_selection": [], + "exponent": 0, + "keyBits": 2048, +} +EOF + +tss2 provision + +# generate private key +tss2 createkey --path=HS/SRK/testkey --type="noDa,sign" --authValue="" + +# print private key modulus +openssl rsa -provider tpm2 -in tss2:HS/SRK/testkey -modulus -noout + +# must be 32 characters, the length of the sha256 digest +echo -n "abcde12345abcde12345abcde12345ab" > testdata + +# create signature and export public key +tss2 sign --keyPath=HS/SRK/testkey --authValue="abc" --digest=testdata \ + --publicKey=testkey.pub --signature=testdata.sig + +# print public key modulus +openssl rsa -pubin -in testkey.pub -modulus -noout + +rm -rf $fapidir +rm -f testdata testkey.pub testdata.sig diff --git a/test/store_errors.sh b/test/store_errors.sh index a6e0916..68d0125 100755 --- a/test/store_errors.sh +++ b/test/store_errors.sh @@ -22,6 +22,10 @@ should_fail "Could not open file or uri" openssl rsa -provider tpm2 -in handle:0xBAD -modulus -noout 2> out should_fail "Could not (find|read)" +# unknown TSS2 path +openssl rsa -provider tpm2 -in tss2:/SRK/unknown -modulus -noout 2> out +should_fail "Could not (find|read)" + # unknown TPM2 object openssl rsa -provider tpm2 -in object:unknown -modulus -noout 2> out should_fail "Could not open file or uri"