/* * OpenConnect (SSL + DTLS) VPN client * * Copyright © 2008-2015 Intel Corporation. * * Author: 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 * version 2.1, as published by the Free Software Foundation. * * This program 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. */ /* * TPM code based on client-tpm.c from * Carolin Latze and Tobias Soder */ #include #include #include #include #include "openconnect-internal.h" #include "gnutls.h" #ifdef HAVE_TROUSERS #include #include struct oc_tpm1_ctx { TSS_HCONTEXT tpm_context; TSS_HKEY srk; TSS_HPOLICY srk_policy; TSS_HKEY tpm_key; TSS_HPOLICY tpm_key_policy; }; /* Signing function for TPM privkeys, set with gnutls_privkey_import_ext() */ static int tpm_sign_fn(gnutls_privkey_t key, void *_vpninfo, const gnutls_datum_t *data, gnutls_datum_t *sig) { struct openconnect_info *vpninfo = _vpninfo; TSS_HHASH hash; int err; vpn_progress(vpninfo, PRG_DEBUG, _("TPM sign function called for %d bytes.\n"), data->size); err = Tspi_Context_CreateObject(vpninfo->tpm1->tpm_context, TSS_OBJECT_TYPE_HASH, TSS_HASH_OTHER, &hash); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to create TPM hash object: %s\n"), Trspi_Error_String(err)); return GNUTLS_E_PK_SIGN_FAILED; } err = Tspi_Hash_SetHashValue(hash, data->size, data->data); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to set value in TPM hash object: %s\n"), Trspi_Error_String(err)); Tspi_Context_CloseObject(vpninfo->tpm1->tpm_context, hash); return GNUTLS_E_PK_SIGN_FAILED; } err = Tspi_Hash_Sign(hash, vpninfo->tpm1->tpm_key, &sig->size, &sig->data); Tspi_Context_CloseObject(vpninfo->tpm1->tpm_context, hash); if (err) { if (vpninfo->tpm1->tpm_key_policy || err != TPM_E_AUTHFAIL) vpn_progress(vpninfo, PRG_ERR, _("TPM hash signature failed: %s\n"), Trspi_Error_String(err)); if (err == TPM_E_AUTHFAIL) return GNUTLS_E_INSUFFICIENT_CREDENTIALS; else return GNUTLS_E_PK_SIGN_FAILED; } return 0; } int load_tpm1_key(struct openconnect_info *vpninfo, gnutls_datum_t *fdata, gnutls_privkey_t *pkey, gnutls_datum_t *pkey_sig) { static const TSS_UUID SRK_UUID = TSS_UUID_SRK; gnutls_datum_t asn1; unsigned int tss_len; char *pass; int ofs, err; err = gnutls_pem_base64_decode_alloc("TSS KEY BLOB", fdata, &asn1); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Error decoding TSS key blob: %s\n"), gnutls_strerror(err)); return -EINVAL; } vpninfo->tpm1 = calloc(1, sizeof(*vpninfo->tpm1)); /* Ick. We have to parse the ASN1 OCTET_STRING for ourselves. */ if (asn1.size < 2 || asn1.data[0] != 0x04 /* OCTET_STRING */) { vpn_progress(vpninfo, PRG_ERR, _("Error in TSS key blob\n")); goto out_blob; } tss_len = asn1.data[1]; ofs = 2; if (tss_len & 0x80) { int lenlen = tss_len & 0x7f; if (asn1.size < 2 + lenlen || lenlen > 3) { vpn_progress(vpninfo, PRG_ERR, _("Error in TSS key blob\n")); goto out_blob; } tss_len = 0; while (lenlen) { tss_len <<= 8; tss_len |= asn1.data[ofs++]; lenlen--; } } if (tss_len + ofs != asn1.size) { vpn_progress(vpninfo, PRG_ERR, _("Error in TSS key blob\n")); goto out_blob; } err = Tspi_Context_Create(&vpninfo->tpm1->tpm_context); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to create TPM context: %s\n"), Trspi_Error_String(err)); goto out_blob; } err = Tspi_Context_Connect(vpninfo->tpm1->tpm_context, NULL); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to connect TPM context: %s\n"), Trspi_Error_String(err)); goto out_context; } err = Tspi_Context_LoadKeyByUUID(vpninfo->tpm1->tpm_context, TSS_PS_TYPE_SYSTEM, SRK_UUID, &vpninfo->tpm1->srk); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to load TPM SRK key: %s\n"), Trspi_Error_String(err)); goto out_context; } err = Tspi_GetPolicyObject(vpninfo->tpm1->srk, TSS_POLICY_USAGE, &vpninfo->tpm1->srk_policy); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to load TPM SRK policy object: %s\n"), Trspi_Error_String(err)); goto out_srk; } pass = vpninfo->cert_password; vpninfo->cert_password = NULL; while (1) { static const char nullpass[20]; /* We don't seem to get the error here... */ if (pass) err = Tspi_Policy_SetSecret(vpninfo->tpm1->srk_policy, TSS_SECRET_MODE_PLAIN, strlen(pass), (BYTE *)pass); else /* Well-known NULL key */ err = Tspi_Policy_SetSecret(vpninfo->tpm1->srk_policy, TSS_SECRET_MODE_SHA1, sizeof(nullpass), (BYTE *)nullpass); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to set TPM PIN: %s\n"), Trspi_Error_String(err)); goto out_srkpol; } free_pass(&pass); /* ... we get it here instead. */ err = Tspi_Context_LoadKeyByBlob(vpninfo->tpm1->tpm_context, vpninfo->tpm1->srk, tss_len, asn1.data + ofs, &vpninfo->tpm1->tpm_key); if (!err) break; if (pass) vpn_progress(vpninfo, PRG_ERR, _("Failed to load TPM key blob: %s\n"), Trspi_Error_String(err)); if (err != TPM_E_AUTHFAIL) goto out_srkpol; err = request_passphrase(vpninfo, "openconnect_tpm_srk", &pass, _("Enter TPM SRK PIN:")); if (err) goto out_srkpol; } gnutls_privkey_init(pkey); /* This would be nicer if there was a destructor callback. I could allocate a data structure with the TPM handles and the vpninfo pointer, and destroy that properly when the key is destroyed. */ gnutls_privkey_import_ext(*pkey, GNUTLS_PK_RSA, vpninfo, tpm_sign_fn, NULL, 0); retry_sign: err = gnutls_privkey_sign_data(*pkey, GNUTLS_DIG_SHA1, 0, fdata, pkey_sig); if (err == GNUTLS_E_INSUFFICIENT_CREDENTIALS) { if (!vpninfo->tpm1->tpm_key_policy) { err = Tspi_Context_CreateObject(vpninfo->tpm1->tpm_context, TSS_OBJECT_TYPE_POLICY, TSS_POLICY_USAGE, &vpninfo->tpm1->tpm_key_policy); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to create key policy object: %s\n"), Trspi_Error_String(err)); goto out_key; } err = Tspi_Policy_AssignToObject(vpninfo->tpm1->tpm_key_policy, vpninfo->tpm1->tpm_key); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to assign policy to key: %s\n"), Trspi_Error_String(err)); goto out_key_policy; } } err = request_passphrase(vpninfo, "openconnect_tpm_key", &pass, _("Enter TPM key PIN:")); if (err) goto out_key_policy; err = Tspi_Policy_SetSecret(vpninfo->tpm1->tpm_key_policy, TSS_SECRET_MODE_PLAIN, strlen(pass), (void *)pass); free_pass(&pass); if (err) { vpn_progress(vpninfo, PRG_ERR, _("Failed to set key PIN: %s\n"), Trspi_Error_String(err)); goto out_key_policy; } goto retry_sign; } free(asn1.data); return 0; out_key_policy: Tspi_Context_CloseObject(vpninfo->tpm1->tpm_context, vpninfo->tpm1->tpm_key_policy); vpninfo->tpm1->tpm_key_policy = 0; out_key: Tspi_Context_CloseObject(vpninfo->tpm1->tpm_context, vpninfo->tpm1->tpm_key); vpninfo->tpm1->tpm_key = 0; out_srkpol: Tspi_Context_CloseObject(vpninfo->tpm1->tpm_context, vpninfo->tpm1->srk_policy); vpninfo->tpm1->srk_policy = 0; out_srk: Tspi_Context_CloseObject(vpninfo->tpm1->tpm_context, vpninfo->tpm1->srk); vpninfo->tpm1->srk = 0; out_context: Tspi_Context_Close(vpninfo->tpm1->tpm_context); vpninfo->tpm1->tpm_context = 0; out_blob: free(asn1.data); free(vpninfo->tpm1); vpninfo->tpm1 = NULL; return -EIO; } void release_tpm1_ctx(struct openconnect_info *vpninfo) { if (!vpninfo->tpm1) return; if (vpninfo->tpm1->tpm_key_policy) { Tspi_Context_CloseObject(vpninfo->tpm1->tpm_context, vpninfo->tpm1->tpm_key_policy); vpninfo->tpm1->tpm_key = 0; } if (vpninfo->tpm1->tpm_key) { Tspi_Context_CloseObject(vpninfo->tpm1->tpm_context, vpninfo->tpm1->tpm_key); vpninfo->tpm1->tpm_key = 0; } if (vpninfo->tpm1->srk_policy) { Tspi_Context_CloseObject(vpninfo->tpm1->tpm_context, vpninfo->tpm1->srk_policy); vpninfo->tpm1->srk_policy = 0; } if (vpninfo->tpm1->srk) { Tspi_Context_CloseObject(vpninfo->tpm1->tpm_context, vpninfo->tpm1->srk); vpninfo->tpm1->srk = 0; } if (vpninfo->tpm1->tpm_context) { Tspi_Context_Close(vpninfo->tpm1->tpm_context); vpninfo->tpm1->tpm_context = 0; } free(vpninfo->tpm1); vpninfo->tpm1 = NULL; }; #endif /* HAVE_TROUSERS */