diff --git a/configure.ac b/configure.ac index ff2a7d82..cbe6d9e0 100644 --- a/configure.ac +++ b/configure.ac @@ -492,7 +492,7 @@ case "$ssl_library" in AC_SUBST(TPM2_LIBS, ['$(TASN1_LIBS) $(TSS2_ESYS_LIBS)']) tss2lib=tss2-esys], [:]) - if test "$tss2lib" = "xxNOTIMPLEMENTEDYETxx"; then + if test "$tss2lib" = ""; then AC_CHECK_LIB([tss], [TSS_Create], [tss2inc=tss2 tss2lib=tss], AC_CHECK_LIB([ibmtss], [TSS_Create], [tss2inc=ibmtss diff --git a/gnutls_tpm2_esys.c b/gnutls_tpm2_esys.c index e000ca74..36d36fa8 100644 --- a/gnutls_tpm2_esys.c +++ b/gnutls_tpm2_esys.c @@ -510,7 +510,7 @@ int install_tpm2_key(struct openconnect_info *vpninfo, gnutls_privkey_t *pkey, g &vpninfo->tpm2->pub); if (r) { vpn_progress(vpninfo, PRG_ERR, - _("Failed to import TPM2 private key data: 0x%x\n"), + _("Failed to import TPM2 public key data: 0x%x\n"), r); goto err_out; } diff --git a/gnutls_tpm2_ibm.c b/gnutls_tpm2_ibm.c index 33f360c8..a0077457 100644 --- a/gnutls_tpm2_ibm.c +++ b/gnutls_tpm2_ibm.c @@ -2,8 +2,10 @@ * OpenConnect (SSL + DTLS) VPN client * * Copyright © 2018 David Woodhouse. + * Copyright © 2017-2018 James Bottomley * - * Author: David Woodhouse + * Authors: James Bottomley + * David Woodhouse * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public License @@ -25,43 +27,563 @@ #define TSSINCLUDE(x) < HAVE_TSS2/x > #include TSSINCLUDE(tss.h) +#include TSSINCLUDE(tssresponsecode.h) +#include TSSINCLUDE(Unmarshal_fp.h) +#include TSSINCLUDE(RSA_Decrypt_fp.h) +#include TSSINCLUDE(Sign_fp.h) + +#define KEY_AUTH_FAILED 0x9a2 +#define PARENT_AUTH_FAILED 0x98e struct oc_tpm2_ctx { TPM2B_PUBLIC pub; TPM2B_PRIVATE priv; - TPM2B_DIGEST userauth; - TPM2B_DIGEST ownerauth; + char *parent_pass, *key_pass; unsigned int need_userauth:1; - unsigned int need_ownerauth:1; + unsigned int parent; }; +static void free_pass(char **p) +{ + if (!*p) + return; + + memset(*p, 0x5a, strlen(*p)); + free(*p); +} + +static void tpm2_error(TPM_RC rc, const char *reason) +{ + const char *msg, *submsg, *num; + + fprintf(stderr, "%s failed with %d\n", reason, rc); + TSS_ResponseCode_toString(&msg, &submsg, &num, rc); + fprintf(stderr, "%s%s%s\n", msg, submsg, num); +} + +static TPM_RC tpm2_readpublic(TSS_CONTEXT *tssContext, TPM_HANDLE handle, + TPMT_PUBLIC *pub) +{ + ReadPublic_In rin; + ReadPublic_Out rout; + TPM_RC rc; + + rin.objectHandle = handle; + rc = TSS_Execute (tssContext, + (RESPONSE_PARAMETERS *)&rout, + (COMMAND_PARAMETERS *)&rin, + NULL, + TPM_CC_ReadPublic, + TPM_RH_NULL, NULL, 0); + if (rc) { + tpm2_error(rc, "TPM2_ReadPublic"); + return rc; + } + if (pub) + *pub = rout.outPublic.publicArea; + + return rc; +} + +static TPM_RC tpm2_get_bound_handle(TSS_CONTEXT *tssContext, TPM_HANDLE *handle, + TPM_HANDLE bind, const char *auth) +{ + TPM_RC rc; + StartAuthSession_In in; + StartAuthSession_Out out; + StartAuthSession_Extra extra; + + memset(&in, 0, sizeof(in)); + memset(&extra, 0 , sizeof(extra)); + in.bind = bind; + extra.bindPassword = auth; + in.sessionType = TPM_SE_HMAC; + in.authHash = TPM_ALG_SHA256; + in.tpmKey = TPM_RH_NULL; + in.symmetric.algorithm = TPM_ALG_AES; + in.symmetric.keyBits.aes = 128; + in.symmetric.mode.aes = TPM_ALG_CFB; + rc = TSS_Execute(tssContext, + (RESPONSE_PARAMETERS *)&out, + (COMMAND_PARAMETERS *)&in, + (EXTRA_PARAMETERS *)&extra, + TPM_CC_StartAuthSession, + TPM_RH_NULL, NULL, 0); + if (rc) { + tpm2_error(rc, "TPM2_StartAuthSession"); + return rc; + } + + *handle = out.sessionHandle; + + return TPM_RC_SUCCESS; +} + +static TPM_RC tpm2_get_session_handle(TSS_CONTEXT *tssContext, TPM_HANDLE *handle, + TPM_HANDLE salt_key, TPM_SE sessionType) +{ + TPM_RC rc; + StartAuthSession_In in; + StartAuthSession_Out out; + StartAuthSession_Extra extra; + + memset(&in, 0, sizeof(in)); + memset(&extra, 0 , sizeof(extra)); + in.bind = TPM_RH_NULL; + in.sessionType = sessionType; + in.authHash = TPM_ALG_SHA256; + in.tpmKey = TPM_RH_NULL; + in.symmetric.algorithm = TPM_ALG_AES; + in.symmetric.keyBits.aes = 128; + in.symmetric.mode.aes = TPM_ALG_CFB; + if (salt_key) { + /* For the TSS to use a key as salt, it must have + * access to the public part. It does this by keeping + * key files, but request the public part just to make + * sure*/ + tpm2_readpublic(tssContext, salt_key, NULL); + /* don't care what rout returns, the purpose of the + * operation was to get the public key parameters into + * the tss so it can construct the salt */ + in.tpmKey = salt_key; + } + rc = TSS_Execute(tssContext, + (RESPONSE_PARAMETERS *)&out, + (COMMAND_PARAMETERS *)&in, + (EXTRA_PARAMETERS *)&extra, + TPM_CC_StartAuthSession, + TPM_RH_NULL, NULL, 0); + if (rc) { + tpm2_error(rc, "TPM2_StartAuthSession"); + return rc; + } + + *handle = out.sessionHandle; + + return TPM_RC_SUCCESS; +} + +static void tpm2_flush_handle(TSS_CONTEXT *tssContext, TPM_HANDLE h) +{ + FlushContext_In in; + + if (!h) + return; + + in.flushHandle = h; + TSS_Execute(tssContext, NULL, + (COMMAND_PARAMETERS *)&in, + NULL, + TPM_CC_FlushContext, + TPM_RH_NULL, NULL, 0); +} + +static void tpm2_flush_srk(TSS_CONTEXT *tssContext, TPM_HANDLE hSRK) +{ + /* only flush if it's a volatile key which we must have created */ + if ((hSRK & 0xFF000000) == 0x80000000) + tpm2_flush_handle(tssContext, hSRK); +} + + + +static TPM_RC tpm2_load_srk(TSS_CONTEXT *tssContext, TPM_HANDLE *h, + const char *auth, TPM_HANDLE hierarchy) +{ + TPM_RC rc; + CreatePrimary_In in; + CreatePrimary_Out out; + TPM_HANDLE session; + + /* SPS owner */ + in.primaryHandle = hierarchy; + if (auth) { + in.inSensitive.sensitive.userAuth.t.size = strlen(auth); + memcpy(in.inSensitive.sensitive.userAuth.t.buffer, auth, strlen(auth)); + } else { + in.inSensitive.sensitive.userAuth.t.size = 0; + } + + /* no sensitive date for storage keys */ + in.inSensitive.sensitive.data.t.size = 0; + /* no outside info */ + in.outsideInfo.t.size = 0; + /* no PCR state */ + in.creationPCR.count = 0; + + /* public parameters for an RSA2048 key */ + in.inPublic.publicArea.type = TPM_ALG_ECC; + in.inPublic.publicArea.nameAlg = TPM_ALG_SHA256; + in.inPublic.publicArea.objectAttributes.val = + TPMA_OBJECT_NODA | + TPMA_OBJECT_SENSITIVEDATAORIGIN | + TPMA_OBJECT_USERWITHAUTH | + TPMA_OBJECT_DECRYPT | + TPMA_OBJECT_RESTRICTED; + in.inPublic.publicArea.parameters.eccDetail.symmetric.algorithm = TPM_ALG_AES; + in.inPublic.publicArea.parameters.eccDetail.symmetric.keyBits.aes = 128; + in.inPublic.publicArea.parameters.eccDetail.symmetric.mode.aes = TPM_ALG_CFB; + in.inPublic.publicArea.parameters.eccDetail.scheme.scheme = TPM_ALG_NULL; + in.inPublic.publicArea.parameters.eccDetail.curveID = TPM_ECC_NIST_P256; + in.inPublic.publicArea.parameters.eccDetail.kdf.scheme = TPM_ALG_NULL; + + in.inPublic.publicArea.unique.ecc.x.t.size = 0; + in.inPublic.publicArea.unique.ecc.y.t.size = 0; + in.inPublic.publicArea.authPolicy.t.size = 0; + + /* use a bound session here because we have no known key objects + * to encrypt a salt to */ + rc = tpm2_get_bound_handle(tssContext, &session, hierarchy, auth); + if (rc) + return rc; + + rc = TSS_Execute(tssContext, + (RESPONSE_PARAMETERS *)&out, + (COMMAND_PARAMETERS *)&in, + NULL, + TPM_CC_CreatePrimary, + session, auth, TPMA_SESSION_DECRYPT, + TPM_RH_NULL, NULL, 0); + + if (rc) { + tpm2_error(rc, "TSS_CreatePrimary"); + tpm2_flush_handle(tssContext, session); + return rc; + } + + *h = out.objectHandle; + + return 0; +} + + +static TPM_HANDLE tpm2_load_key(struct openconnect_info *vpninfo, TSS_CONTEXT **tsscp) +{ + TSS_CONTEXT *tssContext; + Load_In in; + Load_Out out; + TPM_HANDLE key = 0; + TPM_RC rc; + TPM_HANDLE session; + char *pass = vpninfo->tpm2->parent_pass; + int need_pw = 0; + + vpninfo->tpm2->parent_pass = NULL; + + rc = TSS_Create(&tssContext); + if (rc) { + tpm2_error(rc, "TSS_Create"); + return 0; + } + + memset(&in, 0, sizeof(in)); + memset(&out, 0, sizeof(out)); + + if (vpninfo->tpm2->parent >> HR_SHIFT == TPM_HT_PERSISTENT) { + if (!pass) { + TPMT_PUBLIC pub; + rc = tpm2_readpublic(tssContext, vpninfo->tpm2->parent, &pub); + if (rc) + goto out; + + if (!(pub.objectAttributes.val & TPMA_OBJECT_NODA)) + need_pw = 1; + } + + in.parentHandle = vpninfo->tpm2->parent; + } else { + reauth_srk: + rc = tpm2_load_srk(tssContext, &in.parentHandle, pass, vpninfo->tpm2->parent); + if (rc == KEY_AUTH_FAILED) { + free_pass(&pass); + if (!request_passphrase(vpninfo, "openconnect_tpm2_hierarchy", &pass, + _("Enter TPM2 %s hierarchy password:"), "owner")) { + goto reauth_srk; + } + } + if (rc) + goto out; + } + rc = tpm2_get_session_handle(tssContext, &session, in.parentHandle, + TPM_SE_HMAC); + if (rc) + goto out_flush_srk; + + memcpy(&in.inPublic, &vpninfo->tpm2->pub, sizeof(in.inPublic)); + memcpy(&in.inPrivate, &vpninfo->tpm2->priv, sizeof(in.inPrivate)); + if (need_pw && !pass) { + reauth_parent: + if (request_passphrase(vpninfo, "openconnect_tpm2_parent", &pass, + _("Enter TPM2 parent key password:"))) { + tpm2_flush_handle(tssContext, session); + goto out_flush_srk; + } + } + rc = TSS_Execute(tssContext, + (RESPONSE_PARAMETERS *)&out, + (COMMAND_PARAMETERS *)&in, + NULL, + TPM_CC_Load, + session, pass, 0, + TPM_RH_NULL, NULL, 0); + if (rc == PARENT_AUTH_FAILED) { + free_pass(&pass); + goto reauth_parent; + } + if (rc) { + tpm2_error(rc, "TPM2_Load"); + tpm2_flush_handle(tssContext, session); + } + else + key = out.objectHandle; + + out_flush_srk: + tpm2_flush_srk(tssContext, in.parentHandle); + out: + vpninfo->tpm2->parent_pass = pass; + if (!key) + TSS_Delete(tssContext); + else + *tsscp = tssContext; + return key; +} + +static void tpm2_unload_key(TSS_CONTEXT *tssContext, TPM_HANDLE key) +{ + tpm2_flush_handle(tssContext, key); + + TSS_Delete(tssContext); +} + +#define PKCS1_PAD_OVERHEAD 11 + int tpm2_rsa_sign_hash_fn(gnutls_privkey_t key, gnutls_sign_algorithm_t algo, void *_vpninfo, unsigned int flags, const gnutls_datum_t *data, gnutls_datum_t *sig) { - return GNUTLS_E_PK_SIGN_FAILED; + struct openconnect_info *vpninfo = _vpninfo; + TSS_CONTEXT *tssContext = NULL; + RSA_Decrypt_In in; + RSA_Decrypt_Out out; + int ret = GNUTLS_E_PK_SIGN_FAILED; + TPM_HANDLE authHandle; + TPM_RC rc; + char *pass = vpninfo->tpm2->key_pass; + + vpninfo->tpm2->key_pass = NULL; + + memset(&in, 0, sizeof(in)); + + in.cipherText.t.size = vpninfo->tpm2->pub.publicArea.unique.rsa.t.size; + + if (data->size + PKCS1_PAD_OVERHEAD > in.cipherText.t.size) { + vpn_progress(vpninfo, PRG_ERR, + _("TPM2 digest too large: %d > %d\n"), + data->size, in.cipherText.t.size - PKCS1_PAD_OVERHEAD); + return GNUTLS_E_PK_SIGN_FAILED; + } + + /* PKCS#1 padding */ + in.cipherText.t.buffer[0] = 0; + in.cipherText.t.buffer[1] = 1; + memset(in.cipherText.t.buffer + 2, 0xff, in.cipherText.t.size - data->size - 3); + in.cipherText.t.buffer[in.cipherText.t.size - data->size - 1] = 0; + memcpy(in.cipherText.t.buffer + in.cipherText.t.size - data->size, data->data, data->size); + + in.inScheme.scheme = TPM_ALG_NULL; + in.keyHandle = tpm2_load_key(vpninfo, &tssContext); + in.label.t.size = 0; + if (!in.keyHandle) + return GNUTLS_E_PK_SIGN_FAILED; + + rc = tpm2_get_session_handle(tssContext, &authHandle, 0, TPM_SE_HMAC); + if (rc) + goto out; + + reauth: + rc = TSS_Execute(tssContext, + (RESPONSE_PARAMETERS *)&out, + (COMMAND_PARAMETERS *)&in, + NULL, + TPM_CC_RSA_Decrypt, + authHandle, pass, 0, + TPM_RH_NULL, NULL, 0); + if (rc == KEY_AUTH_FAILED) { + free_pass(&pass); + if (!request_passphrase(vpninfo, "openconnect_tpm2_key", + &pass, _("Enter TPM2 key password:"))) + goto reauth; + } + if (rc) { + tpm2_error(rc, "TPM2_RSA_Decrypt"); + /* failure means auth handle is not flushed */ + tpm2_flush_handle(tssContext, authHandle); + goto out; + } + + vpninfo->tpm2->key_pass = pass; + + sig->data = malloc(out.message.t.size); + if (!sig->data) + goto out; + + sig->size = out.message.t.size; + memcpy(sig->data, out.message.t.buffer, out.message.t.size); + ret = 0; + out: + tpm2_unload_key(tssContext, in.keyHandle); + + return ret; } int tpm2_ec_sign_hash_fn(gnutls_privkey_t key, gnutls_sign_algorithm_t algo, void *_vpninfo, unsigned int flags, const gnutls_datum_t *data, gnutls_datum_t *sig) { - return GNUTLS_E_PK_SIGN_FAILED; + struct openconnect_info *vpninfo = _vpninfo; + TSS_CONTEXT *tssContext = NULL; + Sign_In in; + Sign_Out out; + int ret = GNUTLS_E_PK_SIGN_FAILED; + TPM_HANDLE authHandle; + TPM_RC rc; + char *pass = vpninfo->tpm2->key_pass; + gnutls_datum_t sig_r, sig_s; + + vpninfo->tpm2->key_pass = NULL; + + vpn_progress(vpninfo, PRG_DEBUG, + _("TPM2 EC sign function called for %d bytes.\n"), + data->size); + + memset(&in, 0, sizeof(in)); + + switch (algo) { + case GNUTLS_SIGN_ECDSA_SHA1: in.inScheme.details.ecdsa.hashAlg = TPM_ALG_SHA1; break; + case GNUTLS_SIGN_ECDSA_SHA256: in.inScheme.details.ecdsa.hashAlg = TPM_ALG_SHA256; break; + case GNUTLS_SIGN_ECDSA_SHA384: in.inScheme.details.ecdsa.hashAlg = TPM_ALG_SHA384; break; +#ifdef TPM_ALG_SHA512 + case GNUTLS_SIGN_ECDSA_SHA512: in.inScheme.details.ecdsa.hashAlg = TPM_ALG_SHA512; break; +#endif + default: + vpn_progress(vpninfo, PRG_ERR, + _("Unknown TPM2 EC digest size %d\n"), + data->size); + return GNUTLS_E_PK_SIGN_FAILED; + } + + in.inScheme.scheme = TPM_ALG_ECDSA; + in.digest.t.size = data->size; + memcpy(in.digest.t.buffer, data->data, data->size); + in.validation.tag = TPM_ST_HASHCHECK; + in.validation.hierarchy = TPM_RH_NULL; + in.validation.digest.t.size = 0; + + in.keyHandle = tpm2_load_key(vpninfo, &tssContext); + if (!in.keyHandle) + return GNUTLS_E_PK_SIGN_FAILED; + + rc = tpm2_get_session_handle(tssContext, &authHandle, 0, TPM_SE_HMAC); + if (rc) + goto out; + + reauth: + rc = TSS_Execute(tssContext, + (RESPONSE_PARAMETERS *)&out, + (COMMAND_PARAMETERS *)&in, + NULL, + TPM_CC_Sign, + authHandle, pass, 0, + TPM_RH_NULL, NULL, 0); + if (rc == KEY_AUTH_FAILED) { + free_pass(&pass); + if (!request_passphrase(vpninfo, "openconnect_tpm2_key", + &pass, _("Enter TPM2 key password:"))) + goto reauth; + } + if (rc) { + tpm2_error(rc, "TPM2_Sign"); + tpm2_flush_handle(tssContext, authHandle); + goto out; + } + + vpninfo->tpm2->key_pass = pass; + sig_r.data = out.signature.signature.ecdsa.signatureR.t.buffer; + sig_r.size = out.signature.signature.ecdsa.signatureR.t.size; + sig_s.data = out.signature.signature.ecdsa.signatureS.t.buffer; + sig_s.size = out.signature.signature.ecdsa.signatureS.t.size; + + ret = gnutls_encode_rs_value(sig, &sig_r, &sig_s); + out: + tpm2_unload_key(tssContext, in.keyHandle); + + + return ret; } int install_tpm2_key(struct openconnect_info *vpninfo, gnutls_privkey_t *pkey, gnutls_datum_t *pkey_sig, unsigned int parent, int emptyauth, gnutls_datum_t *privdata, gnutls_datum_t *pubdata) { - vpn_progress(vpninfo, PRG_ERR, - _("TPM2 support via IBM TSS not yet implemented\n")); + TPM_RC rc; + BYTE *der; + INT32 dersize; + + if (parent >> HR_SHIFT != TPM_HT_PERSISTENT && + parent != TPM_RH_OWNER && parent != TPM_RH_NULL && + parent != TPM_RH_ENDORSEMENT && parent != TPM_RH_PLATFORM) { + vpn_progress(vpninfo, PRG_ERR, + _("Invalid TPM2 parent handle 0x%08x\n"), parent); + return -EINVAL; + } + vpninfo->tpm2 = calloc(1, sizeof(*vpninfo->tpm2)); + if (!vpninfo->tpm2) + return -ENOMEM; + + vpninfo->tpm2->parent = parent; + vpninfo->tpm2->need_userauth = !emptyauth; + + der = privdata->data; + dersize = privdata->size; + rc = TPM2B_PRIVATE_Unmarshal(&vpninfo->tpm2->priv, &der, &dersize); + if (rc) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to import TPM2 private key data: 0x%x\n"), + rc); + goto err_out; + } + + der = pubdata->data; + dersize = pubdata->size; + rc = TPM2B_PUBLIC_Unmarshal(&vpninfo->tpm2->pub, &der, &dersize, FALSE); + if (rc) { + vpn_progress(vpninfo, PRG_ERR, + _("Failed to import TPM2 public key data: 0x%x\n"), + rc); + goto err_out; + } + + switch(vpninfo->tpm2->pub.publicArea.type) { + case TPM_ALG_RSA: return GNUTLS_PK_RSA; + case TPM_ALG_ECC: return GNUTLS_PK_ECDSA; + } + + vpn_progress(vpninfo, PRG_ERR, + _("Unsupported TPM2 key type %d\n"), + vpninfo->tpm2->pub.publicArea.type); +; + err_out: + release_tpm2_ctx(vpninfo); return -EINVAL; } void release_tpm2_ctx(struct openconnect_info *vpninfo) { - if (vpninfo->tpm2) + if (vpninfo->tpm2) { + free_pass(&vpninfo->tpm2->parent_pass); + free_pass(&vpninfo->tpm2->key_pass); free(vpninfo->tpm2); - vpninfo->tpm2 = NULL; + vpninfo->tpm2 = NULL; + } } diff --git a/www/building.xml b/www/building.xml index 7200a92a..64e0b87a 100644 --- a/www/building.xml +++ b/www/building.xml @@ -32,7 +32,7 @@ And optionally also:
  • libp11 (also needed for PKCS#11 support if using OpenSSL)
  • libproxy
  • trousers (for TPMv1 support if using GnuTLS)
  • -
  • tss2-esys and libtasn1 (for TPMv2 support if using GnuTLS)
  • +
  • libtasn1 and either tss2-esys or IBM's TPM 2.0 TSS. (for TPMv2 support if using GnuTLS)
  • libstoken (for SecurID software token support)
  • libpskc (for RFC6030 PSKC file storage of HOTP/TOTP keys)
  • libpcsclite (for Yubikey hardware HOTP/HOTP support)
  • diff --git a/www/tpm.xml b/www/tpm.xml index df17c8bc..d04295d0 100644 --- a/www/tpm.xml +++ b/www/tpm.xml @@ -47,7 +47,7 @@ The tpm2-tss-engine -

    The GnuTLS build of OpenConnect supports the former variant, when built with the libtasn1 and tss2-esys libraries.

    +

    The GnuTLS build of OpenConnect supports the former variant, when built with libtasn1 and either tss2-esys or IBM TSS 2.0 libraries.