diff options
Diffstat (limited to '')
-rw-r--r-- | lib/tpm2_esys.c | 1139 |
1 files changed, 1139 insertions, 0 deletions
diff --git a/lib/tpm2_esys.c b/lib/tpm2_esys.c new file mode 100644 index 0000000..70209b7 --- /dev/null +++ b/lib/tpm2_esys.c @@ -0,0 +1,1139 @@ +/* + * Copyright © 2018-2021 David Woodhouse. + * Copyright © 2019,2021 Red Hat, Inc. + * + * Author: David Woodhouse <dwmw2@infradead.org>, Nikos Mavrogiannopoulos, + * Daiki Ueno + * + * This file is part of GnuTLS. + * + * The GnuTLS is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License + * as published by the Free Software Foundation; either version 2.1 of + * the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program. If not, see <https://www.gnu.org/licenses/> + * + */ + +/* Portions taken from tpm2-tss-engine, copyright as below: */ + +/******************************************************************************* + * Copyright 2017-2018, Fraunhofer SIT sponsored by Infineon Technologies AG + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. + * + * 2. 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. + * + * 3. Neither the name of tpm2-tss-engine nor the names of its contributors + * may 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 HOLDER 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. + ******************************************************************************/ + +#include "config.h" + +#include "gnutls_int.h" +#include "abstract_int.h" + +#include <errno.h> +#include <stdio.h> +#include <stdint.h> +#include <string.h> + +#include "tpm2.h" +#include "locks.h" + +#include <tss2/tss2_mu.h> +#include <tss2/tss2_esys.h> +#include <tss2/tss2_tctildr.h> + +#include <dlfcn.h> + +/* We don't want to link to libtss2-esys, as it brings in other + * crypto libraries. Instead, only dlopen it as needed. + */ + +static void *_gnutls_tss2_esys_dlhandle; +static void *_gnutls_tss2_mu_dlhandle; +static void *_gnutls_tss2_tctildr_dlhandle; + +/* Copied from gnulib/lib/intprops.h */ + +/* Does the __typeof__ keyword work? This could be done by + 'configure', but for now it's easier to do it by hand. */ +#undef HAVE___TYPEOF__ +#if (2 <= __GNUC__ \ + || (4 <= __clang_major__) \ + || (1210 <= __IBMC__ && defined __IBM__TYPEOF__) \ + || (0x5110 <= __SUNPRO_C && !__STDC__)) +# define HAVE___TYPEOF__ 1 +#else +# define HAVE___TYPEOF__ 0 +#endif + +#if HAVE___TYPEOF__ +static __typeof__(Esys_GetCapability) (*_gnutls_tss2_Esys_GetCapability); +static __typeof__(Esys_Free) (*_gnutls_tss2_Esys_Free); +static __typeof__(Esys_TR_SetAuth) (*_gnutls_tss2_Esys_TR_SetAuth); +static __typeof__(Esys_CreatePrimary) (*_gnutls_tss2_Esys_CreatePrimary); +static __typeof__(Esys_Initialize) (*_gnutls_tss2_Esys_Initialize); +static __typeof__(Esys_Startup) (*_gnutls_tss2_Esys_Startup); +static __typeof__(Esys_TR_FromTPMPublic) (*_gnutls_tss2_Esys_TR_FromTPMPublic); +static __typeof__(Esys_ReadPublic) (*_gnutls_tss2_Esys_ReadPublic); +static __typeof__(Esys_Load) (*_gnutls_tss2_Esys_Load); +static __typeof__(Esys_FlushContext) (*_gnutls_tss2_Esys_FlushContext); +static __typeof__(Esys_Finalize) (*_gnutls_tss2_Esys_Finalize); +static __typeof__(Esys_RSA_Decrypt) (*_gnutls_tss2_Esys_RSA_Decrypt); +static __typeof__(Esys_Sign) (*_gnutls_tss2_Esys_Sign); + +static __typeof__(Tss2_MU_TPM2B_PRIVATE_Unmarshal) +(*_gnutls_tss2_Tss2_MU_TPM2B_PRIVATE_Unmarshal); +static __typeof__(Tss2_MU_TPM2B_PUBLIC_Unmarshal) +(*_gnutls_tss2_Tss2_MU_TPM2B_PUBLIC_Unmarshal); + +static __typeof__(Tss2_TctiLdr_Initialize) +(*_gnutls_tss2_Tss2_TctiLdr_Initialize); +static __typeof__(Tss2_TctiLdr_Finalize) +(*_gnutls_tss2_Tss2_TctiLdr_Finalize); +#else +static TSS2_RC +(*_gnutls_tss2_Esys_GetCapability)(ESYS_CONTEXT *esysContext, + ESYS_TR shandle1, + ESYS_TR shandle2, + ESYS_TR shandle3, + TPM2_CAP capability, + UINT32 property, + UINT32 propertyCount, + TPMI_YES_NO *moreData, + TPMS_CAPABILITY_DATA **capabilityData); +static void (*_gnutls_tss2_Esys_Free)(void *__ptr); +static TSS2_RC (*_gnutls_tss2_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, + ESYS_TR handle, + TPM2B_AUTH const *authValue); +static TSS2_RC +(*_gnutls_tss2_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, + ESYS_TR primaryHandle, + ESYS_TR shandle1, + ESYS_TR shandle2, + ESYS_TR shandle3, + const TPM2B_SENSITIVE_CREATE *inSensitive, + const TPM2B_PUBLIC *inPublic, + const TPM2B_DATA *outsideInfo, + const TPML_PCR_SELECTION *creationPCR, + ESYS_TR *objectHandle, + TPM2B_PUBLIC **outPublic, + TPM2B_CREATION_DATA **creationData, + TPM2B_DIGEST **creationHash, + TPMT_TK_CREATION **creationTicket); +static TSS2_RC (*_gnutls_tss2_Esys_Initialize)(ESYS_CONTEXT **esys_context, + TSS2_TCTI_CONTEXT *tcti, + TSS2_ABI_VERSION *abiVersion); +static TSS2_RC (*_gnutls_tss2_Esys_Startup)(ESYS_CONTEXT *esysContext, + TPM2_SU startupType); +static TSS2_RC (*_gnutls_tss2_Esys_TR_FromTPMPublic)(ESYS_CONTEXT *esysContext, + TPM2_HANDLE tpm_handle, + ESYS_TR optionalSession1, + ESYS_TR optionalSession2, + ESYS_TR optionalSession3, + ESYS_TR *object); +static TSS2_RC (*_gnutls_tss2_Esys_ReadPublic)(ESYS_CONTEXT *esysContext, + ESYS_TR objectHandle, + ESYS_TR shandle1, + ESYS_TR shandle2, + ESYS_TR shandle3, + TPM2B_PUBLIC **outPublic, + TPM2B_NAME **name, + TPM2B_NAME **qualifiedName); +static TSS2_RC (*_gnutls_tss2_Esys_Load)(ESYS_CONTEXT *esysContext, + ESYS_TR parentHandle, + ESYS_TR shandle1, + ESYS_TR shandle2, + ESYS_TR shandle3, + const TPM2B_PRIVATE *inPrivate, + const TPM2B_PUBLIC *inPublic, + ESYS_TR *objectHandle); +static TSS2_RC (*_gnutls_tss2_Esys_FlushContext)(ESYS_CONTEXT *esysContext, + ESYS_TR flushHandle); +static void (*_gnutls_tss2_Esys_Finalize)(ESYS_CONTEXT **context); +static TSS2_RC +(*_gnutls_tss2_Esys_RSA_Decrypt)(ESYS_CONTEXT *esysContext, + ESYS_TR keyHandle, + ESYS_TR shandle1, + ESYS_TR shandle2, + ESYS_TR shandle3, + const TPM2B_PUBLIC_KEY_RSA *cipherText, + const TPMT_RSA_DECRYPT *inScheme, + const TPM2B_DATA *label, + TPM2B_PUBLIC_KEY_RSA **message); +static TSS2_RC (*_gnutls_tss2_Esys_Sign)(ESYS_CONTEXT *esysContext, + ESYS_TR keyHandle, + ESYS_TR shandle1, + ESYS_TR shandle2, + ESYS_TR shandle3, + const TPM2B_DIGEST *digest, + const TPMT_SIG_SCHEME *inScheme, + const TPMT_TK_HASHCHECK *validation, + TPMT_SIGNATURE **signature); + +static TSS2_RC +(*_gnutls_tss2_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], + size_t buffer_size, + size_t *offset, + TPM2B_PRIVATE *dest); +static TSS2_RC +(*_gnutls_tss2_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], + size_t buffer_size, + size_t *offset, + TPM2B_PUBLIC *dest); + +static TSS2_RC +(*_gnutls_tss2_Tss2_TctiLdr_Initialize)(const char *nameConf, + TSS2_TCTI_CONTEXT **context); +static void (*_gnutls_tss2_Tss2_TctiLdr_Finalize)(TSS2_TCTI_CONTEXT **context); +#endif + +#define DLSYM_TSS2(sys, sym) \ + _gnutls_tss2_##sym = dlsym(_gnutls_tss2_##sys##_dlhandle, #sym); \ + if (!_gnutls_tss2_##sym) { \ + return -1; \ + } + +static int +init_tss2_funcs(void) +{ + if (!_gnutls_tss2_esys_dlhandle) { + _gnutls_tss2_esys_dlhandle = + dlopen("libtss2-esys.so.0", RTLD_NOW | RTLD_GLOBAL); + if (!_gnutls_tss2_esys_dlhandle) { + _gnutls_debug_log("tpm2: unable to dlopen libtss2-esys\n"); + return -1; + } + } + + DLSYM_TSS2(esys, Esys_GetCapability) + DLSYM_TSS2(esys, Esys_Free) + DLSYM_TSS2(esys, Esys_TR_SetAuth) + DLSYM_TSS2(esys, Esys_CreatePrimary) + DLSYM_TSS2(esys, Esys_Initialize) + DLSYM_TSS2(esys, Esys_Startup) + DLSYM_TSS2(esys, Esys_TR_FromTPMPublic) + DLSYM_TSS2(esys, Esys_ReadPublic) + DLSYM_TSS2(esys, Esys_Load) + DLSYM_TSS2(esys, Esys_FlushContext) + DLSYM_TSS2(esys, Esys_Finalize) + DLSYM_TSS2(esys, Esys_RSA_Decrypt) + DLSYM_TSS2(esys, Esys_Sign) + + if (!_gnutls_tss2_mu_dlhandle) { + _gnutls_tss2_mu_dlhandle = + dlopen("libtss2-mu.so.0", RTLD_NOW | RTLD_GLOBAL); + if (!_gnutls_tss2_mu_dlhandle) { + _gnutls_debug_log("tpm2: unable to dlopen libtss2-mu\n"); + return -1; + } + } + + DLSYM_TSS2(mu, Tss2_MU_TPM2B_PRIVATE_Unmarshal) + DLSYM_TSS2(mu, Tss2_MU_TPM2B_PUBLIC_Unmarshal) + + if (!_gnutls_tss2_tctildr_dlhandle) { + _gnutls_tss2_tctildr_dlhandle = + dlopen("libtss2-tctildr.so.0", RTLD_NOW | RTLD_GLOBAL); + if (!_gnutls_tss2_tctildr_dlhandle) { + _gnutls_debug_log("tpm2: unable to dlopen libtss2-tctildr\n"); + return -1; + } + } + + DLSYM_TSS2(tctildr, Tss2_TctiLdr_Initialize) + DLSYM_TSS2(tctildr, Tss2_TctiLdr_Finalize) + + return 0; +} + +struct tpm2_info_st { + TPM2B_PUBLIC pub; + TPM2B_PRIVATE priv; + TPM2B_DIGEST userauth; + TPM2B_DIGEST ownerauth; + unsigned bits; + bool need_userauth; + bool need_ownerauth; + bool did_ownerauth; + unsigned int parent; + struct pin_info_st *pin_info; +}; + +static TSS2_TCTI_CONTEXT *tcti_ctx; + +#define PRIMARY_HASH_ALGORITHM TPM2_ALG_SHA256 +#define PRIMARY_OBJECT_ATTRIBUTES (TPMA_OBJECT_USERWITHAUTH | \ + TPMA_OBJECT_RESTRICTED | \ + TPMA_OBJECT_DECRYPT | \ + TPMA_OBJECT_NODA | \ + TPMA_OBJECT_FIXEDTPM | \ + TPMA_OBJECT_FIXEDPARENT | \ + TPMA_OBJECT_SENSITIVEDATAORIGIN) + +static const TPM2B_PUBLIC primary_template_rsa = { + .publicArea = { + .type = TPM2_ALG_RSA, + .nameAlg = PRIMARY_HASH_ALGORITHM, + .objectAttributes = PRIMARY_OBJECT_ATTRIBUTES, + .authPolicy = { + .size = 0, + }, + .parameters.rsaDetail = { + .symmetric = { + .algorithm = TPM2_ALG_AES, + .keyBits.aes = 128, + .mode.aes = TPM2_ALG_CFB, + }, + .scheme = { + .scheme = TPM2_ALG_NULL, + .details = {} + }, + .keyBits = 2048, + .exponent = 0, + }, + .unique.rsa = { + .size = 0, + } + } +}; + +static const TPM2B_PUBLIC primary_template_ecc = { + .publicArea = { + .type = TPM2_ALG_ECC, + .nameAlg = PRIMARY_HASH_ALGORITHM, + .objectAttributes = PRIMARY_OBJECT_ATTRIBUTES, + .authPolicy = { + .size = 0, + }, + .parameters.eccDetail = { + .symmetric = { + .algorithm = TPM2_ALG_AES, + .keyBits.aes = 128, + .mode.aes = TPM2_ALG_CFB, + }, + .scheme = { + .scheme = TPM2_ALG_NULL, + .details = {} + }, + .curveID = TPM2_ECC_NIST_P256, + .kdf = { + .scheme = TPM2_ALG_NULL, + .details = {} + }, + }, + .unique.ecc = { + .x.size = 0, + .y.size = 0 + } + } +}; + +static const TPM2B_SENSITIVE_CREATE primary_sensitive = { + .sensitive = { + .userAuth = { + .size = 0, + }, + .data = { + .size = 0, + } + } +}; + +static const TPM2B_DATA all_outside_info = { + .size = 0, +}; + +static const TPML_PCR_SELECTION all_creation_pcr = { + .count = 0, +}; + + +#define rc_is_key_auth_failed(rc) (((rc) & 0xff) == TPM2_RC_BAD_AUTH) +#define rc_is_parent_auth_failed(rc) (((rc) & 0xff) == TPM2_RC_AUTH_FAIL) + +struct tpm2_info_st *tpm2_info_init(struct pin_info_st *pin) +{ + struct tpm2_info_st *t = gnutls_calloc(1, sizeof(struct tpm2_info_st)); + + if (t == NULL) { + return NULL; + } + + t->pin_info = pin; + + return t; +} + +static int tpm2_pin(struct pin_info_st *pin_info, const char *url, + const char *label, + char *pin, unsigned int pin_size) +{ + int ret; + + if (!label) { + label = "unknown"; + } + + ret = _gnutls_retrieve_pin(pin_info, url, label, 0, pin, pin_size); + if (ret < 0) { + return gnutls_assert_val(ret); + } + return ret; +} + +static void install_tpm_passphrase(TPM2B_DIGEST *auth, char *pass) +{ + if (strlen(pass) > sizeof(auth->buffer) - 1) { + _gnutls_debug_log("tpm2: password too long; truncating\n"); + } + auth->size = strlen(pass); + snprintf((char*)auth->buffer, sizeof(auth->buffer), "%s", pass); + zeroize_key(pass, auth->size); +} + +/* Figure out usable primary template according to the capabilities of + * the TPM chip; ECC is preferred over RSA for performance reasons. + */ +static const TPM2B_PUBLIC * +get_primary_template(ESYS_CONTEXT *ctx) +{ + TPMS_CAPABILITY_DATA *capability_data; + UINT32 i; + TSS2_RC rc; + + rc = _gnutls_tss2_Esys_GetCapability(ctx, + ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, + TPM2_CAP_ALGS, 0, TPM2_MAX_CAP_ALGS, + NULL, &capability_data); + if (rc) { + _gnutls_debug_log("tpm2: Esys_GetCapability failed: 0x%x\n", rc); + return NULL; + } + + for (i = 0; i < capability_data->data.algorithms.count; i++) { + if (capability_data->data.algorithms.algProperties[i].alg == + TPM2_ALG_ECC) { + _gnutls_tss2_Esys_Free(capability_data); + return &primary_template_ecc; + } + } + + for (i = 0; i < capability_data->data.algorithms.count; i++) { + if (capability_data->data.algorithms.algProperties[i].alg == + TPM2_ALG_RSA) { + _gnutls_tss2_Esys_Free(capability_data); + return &primary_template_rsa; + } + } + + _gnutls_tss2_Esys_Free(capability_data); + _gnutls_debug_log("tpm2: unable to find primary template\n"); + return NULL; +} + +static const char * +tpm2_hierarchy_name(TPM2_RH hierarchy) +{ + switch (hierarchy) { + case TPM2_RH_OWNER: + return "owner"; + case TPM2_RH_NULL: + return "null"; + case TPM2_RH_ENDORSEMENT: + return "endorsement"; + case TPM2_RH_PLATFORM: + return "platform"; + default: + gnutls_assert(); + return NULL; + } +} + +static ESYS_TR +tpm2_hierarchy_to_esys_handle(TPM2_RH hierarchy) +{ + switch (hierarchy) { + case TPM2_RH_OWNER: + return ESYS_TR_RH_OWNER; + case TPM2_RH_NULL: + return ESYS_TR_RH_NULL; + case TPM2_RH_ENDORSEMENT: + return ESYS_TR_RH_ENDORSEMENT; + case TPM2_RH_PLATFORM: + return ESYS_TR_RH_PLATFORM; + default: + gnutls_assert(); + return ESYS_TR_NONE; + } +} + +static int init_tpm2_primary(struct tpm2_info_st *info, + ESYS_CONTEXT *ctx, ESYS_TR *primary_handle) +{ + TSS2_RC rc; + const char *hierarchy_name; + ESYS_TR hierarchy; + const TPM2B_PUBLIC *primary_template; + + hierarchy_name = tpm2_hierarchy_name(info->parent); + hierarchy = tpm2_hierarchy_to_esys_handle(info->parent); + + if (!hierarchy_name || hierarchy == ESYS_TR_NONE) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + _gnutls_debug_log("tpm2: creating primary key under %s hierarchy\n", + hierarchy_name); + reauth: + if (info->need_ownerauth) { + char pass[GNUTLS_PKCS11_MAX_PIN_LEN]; + if (tpm2_pin(info->pin_info, "tpm2:", hierarchy_name, + pass, sizeof(pass))) { + return gnutls_assert_val(GNUTLS_E_TPM_KEY_PASSWORD_ERROR); + } + install_tpm_passphrase(&info->ownerauth, pass); + info->need_ownerauth = false; + } + rc = _gnutls_tss2_Esys_TR_SetAuth(ctx, hierarchy, &info->ownerauth); + if (rc) { + _gnutls_debug_log("tpm2: Esys_TR_SetAuth failed: 0x%x\n", rc); + return gnutls_assert_val(GNUTLS_E_TPM_ERROR); + } + primary_template = get_primary_template(ctx); + if (!primary_template) { + return gnutls_assert_val(GNUTLS_E_TPM_ERROR); + } + rc = _gnutls_tss2_Esys_CreatePrimary(ctx, hierarchy, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + &primary_sensitive, + primary_template, + &all_outside_info, &all_creation_pcr, + primary_handle, NULL, NULL, NULL, NULL); + if (rc_is_key_auth_failed(rc)) { + _gnutls_debug_log("tpm2: Esys_CreatePrimary owner auth failed\n"); + info->need_ownerauth = true; + goto reauth; + } else if (rc) { + _gnutls_debug_log("tpm2: Esys_CreatePrimary failed: 0x%x\n", rc); + return gnutls_assert_val(GNUTLS_E_TPM_ERROR); + } + return 0; +} + +#define parent_is_generated(parent) ((parent) >> TPM2_HR_SHIFT == TPM2_HT_PERMANENT) +#define parent_is_persistent(parent) ((parent) >> TPM2_HR_SHIFT == TPM2_HT_PERSISTENT) + +static int init_tpm2_key(ESYS_CONTEXT **ctx, ESYS_TR *key_handle, + struct tpm2_info_st *info) +{ + ESYS_TR parent_handle = ESYS_TR_NONE; + TSS2_RC rc; + + *key_handle = ESYS_TR_NONE; + + _gnutls_debug_log("tpm2: establishing connection with TPM\n"); + + rc = _gnutls_tss2_Esys_Initialize(ctx, tcti_ctx, NULL); + if (rc) { + gnutls_assert(); + _gnutls_debug_log("tpm2: Esys_Initialize failed: 0x%x\n", rc); + goto error; + } + + rc = _gnutls_tss2_Esys_Startup(*ctx, TPM2_SU_CLEAR); + if (rc == TPM2_RC_INITIALIZE) { + _gnutls_debug_log("tpm2: was already started up thus false positive failing in tpm2tss log\n"); + } else if (rc) { + gnutls_assert(); + _gnutls_debug_log("tpm2: Esys_Startup failed: 0x%x\n", rc); + goto error; + } + + if (parent_is_generated(info->parent)) { + if (init_tpm2_primary(info, *ctx, &parent_handle)) { + gnutls_assert(); + goto error; + } + } else { + rc = _gnutls_tss2_Esys_TR_FromTPMPublic(*ctx, info->parent, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + &parent_handle); + if (rc) { + gnutls_assert(); + _gnutls_debug_log("tpm2: Esys_TR_FromTPMPublic failed for parent 0x%x: 0x%x\n", + info->parent, rc); + goto error; + } + /* If we don't already have a password (and haven't already authenticated + * successfully), check the NODA flag on the parent and demand one if DA + * protection is enabled (since that strongly implies there is a non-empty + * password). */ + if (!info->did_ownerauth && !info->ownerauth.size) { + TPM2B_PUBLIC *pub = NULL; + + rc = _gnutls_tss2_Esys_ReadPublic(*ctx, parent_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + &pub, NULL, NULL); + if (!rc && + !(pub->publicArea.objectAttributes & TPMA_OBJECT_NODA)) { + info->need_ownerauth = true; + } + _gnutls_tss2_Esys_Free(pub); + } + reauth: + if (info->need_ownerauth) { + char pass[GNUTLS_PKCS11_MAX_PIN_LEN]; + if (tpm2_pin(info->pin_info, "tpm2:", "parent", + pass, sizeof(pass))) { + return gnutls_assert_val(GNUTLS_E_TPM_KEY_PASSWORD_ERROR); + } + install_tpm_passphrase(&info->ownerauth, pass); + info->need_ownerauth = false; + } + rc = _gnutls_tss2_Esys_TR_SetAuth(*ctx, parent_handle, &info->ownerauth); + if (rc) { + gnutls_assert(); + _gnutls_debug_log("tpm2: Esys_TR_SetAuth failed: 0x%x\n", + rc); + goto error; + } + } + + _gnutls_debug_log("tpm2: loading TPM2 key blob, parent handle 0x%x\n", + parent_handle); + + rc = _gnutls_tss2_Esys_Load(*ctx, parent_handle, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + &info->priv, &info->pub, + key_handle); + if (rc_is_parent_auth_failed(rc)) { + gnutls_assert(); + _gnutls_debug_log("tpm2: Esys_Load auth failed\n"); + info->need_ownerauth = true; + goto reauth; + } + if (rc) { + gnutls_assert(); + _gnutls_debug_log("tpm2: Esys_Load failed: 0x%x\n", rc); + goto error; + } + info->did_ownerauth = true; + + if (parent_is_generated(info->parent)) { + rc = _gnutls_tss2_Esys_FlushContext(*ctx, parent_handle); + if (rc) { + _gnutls_debug_log("tpm2: Esys_FlushContext for generated primary failed: 0x%x\n", + rc); + } + /* But it's non-fatal. */ + } + + return 0; + error: + if (parent_is_generated(info->parent) && parent_handle != ESYS_TR_NONE) { + _gnutls_tss2_Esys_FlushContext(*ctx, parent_handle); + } + if (*key_handle != ESYS_TR_NONE) { + _gnutls_tss2_Esys_FlushContext(*ctx, *key_handle); + } + *key_handle = ESYS_TR_NONE; + + _gnutls_tss2_Esys_Finalize(ctx); + return GNUTLS_E_TPM_ERROR; +} + +static int +auth_tpm2_key(struct tpm2_info_st *info, ESYS_CONTEXT *ctx, ESYS_TR key_handle) +{ + TSS2_RC rc; + + if (info->need_userauth) { + char pass[GNUTLS_PKCS11_MAX_PIN_LEN]; + if (tpm2_pin(info->pin_info, "tpm2:", "key", + pass, sizeof(pass))) { + return gnutls_assert_val(GNUTLS_E_TPM_KEY_PASSWORD_ERROR); + } + + install_tpm_passphrase(&info->userauth, pass); + info->need_userauth = false; + } + + rc = _gnutls_tss2_Esys_TR_SetAuth(ctx, key_handle, &info->userauth); + if (rc) { + _gnutls_debug_log("tpm2: Esys_TR_SetAuth failed: 0x%x\n", rc); + return gnutls_assert_val(GNUTLS_E_TPM_ERROR); + } + return 0; +} + +int tpm2_rsa_sign_hash_fn(gnutls_privkey_t key, gnutls_sign_algorithm_t algo, + void *_info, unsigned int flags, + const gnutls_datum_t *data, gnutls_datum_t *sig) +{ + struct tpm2_info_st *info = _info; + int ret; + ESYS_CONTEXT *ectx = NULL; + TPM2B_PUBLIC_KEY_RSA digest, *tsig = NULL; + TPM2B_DATA label = { .size = 0 }; + TPMT_RSA_DECRYPT in_scheme = { .scheme = TPM2_ALG_NULL }; + ESYS_TR key_handle = ESYS_TR_NONE; + const gnutls_sign_entry_st *se; + gnutls_x509_spki_st params; + TSS2_RC rc; + + _gnutls_debug_log("tpm2: RSA (%s) sign function called for %d bytes\n", + gnutls_sign_get_name(algo), data->size); + + se = _gnutls_sign_to_entry(algo); + if (unlikely(se == NULL)) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + switch (se->pk) { + case GNUTLS_PK_RSA_PSS: + /* This code is a copy from privkey_sign_* functions and + * exercised twice because gnutls_privkey_sign_hash_func + * currently does not provide access to SPKI params + * calculated. */ + ret = _gnutls_privkey_get_spki_params(key, ¶ms); + if (ret < 0) { + return gnutls_assert_val(ret); + } + + flags |= GNUTLS_PRIVKEY_SIGN_FLAG_RSA_PSS; + ret = _gnutls_privkey_update_spki_params(key, + key->pk_algorithm, + se->hash, flags, + ¶ms); + if (ret < 0) { + return gnutls_assert_val(ret); + } + + FIX_SIGN_PARAMS(params, flags, se->hash); + + digest.size = info->pub.publicArea.unique.rsa.size; + ret = _gnutls_rsa_pss_sign_pad(¶ms, tpm2_rsa_key_bits(info), + data, + digest.buffer, digest.size); + if (ret < 0) { + return gnutls_assert_val(GNUTLS_E_PK_SIGN_FAILED); + } + break; + case GNUTLS_PK_RSA: + digest.size = info->pub.publicArea.unique.rsa.size; + ret = _gnutls_rsa_pkcs1_sign_pad(tpm2_rsa_key_bits(info), + data, + digest.buffer, digest.size); + if (ret < 0) { + return gnutls_assert_val(GNUTLS_E_PK_SIGN_FAILED); + } + break; + default: + return gnutls_assert_val(GNUTLS_E_INTERNAL_ERROR); + } + + ret = init_tpm2_key(&ectx, &key_handle, info); + if (ret < 0) { + gnutls_assert(); + goto out; + } + reauth: + ret = auth_tpm2_key(info, ectx, key_handle); + if (ret < 0) { + gnutls_assert(); + goto out; + } + + rc = _gnutls_tss2_Esys_RSA_Decrypt(ectx, key_handle, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + &digest, &in_scheme, &label, &tsig); + if (rc_is_key_auth_failed(rc)) { + gnutls_assert(); + _gnutls_debug_log("tpm2: Esys_RSA_Decrypt auth failed\n"); + info->need_userauth = true; + goto reauth; + } + if (rc) { + gnutls_assert(); + _gnutls_debug_log("tpm2: failed to generate RSA signature: 0x%x\n", rc); + goto out; + } + + ret = _gnutls_set_datum(sig, tsig->buffer, tsig->size); + out: + _gnutls_tss2_Esys_Free(tsig); + + if (key_handle != ESYS_TR_NONE) { + _gnutls_tss2_Esys_FlushContext(ectx, key_handle); + } + + if (ectx) { + _gnutls_tss2_Esys_Finalize(&ectx); + } + + return ret; +} + +int tpm2_ec_sign_hash_fn(gnutls_privkey_t key, gnutls_sign_algorithm_t algo, + void *_info, unsigned int flags, + const gnutls_datum_t *data, gnutls_datum_t *sig) +{ + struct tpm2_info_st *info = _info; + int ret; + ESYS_CONTEXT *ectx = NULL; + TPM2B_DIGEST digest; + TPMT_SIGNATURE *tsig = NULL; + ESYS_TR key_handle = ESYS_TR_NONE; + TSS2_RC rc; + TPMT_TK_HASHCHECK validation = { .tag = TPM2_ST_HASHCHECK, + .hierarchy = TPM2_RH_NULL, + .digest.size = 0 }; + TPMT_SIG_SCHEME in_scheme = { .scheme = TPM2_ALG_ECDSA }; + gnutls_datum_t sig_r, sig_s; + + _gnutls_debug_log("tpm2: EC sign function called for %d bytes\n", + data->size); + + switch (algo) { + case GNUTLS_SIGN_ECDSA_SHA1: + in_scheme.details.ecdsa.hashAlg = TPM2_ALG_SHA1; + break; + case GNUTLS_SIGN_ECDSA_SHA256: + case GNUTLS_SIGN_ECDSA_SECP256R1_SHA256: + in_scheme.details.ecdsa.hashAlg = TPM2_ALG_SHA256; + break; + case GNUTLS_SIGN_ECDSA_SHA384: + case GNUTLS_SIGN_ECDSA_SECP384R1_SHA384: + in_scheme.details.ecdsa.hashAlg = TPM2_ALG_SHA384; + break; + case GNUTLS_SIGN_ECDSA_SHA512: + case GNUTLS_SIGN_ECDSA_SECP521R1_SHA512: + in_scheme.details.ecdsa.hashAlg = TPM2_ALG_SHA512; + break; + default: + _gnutls_debug_log("tpm2: Unknown TPM2 EC digest size %d\n", + data->size); + return GNUTLS_E_PK_SIGN_FAILED; + } + + memcpy(digest.buffer, data->data, data->size); + digest.size = data->size; + + ret = init_tpm2_key(&ectx, &key_handle, info); + if (ret < 0) { + gnutls_assert(); + goto out; + } + reauth: + ret = auth_tpm2_key(info, ectx, key_handle); + if (ret < 0) { + gnutls_assert(); + goto out; + } + + rc = _gnutls_tss2_Esys_Sign(ectx, key_handle, + ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, + &digest, &in_scheme, &validation, + &tsig); + if (rc_is_key_auth_failed(rc)) { + _gnutls_debug_log("tpm2: Esys_Sign auth failed\n"); + info->need_userauth = true; + goto reauth; + } + if (rc) { + _gnutls_debug_log("tpm2: failed to generate EC signature: 0x%x\n", rc); + goto out; + } + + sig_r.data = tsig->signature.ecdsa.signatureR.buffer; + sig_r.size = tsig->signature.ecdsa.signatureR.size; + sig_s.data = tsig->signature.ecdsa.signatureS.buffer; + sig_s.size = tsig->signature.ecdsa.signatureS.size; + + ret = gnutls_encode_rs_value(sig, &sig_r, &sig_s); + out: + _gnutls_tss2_Esys_Free(tsig); + + if (key_handle != ESYS_TR_NONE) { + _gnutls_tss2_Esys_FlushContext(ectx, key_handle); + } + + if (ectx) { + _gnutls_tss2_Esys_Finalize(&ectx); + } + + return ret; +} + +GNUTLS_ONCE(tpm2_esys_once); + +static void +tpm2_esys_once_init(void) +{ + const char *tcti; + const char * const tcti_vars[] = { + "GNUTLS_TPM2_TCTI", + "TPM2TOOLS_TCTI", + "TCTI", + "TEST_TCTI" + }; + size_t i; + TSS2_RC rc; + + if (init_tss2_funcs() < 0) { + _gnutls_debug_log("tpm2: unable to initialize TSS2 functions\n"); + return; + } + + for (i = 0; i < sizeof(tcti_vars) / sizeof(tcti_vars[0]); i++) { + tcti = secure_getenv(tcti_vars[i]); + if (tcti && *tcti != '\0') { + _gnutls_debug_log("tpm2: TCTI configuration found in %s\n", + tcti_vars[i]); + break; + } + } + if (tcti && *tcti != '\0') { + rc = _gnutls_tss2_Tss2_TctiLdr_Initialize(tcti, &tcti_ctx); + if (rc) { + _gnutls_debug_log("tpm2: TSS2_TctiLdr_Initialize failed: 0x%x\n", + rc); + } + } +} + +/* called by the global destructor through _gnutls_tpm2_deinit */ +void +tpm2_esys_deinit(void) +{ + if (tcti_ctx) { + _gnutls_tss2_Tss2_TctiLdr_Finalize(&tcti_ctx); + tcti_ctx = NULL; + } + if (_gnutls_tss2_esys_dlhandle) { + dlclose(_gnutls_tss2_esys_dlhandle); + _gnutls_tss2_esys_dlhandle = NULL; + } + if (_gnutls_tss2_mu_dlhandle) { + dlclose(_gnutls_tss2_mu_dlhandle); + _gnutls_tss2_mu_dlhandle = NULL; + } + if (_gnutls_tss2_tctildr_dlhandle) { + dlclose(_gnutls_tss2_tctildr_dlhandle); + _gnutls_tss2_tctildr_dlhandle = NULL; + } +} + +int install_tpm2_key(struct tpm2_info_st *info, gnutls_privkey_t pkey, + unsigned int parent, bool emptyauth, + gnutls_datum_t *privdata, gnutls_datum_t *pubdata) +{ + TSS2_RC rc; + + (void)gnutls_once(&tpm2_esys_once, tpm2_esys_once_init); + + if (!tcti_ctx) { + return gnutls_assert_val(GNUTLS_E_TPM_ERROR); + } + + if (!parent_is_persistent(parent) && + parent != TPM2_RH_OWNER && parent != TPM2_RH_NULL && + parent != TPM2_RH_ENDORSEMENT && parent != TPM2_RH_PLATFORM) { + _gnutls_debug_log("tpm2: Invalid TPM2 parent handle 0x%08x\n", + parent); + return gnutls_assert_val(GNUTLS_E_TPM_ERROR); + } + + info->parent = parent; + + rc = _gnutls_tss2_Tss2_MU_TPM2B_PRIVATE_Unmarshal(privdata->data, privdata->size, NULL, + &info->priv); + if (rc) { + _gnutls_debug_log("tpm2: failed to import private key data: 0x%x\n", + rc); + return gnutls_assert_val(GNUTLS_E_TPM_ERROR); + } + + rc = _gnutls_tss2_Tss2_MU_TPM2B_PUBLIC_Unmarshal(pubdata->data, pubdata->size, NULL, + &info->pub); + if (rc) { + _gnutls_debug_log("tpm2: failed to import public key data: 0x%x\n", + rc); + return gnutls_assert_val(GNUTLS_E_TPM_ERROR); + } + + info->need_userauth = !emptyauth; + + switch (info->pub.publicArea.type) { + case TPM2_ALG_RSA: + return GNUTLS_PK_RSA; + case TPM2_ALG_ECC: + return GNUTLS_PK_ECDSA; + default: + _gnutls_debug_log("tpm2: unsupported key type %d\n", + info->pub.publicArea.type); + return gnutls_assert_val(GNUTLS_E_TPM_ERROR); + } +} + +uint16_t tpm2_key_curve(struct tpm2_info_st *info) +{ + return info->pub.publicArea.parameters.eccDetail.curveID; +} + +int tpm2_rsa_key_bits(struct tpm2_info_st *info) +{ + return info->pub.publicArea.parameters.rsaDetail.keyBits; +} + +void release_tpm2_ctx(struct tpm2_info_st *info) +{ + if (info) { + zeroize_key(info->ownerauth.buffer, + sizeof(info->ownerauth.buffer)); + zeroize_key(info->userauth.buffer, + sizeof(info->userauth.buffer)); + gnutls_free(info); + } +} + +void tpm2_deinit_fn(gnutls_privkey_t key, void *priv) +{ + release_tpm2_ctx(priv); +} + +static gnutls_ecc_curve_t +tpm2_curve_to_gnutls_curve(TPMI_ECC_CURVE curve) { + switch (curve) { + case TPM2_ECC_NIST_P192: + return GNUTLS_ECC_CURVE_SECP192R1; + case TPM2_ECC_NIST_P224: + return GNUTLS_ECC_CURVE_SECP224R1; + case TPM2_ECC_NIST_P256: + return GNUTLS_ECC_CURVE_SECP256R1; + case TPM2_ECC_NIST_P384: + return GNUTLS_ECC_CURVE_SECP384R1; + case TPM2_ECC_NIST_P521: + return GNUTLS_ECC_CURVE_SECP521R1; + default: + return GNUTLS_ECC_CURVE_INVALID; + } +} + +static int +convert_public_rsa(struct tpm2_info_st *info, gnutls_pk_params_st *params) +{ + int ret; + UINT32 exponent; + + memset(params, 0, sizeof(gnutls_pk_params_st)); + + params->algo = GNUTLS_PK_RSA; + params->params_nr = 2; + + ret = _gnutls_mpi_init_scan_nz(¶ms->params[RSA_MODULUS], + info->pub.publicArea.unique.rsa.buffer, + info->pub.publicArea.unique.rsa.size); + if (ret < 0) { + return gnutls_assert_val(ret); + } + + exponent = info->pub.publicArea.parameters.rsaDetail.exponent; + if (exponent == 0) { + exponent = 0x10001; + } + ret = _gnutls_mpi_init(¶ms->params[RSA_PUB]); + if (ret < 0) { + return gnutls_assert_val(ret); + } + _gnutls_mpi_set_ui(params->params[RSA_PUB], exponent); + + return 0; +} + +static int +convert_public_ecc(struct tpm2_info_st *info, gnutls_pk_params_st *params) +{ + int ret; + + TPMS_ECC_PARMS *detail = &info->pub.publicArea.parameters.eccDetail; + TPMS_ECC_POINT *point = &info->pub.publicArea.unique.ecc; + + memset(params, 0, sizeof(gnutls_pk_params_st)); + + params->algo = GNUTLS_PK_ECDSA; + params->params_nr = 2; + + ret = _gnutls_mpi_init_scan_nz(¶ms->params[ECC_X], + point->x.buffer, point->x.size); + if (ret < 0) { + return gnutls_assert_val(ret); + } + ret = _gnutls_mpi_init_scan_nz(¶ms->params[ECC_Y], + point->y.buffer, point->y.size); + if (ret < 0) { + return gnutls_assert_val(ret); + } + + params->curve = tpm2_curve_to_gnutls_curve(detail->curveID); + if (params->curve == GNUTLS_ECC_CURVE_INVALID) { + return gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + } + + return 0; +} + +int +tpm2_convert_public(gnutls_privkey_t key, + void *_info, + gnutls_pk_params_st *params) +{ + struct tpm2_info_st *info = _info; + + switch (info->pub.publicArea.type) { + case TPM2_ALG_RSA: + return convert_public_rsa(info, params); + case TPM2_ALG_ECC: + return convert_public_ecc(info, params); + default: + _gnutls_debug_log("tpm2: unsupported TPM2 key type %d\n", + info->pub.publicArea.type); + return gnutls_assert_val(GNUTLS_E_TPM_ERROR); + } + + return 0; +} |