diff options
-rw-r--r-- | NEWS | 6 | ||||
-rw-r--r-- | README.md | 8 | ||||
-rw-r--r-- | configure.ac | 37 | ||||
-rw-r--r-- | lib/Makefile.am | 9 | ||||
-rw-r--r-- | lib/abstract_int.h | 5 | ||||
-rw-r--r-- | lib/crypto-backend.h | 11 | ||||
-rw-r--r-- | lib/gnutls.asn | 8 | ||||
-rw-r--r-- | lib/gnutls_asn1_tab.c | 10 | ||||
-rw-r--r-- | lib/nettle/Makefile.am | 3 | ||||
-rw-r--r-- | lib/nettle/int/rsa-pad.c | 123 | ||||
-rw-r--r-- | lib/privkey.c | 22 | ||||
-rw-r--r-- | lib/tpm2.c | 296 | ||||
-rw-r--r-- | lib/tpm2.h | 58 | ||||
-rw-r--r-- | lib/tpm2_esys.c | 896 | ||||
-rw-r--r-- | src/Makefile.am | 4 | ||||
-rw-r--r-- | tests/Makefile.am | 4 | ||||
-rwxr-xr-x | tests/tpm2.sh | 221 |
17 files changed, 1710 insertions, 11 deletions
@@ -13,6 +13,12 @@ See the end for copying conditions. configuration directive now also disables TLS ciphersuites that use it as a PRF algorithm. +** libgnutls: The tpm2-tss-engine compatible private blobs can be loaded and + used as a gnutls_privkey_t. The code was originally written for the + OpenConnect VPN project by David Woodhouse. To generate such blobs, + use the tpm2tss-genkey tool from tpm2-tss-engine: + https://github.com/tpm2-software/tpm2-tss-engine/#rsa-operations + ** API and ABI modifications: GNUTLS_PRIVKEY_FLAG_RSA_PSS_FIXED_SALT_LENGTH: new flag in gnutls_privkey_flags_t GNUTLS_VERIFY_RSA_PSS_FIXED_SALT_LENGTH: new flag in gnutls_certificate_verify_flags @@ -42,10 +42,12 @@ We require several tools to check out and build the software, including: * [bison](https://www.gnu.org/software/bison) (for datetime parser in certtool) * [libunbound](https://unbound.net/) (for DANE support) * [libabigail](https://pagure.io/libabigail/) (for abi comparison in make dist) +* [tpm2-tss](https://github.com/tpm2-software/tpm2-tss) (for TPM 2.0 support; optional) * [tcsd](https://trousers.sourceforge.net/) (for TPM support; optional) * [swtpm](https://github.com/stefanberger/swtpm) (for TPM test; optional) -* [ncat](https://nmap.org/download.html) (for TPM test; optional) * [tpm-tools](https://trousers.sourceforge.net/) (for TPM test; optional) +* [tpm2-tools](https://github.com/tpm2-software/tpm2-tools/) (for TPM 2.0 test; optional) +* [ncat](https://nmap.org/download.html) (for TPM test; optional) * [expect](https://core.tcl.tk/expect/index) (for TPM test; optional) The required software is typically distributed with your operating @@ -57,7 +59,7 @@ Debian/Ubuntu: apt-get install -y dash git-core autoconf libtool gettext autopoint apt-get install -y automake autogen nettle-dev libp11-kit-dev libtspi-dev libunistring-dev apt-get install -y guile-2.2-dev libtasn1-6-dev libidn2-0-dev gawk gperf -apt-get install -y libunbound-dev dns-root-data bison gtk-doc-tools +apt-get install -y libtss2-dev libunbound-dev dns-root-data bison gtk-doc-tools apt-get install -y texinfo texlive texlive-generic-recommended texlive-extra-utils ``` @@ -68,7 +70,7 @@ Fedora/RHEL: ``` yum install -y dash git autoconf libtool gettext-devel automake autogen patch yum install -y nettle-devel p11-kit-devel autogen-libopts-devel libunistring-devel -yum install -y trousers-devel guile22-devel libtasn1-devel libidn2-devel gawk gperf +yum install -y tpm2-tss-devel trousers-devel guile22-devel libtasn1-devel libidn2-devel gawk gperf yum install -y libtasn1-tools unbound-devel bison gtk-doc texinfo texlive ``` diff --git a/configure.ac b/configure.ac index c22acef7c3..b689a5f94a 100644 --- a/configure.ac +++ b/configure.ac @@ -852,10 +852,32 @@ AM_CONDITIONAL(P11KIT_0_23_11_API, $PKG_CONFIG --atleast-version=0.23.11 p11-kit AM_CONDITIONAL(ENABLE_PKCS11, test "$with_p11_kit" != "no") AC_ARG_WITH(tpm, + AS_HELP_STRING([--without-tpm2], + [Disable TPM2 support.]), + [with_tpm2=$withval], [with_tpm2=auto]) +if test "$with_tpm2" != "no"; then + PKG_CHECK_MODULES(TSS2, [tss2-esys tss2-mu tss2-tctildr], + [have_tpm2=yes], [have_tpm2=no]) + if test "$have_tpm2" = "yes"; then + tss2lib="tss2-esys tss2-mu tss2-tctildr" + AC_DEFINE([HAVE_TSS2], 1, [Have TSS2]) + elif test "$with_tpm2" = "yes"; then + AC_MSG_ERROR([[ +*** +*** TPM2 support was requested but the required libraries were not found. +*** To disable TPM2 support use --without-tpm2, otherwise you may get tpm2-tss from +*** https://github.com/tpm2-software/tpm2-tss +*** ]]) + fi +fi + +AM_CONDITIONAL(ENABLE_TPM2, test "$have_tpm2" = "yes") + +AC_ARG_WITH(tpm, AS_HELP_STRING([--without-tpm], [Disable TPM (trousers) support.]), [with_tpm=$withval], [with_tpm=yes]) -if test "$with_tpm" != "no"; then +if test "$with_tpm" != "no" && test "$with_tpm2" = "no"; then LIBS="$oldlibs -ltspi" AC_MSG_CHECKING([for tss library]) AC_LINK_IFELSE([AC_LANG_PROGRAM([ @@ -875,10 +897,13 @@ if test "$with_tpm" != "no"; then *** ]]) with_tpm=no]) LIBS="$oldlibs" +else + with_tpm=no fi AM_CONDITIONAL(ENABLE_TROUSERS, test "$with_tpm" != "no") + for l in /usr/lib64 /usr/lib /lib64 /lib /usr/lib/x86_64-linux-gnu/; do if test -f "${l}/libtspi.so.1";then default_trousers_lib="${l}/libtspi.so.1" @@ -1220,14 +1245,22 @@ AC_MSG_NOTICE([External hardware support: Random gen. variant: $rnd_variant PKCS#11 support: $with_p11_kit TPM support: $with_tpm + TPM2 support: $have_tpm2 KTLS support: $enable_ktls ]) -if test -n "$ac_trousers_lib";then + +if test -n "$ac_trousers_lib" && test "$with_tpm" != "no";then AC_MSG_NOTICE([ TPM library: $ac_trousers_lib ]) fi +if test "$with_tpm2" != "no";then +AC_MSG_NOTICE([ + TPM2 library: $tss2lib +]) +fi + AC_MSG_NOTICE([Optional features: (note that included applications might not compile properly if features are disabled) diff --git a/lib/Makefile.am b/lib/Makefile.am index 1a6a7f963c..50cd3dbf7c 100644 --- a/lib/Makefile.am +++ b/lib/Makefile.am @@ -43,7 +43,8 @@ AM_CPPFLAGS = \ -I$(builddir)/includes \ -I$(srcdir)/x509 \ $(LIBTASN1_CFLAGS) \ - $(P11_KIT_CFLAGS) + $(P11_KIT_CFLAGS) \ + $(TPM2_CFLAGS) if !HAVE_LIBUNISTRING SUBDIRS += unistring @@ -87,6 +88,10 @@ if ENABLE_GOST COBJECTS += vko.c endif +if ENABLE_TPM2 +COBJECTS += tpm2.c tpm2.h tpm2_esys.c +endif + if WINDOWS COBJECTS += system/keys-win.c else @@ -151,7 +156,7 @@ libgnutls_la_LIBADD = ../gl/libgnu.la x509/libgnutls_x509.la \ auth/libgnutls_auth.la algorithms/libgnutls_alg.la \ extras/libgnutls_extras.la thirdparty_libadd = $(LTLIBZ) $(LTLIBINTL) $(LIBSOCKET) $(LTLIBNSL) \ - $(P11_KIT_LIBS) $(LIB_SELECT) $(GNUTLS_LIBS_PRIVATE) + $(P11_KIT_LIBS) $(LIB_SELECT) $(TSS2_LIBS) $(GNUTLS_LIBS_PRIVATE) if HAVE_LIBIDN2 thirdparty_libadd += $(LIBIDN2_LIBS) diff --git a/lib/abstract_int.h b/lib/abstract_int.h index 8f436b0a9f..cc839ccf84 100644 --- a/lib/abstract_int.h +++ b/lib/abstract_int.h @@ -25,6 +25,10 @@ #include <gnutls/abstract.h> +typedef int (*gnutls_privkey_pk_params_func) (gnutls_privkey_t key, + void *userdata, + gnutls_pk_params_st *params); + struct gnutls_privkey_st { gnutls_privkey_type_t type; gnutls_pk_algorithm_t pk_algorithm; @@ -42,6 +46,7 @@ struct gnutls_privkey_st { gnutls_privkey_decrypt_func2 decrypt_func2; gnutls_privkey_deinit_func deinit_func; gnutls_privkey_info_func info_func; + gnutls_privkey_pk_params_func pk_params_func; void *userdata; unsigned bits; } ext; diff --git a/lib/crypto-backend.h b/lib/crypto-backend.h index 6cc1853cbe..9874033221 100644 --- a/lib/crypto-backend.h +++ b/lib/crypto-backend.h @@ -465,4 +465,15 @@ int _gnutls_gost_key_unwrap(gnutls_gost_paramset_t gost_params, const gnutls_datum_t *imit, gnutls_datum_t *cek); +int +_gnutls_rsa_pkcs1_sign_pad(size_t key_bits, + const gnutls_datum_t *data, + unsigned char *buffer, size_t buffer_size); + +int +_gnutls_rsa_pss_sign_pad(gnutls_x509_spki_st *params, + size_t key_bits, + const gnutls_datum_t *data, + unsigned char *buffer, size_t buffer_size); + #endif /* GNUTLS_LIB_CRYPTO_BACKEND_H */ diff --git a/lib/gnutls.asn b/lib/gnutls.asn index aca39fd296..e1076fe6f7 100644 --- a/lib/gnutls.asn +++ b/lib/gnutls.asn @@ -174,4 +174,12 @@ GostR3410-KeyTransport ::= SEQUENCE { transportParameters [0] IMPLICIT GostR3410-TransportParameters OPTIONAL } +TPMKey ::= SEQUENCE { + type OBJECT IDENTIFIER, + emptyAuth [0] EXPLICIT BOOLEAN OPTIONAL, + parent INTEGER, + pubkey OCTET STRING, + privkey OCTET STRING +} + END diff --git a/lib/gnutls_asn1_tab.c b/lib/gnutls_asn1_tab.c index 0f56619559..90bbf0bd42 100644 --- a/lib/gnutls_asn1_tab.c +++ b/lib/gnutls_asn1_tab.c @@ -25,7 +25,6 @@ const asn1_static_node gnutls_asn1_tab[] = { { "algorithm", 1073741836, NULL }, { "seed", 7, NULL }, { "OtherPrimeInfos", 1612709899, NULL }, - { "MAX", 1074266122, "1"}, { NULL, 2, "OtherPrimeInfo"}, { "OtherPrimeInfo", 1610612741, NULL }, { "prime", 1073741827, NULL }, @@ -119,9 +118,16 @@ const asn1_static_node gnutls_asn1_tab[] = { { "ephemeralPublicKey", 1610637314, "SubjectPublicKeyInfo"}, { NULL, 4104, "0"}, { "ukm", 7, NULL }, - { "GostR3410-KeyTransport", 536870917, NULL }, + { "GostR3410-KeyTransport", 1610612741, NULL }, { "sessionEncryptedKey", 1073741826, "Gost28147-89-EncryptedKey"}, { "transportParameters", 536895490, "GostR3410-TransportParameters"}, { NULL, 4104, "0"}, + { "TPMKey", 536870917, NULL }, + { "type", 1073741836, NULL }, + { "emptyAuth", 1610637316, NULL }, + { NULL, 2056, "0"}, + { "parent", 1073741827, NULL }, + { "pubkey", 1073741831, NULL }, + { "privkey", 7, NULL }, { NULL, 0, NULL } }; diff --git a/lib/nettle/Makefile.am b/lib/nettle/Makefile.am index a3aa22a64d..bea7c7eda9 100644 --- a/lib/nettle/Makefile.am +++ b/lib/nettle/Makefile.am @@ -46,7 +46,8 @@ libcrypto_la_SOURCES = pk.c mpi.c mac.c cipher.c init.c \ int/dsa-compute-k.c int/dsa-compute-k.h \ int/ecdsa-compute-k.c int/ecdsa-compute-k.h \ int/mpn-base256.c int/mpn-base256.h \ - int/block8.h backport/block-internal.h + int/block8.h backport/block-internal.h \ + int/rsa-pad.c if WINDOWS if HAVE_BCRYPT diff --git a/lib/nettle/int/rsa-pad.c b/lib/nettle/int/rsa-pad.c new file mode 100644 index 0000000000..401bad33f7 --- /dev/null +++ b/lib/nettle/int/rsa-pad.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2021 Red Hat, Inc. + * + * Author: 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 <http://www.gnu.org/licenses/> + * + */ + +#if HAVE_CONFIG_H +# include "config.h" +#endif + +#include "gnutls_int.h" + +#include <nettle/pkcs1.h> +#include <nettle/pss.h> +#include <nettle/sha2.h> + +/* These are helper functions to perform RSA padding before signing, only used + * for the crypto backends that do not support RSA-PKCS1/PSS natively for the + * use with TLS (such as TPM2); not recommended for general usage. + */ + +int +_gnutls_rsa_pkcs1_sign_pad(size_t key_bits, + const gnutls_datum_t *data, + unsigned char *buffer, size_t buffer_size) +{ + size_t key_size = (key_bits + 7) / 8; + size_t size; + mpz_t m; + int ret = 0; + + mpz_init(m); + if (!pkcs1_rsa_digest_encode(m, key_size, data->size, data->data)) { + ret = gnutls_assert_val(GNUTLS_E_PK_SIGN_FAILED); + goto out; + } + + size = nettle_mpz_sizeinbase_256_u(m); + if (size > buffer_size) { + ret = gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER); + goto out; + } + nettle_mpz_get_str_256(buffer_size, buffer, m); + + out: + mpz_clear(m); + return ret; +} + +int +_gnutls_rsa_pss_sign_pad(gnutls_x509_spki_st *params, + size_t key_bits, + const gnutls_datum_t *data, + unsigned char *buffer, size_t buffer_size) +{ + mpz_t m; + int ret = 0; + const struct nettle_hash *hash; + uint8_t salt[SHA512_DIGEST_SIZE]; + size_t size; + + mpz_init(m); + + switch (params->rsa_pss_dig) { + case GNUTLS_DIG_SHA256: + hash = &nettle_sha256; + break; + case GNUTLS_DIG_SHA384: + hash = &nettle_sha384; + break; + case GNUTLS_DIG_SHA512: + hash = &nettle_sha512; + break; + default: + ret = gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + goto out; + } + + if (data->size != hash->digest_size) { + ret = gnutls_assert_val(GNUTLS_E_INVALID_REQUEST); + goto out; + } + + ret = gnutls_rnd(GNUTLS_RND_NONCE, salt, params->salt_size); + if (ret < 0) { + goto out; + } + + /* The emBits for EMSA-PSS encoding is actually one *fewer* + * bit than the RSA modulus. */ + if (!pss_encode_mgf1(m, key_bits - 1, hash, params->salt_size, salt, + data->data)) { + ret = gnutls_assert_val(GNUTLS_E_PK_SIGN_FAILED); + goto out; + } + + size = nettle_mpz_sizeinbase_256_u(m); + if (size > buffer_size) { + ret = gnutls_assert_val(GNUTLS_E_SHORT_MEMORY_BUFFER); + goto out; + } + nettle_mpz_get_str_256(buffer_size, buffer, m); + + out: + mpz_clear(m); + return ret; +} diff --git a/lib/privkey.c b/lib/privkey.c index 7b983b145d..0b774430c1 100644 --- a/lib/privkey.c +++ b/lib/privkey.c @@ -35,6 +35,7 @@ #include <fips.h> #include <system-keys.h> #include "urls.h" +#include "tpm2.h" #include "pkcs11_int.h" #include <abstract_int.h> @@ -265,6 +266,14 @@ _gnutls_privkey_get_mpis(gnutls_privkey_t key, gnutls_pk_params_st * params) } #endif default: + if (key->key.ext.pk_params_func) { + ret = key->key.ext.pk_params_func(key, + key->key.ext.userdata, + params); + if (ret < 0) + return gnutls_assert_val(ret); + return ret; + } gnutls_assert(); return GNUTLS_E_INVALID_REQUEST; } @@ -1658,7 +1667,7 @@ gnutls_privkey_decrypt_data2(gnutls_privkey_t key, * #gnutls_privkey_t type. * * The supported formats are basic unencrypted key, PKCS8, PKCS12, - * and the openssl format. + * TSS2, and the openssl format. * * Returns: On success, %GNUTLS_E_SUCCESS (0) is returned, otherwise a * negative error value. @@ -1673,6 +1682,17 @@ int gnutls_privkey_import_x509_raw(gnutls_privkey_t pkey, gnutls_x509_privkey_t xpriv; int ret; +#ifdef HAVE_TSS2 + if (format == GNUTLS_X509_FMT_PEM && + memmem(data->data, data->size, "--BEGIN TSS2", 12) != NULL) { + ret = _gnutls_load_tpm2_key(pkey, data); + if (ret < 0) + return gnutls_assert_val(ret); + + return 0; + } +#endif + ret = gnutls_x509_privkey_init(&xpriv); if (ret < 0) return gnutls_assert_val(ret); diff --git a/lib/tpm2.c b/lib/tpm2.c new file mode 100644 index 0000000000..5c293a553c --- /dev/null +++ b/lib/tpm2.c @@ -0,0 +1,296 @@ +/* + * 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/> + * + */ + +#include <config.h> + +#include "gnutls_int.h" +#include "global.h" +#include "tpm2.h" +#include "pin.h" +#include "abstract_int.h" + +#include <string.h> +#include <libtasn1.h> + +static const char OID_loadable_key[] = "2.23.133.10.1.3"; + +static int rsa_key_info(gnutls_privkey_t key, unsigned int flags, void *_info) +{ + if (flags & GNUTLS_PRIVKEY_INFO_PK_ALGO) { + return GNUTLS_PK_RSA; + } + + if (flags & GNUTLS_PRIVKEY_INFO_PK_ALGO_BITS) { + struct tpm2_info_st *info = _info; + + return tpm2_rsa_key_bits(info); + } + + if (flags & GNUTLS_PRIVKEY_INFO_HAVE_SIGN_ALGO) { + gnutls_sign_algorithm_t algo = GNUTLS_FLAGS_TO_SIGN_ALGO(flags); + switch (algo) { + case GNUTLS_SIGN_RSA_RAW: + case GNUTLS_SIGN_RSA_SHA1: + case GNUTLS_SIGN_RSA_SHA256: + case GNUTLS_SIGN_RSA_SHA384: + case GNUTLS_SIGN_RSA_SHA512: + return 1; + + case GNUTLS_SIGN_RSA_PSS_SHA256: + case GNUTLS_SIGN_RSA_PSS_RSAE_SHA256: + case GNUTLS_SIGN_RSA_PSS_SHA384: + case GNUTLS_SIGN_RSA_PSS_RSAE_SHA384: + case GNUTLS_SIGN_RSA_PSS_SHA512: + case GNUTLS_SIGN_RSA_PSS_RSAE_SHA512: + return 1; + + default: + _gnutls_debug_log("tpm2: unsupported RSA sign algo %s\n", + gnutls_sign_get_name(algo)); + return 0; + } + } + + if (flags & GNUTLS_PRIVKEY_INFO_SIGN_ALGO) { + return GNUTLS_SIGN_RSA_RAW; + } + + return -1; +} + +static int ec_key_info(gnutls_privkey_t key, unsigned int flags, void *_info) +{ + if (flags & GNUTLS_PRIVKEY_INFO_PK_ALGO) { + return GNUTLS_PK_EC; + } + + if (flags & GNUTLS_PRIVKEY_INFO_HAVE_SIGN_ALGO) { + gnutls_sign_algorithm_t algo = GNUTLS_FLAGS_TO_SIGN_ALGO(flags); + struct tpm2_info_st *info = _info; + uint16_t tpm2_curve = tpm2_key_curve(info); + + switch (algo) { + case GNUTLS_SIGN_ECDSA_SHA1: + case GNUTLS_SIGN_ECDSA_SHA256: + return 1; + + case GNUTLS_SIGN_ECDSA_SECP256R1_SHA256: + return tpm2_curve == 0x0003; /* TPM2_ECC_NIST_P256 */ + + case GNUTLS_SIGN_ECDSA_SECP384R1_SHA384: + return tpm2_curve == 0x0004; /* TPM2_ECC_NIST_P384 */ + + case GNUTLS_SIGN_ECDSA_SECP521R1_SHA512: + return tpm2_curve == 0x0005; /* TPM2_ECC_NIST_P521 */ + + default: + _gnutls_debug_log("tpm2: unsupported EC sign algo %s\n", + gnutls_sign_get_name(algo)); + return 0; + } + } + + if (flags & GNUTLS_PRIVKEY_INFO_SIGN_ALGO) { + return GNUTLS_SIGN_ECDSA_SHA256; + } + + return -1; +} + +static int decode_data(ASN1_TYPE n, gnutls_datum_t *r) +{ + ASN1_DATA_NODE d; + int lenlen; + int result; + + if (!n) { + return GNUTLS_E_INVALID_REQUEST; + } + + result = asn1_read_node_value(n, &d); + if (result != ASN1_SUCCESS) { + return _gnutls_asn2err(result); + } + + result = asn1_get_length_der(d.value, d.value_len, &lenlen); + if (result < 0) { + return _gnutls_asn2err(result); + } + + r->data = (unsigned char *)d.value + lenlen; + r->size = d.value_len - lenlen; + + return 0; +} + +int _gnutls_load_tpm2_key(gnutls_privkey_t pkey, const gnutls_datum_t *fdata) +{ + gnutls_datum_t asn1, pubdata, privdata; + ASN1_TYPE tpmkey = ASN1_TYPE_EMPTY; + char value_buf[16]; + int value_buflen; + bool emptyauth = false; + unsigned int parent; + int err, ret; + struct tpm2_info_st *info = NULL; + + ret = gnutls_pem_base64_decode2("TSS2 PRIVATE KEY", fdata, &asn1); + if (ret < 0) { + /* Report the first error */ + _gnutls_debug_log("tpm2: error decoding TSS2 key blob: %s\n", + gnutls_strerror(ret)); + return ret; + } + + err = asn1_create_element(_gnutls_get_gnutls_asn(), "GNUTLS.TPMKey", + &tpmkey); + if (err != ASN1_SUCCESS) { + _gnutls_debug_log("tpm2: failed to create ASN.1 type: %s\n", + asn1_strerror(err)); + ret = _gnutls_asn2err(err); + goto out_asn1; + } + + err = asn1_der_decoding(&tpmkey, asn1.data, asn1.size, NULL); + if (err != ASN1_SUCCESS) { + _gnutls_debug_log("tpm2: failed to decode key from ASN.1: %s\n", + asn1_strerror(err)); + ret = _gnutls_asn2err(err); + goto out_tpmkey; + } + + value_buflen = sizeof(value_buf); + err = asn1_read_value(tpmkey, "type", value_buf, &value_buflen); + if (err != ASN1_SUCCESS) { + _gnutls_debug_log("tpm2: failed to parse key type OID: %s\n", + asn1_strerror(err)); + ret = _gnutls_asn2err(err); + goto out_tpmkey; + } + if (strncmp(value_buf, OID_loadable_key, value_buflen)) { + _gnutls_debug_log("tpm2: key has unknown type OID %s not %s\n", + value_buf, OID_loadable_key); + ret = GNUTLS_E_TPM_ERROR; + goto out_tpmkey; + } + + value_buflen = sizeof(value_buf); + if (!asn1_read_value(tpmkey, "emptyAuth", value_buf, &value_buflen) && + !strcmp(value_buf, "TRUE")) { + emptyauth = 1; + } + + memset(value_buf, 0, 5); + value_buflen = 5; + err = asn1_read_value(tpmkey, "parent", value_buf, &value_buflen); + if (err == ASN1_ELEMENT_NOT_FOUND) { + parent = 0x40000001; /* RH_OWNER */ + } else if (err != ASN1_SUCCESS) { + _gnutls_debug_log("tpm2: failed to parse TPM2 key parent: %s\n", + asn1_strerror(err)); + ret = GNUTLS_E_TPM_ERROR; + goto out_tpmkey; + } else { + int i = 0; + parent = 0; + + if (value_buflen == 5) { + if (value_buf[0]) { + gnutls_assert(); + _gnutls_debug_log("tpm2: failed to parse parent key\n"); + ret = GNUTLS_E_TPM_ERROR; + goto out_tpmkey; + } + /* Skip the leading zero */ + i++; + } + for ( ; i < value_buflen; i++) { + parent <<= 8; + parent |= value_buf[i]; + } + } + + ret = decode_data(asn1_find_node(tpmkey, "pubkey"), &pubdata); + if (ret < 0) { + _gnutls_debug_log("tpm2: failed to parse pubkey element: %s\n", + gnutls_strerror(ret)); + ret = GNUTLS_E_TPM_ERROR; + goto out_tpmkey; + } + ret = decode_data(asn1_find_node(tpmkey, "privkey"), &privdata); + if (ret < 0) { + _gnutls_debug_log("tpm2: failed to parse privkey element: %s\n", + gnutls_strerror(ret)); + ret = GNUTLS_E_TPM_ERROR; + goto out_tpmkey; + } + + _gnutls_debug_log("tpm2: parsed key with parent %x, emptyauth %d\n", + parent, emptyauth); + + info = tpm2_info_init(&pkey->pin); + if (info == NULL) { + _gnutls_debug_log("tpm2: failed to allocate context\n"); + ret = GNUTLS_E_MEMORY_ERROR; + goto out_tpmkey; + } + + /* Now we've extracted what we need from the ASN.1, invoke the + * actual TPM2 code (whichever implementation we end up with */ + ret = install_tpm2_key(info, pkey, parent, emptyauth, + &privdata, &pubdata); + if (ret < 0) { + goto out_tpmkey; + } + + switch (ret) { + case GNUTLS_PK_RSA: + gnutls_privkey_import_ext4(pkey, info, NULL, + tpm2_rsa_sign_hash_fn, NULL, + tpm2_deinit_fn, rsa_key_info, 0); + pkey->key.ext.pk_params_func = tpm2_convert_public; + break; + + case GNUTLS_PK_ECDSA: + gnutls_privkey_import_ext4(pkey, info, NULL, + tpm2_ec_sign_hash_fn, NULL, + tpm2_deinit_fn, ec_key_info, 0); + pkey->key.ext.pk_params_func = tpm2_convert_public; + break; + + default: + ret = GNUTLS_E_TPM_ERROR; + goto out_tpmkey; + } + + ret = 0; + info = NULL; /* part of pkey now */ + + out_tpmkey: + asn1_delete_structure(&tpmkey); + release_tpm2_ctx(info); + out_asn1: + gnutls_free(asn1.data); + return ret; +} diff --git a/lib/tpm2.h b/lib/tpm2.h new file mode 100644 index 0000000000..b55c2e1331 --- /dev/null +++ b/lib/tpm2.h @@ -0,0 +1,58 @@ +/* + * Copyright (C) 2000-2016 Free Software Foundation, Inc. + * Copyright (C) 2015-2018 Red Hat, Inc. + * + * Author: Nikos Mavrogiannopoulos + * + * 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/> + * + */ + +#ifndef GNUTLS_LIB_TPM2_H +# define GNUTLS_LIB_TPM2_H + +#include "pin.h" + +struct tpm2_info_st; + +struct tpm2_info_st *tpm2_info_init(struct pin_info_st *pin); + +void release_tpm2_ctx(struct tpm2_info_st *info); + +int _gnutls_load_tpm2_key(gnutls_privkey_t pkey, const gnutls_datum_t *fdata); + +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); + +void tpm2_deinit_fn(gnutls_privkey_t key, void *priv); + +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); + +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); + +uint16_t tpm2_key_curve(struct tpm2_info_st *info); +int tpm2_rsa_key_bits(struct tpm2_info_st *info); + +int tpm2_convert_public(gnutls_privkey_t key, + void *userdata, + gnutls_pk_params_st *params); + +#endif /* GNUTLS_LIB_TPM2_H */ diff --git a/lib/tpm2_esys.c b/lib/tpm2_esys.c new file mode 100644 index 0000000000..d219faf1e0 --- /dev/null +++ b/lib/tpm2_esys.c @@ -0,0 +1,896 @@ +/* + * 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 <tss2/tss2_mu.h> +#include <tss2/tss2_esys.h> +#include <tss2/tss2_tctildr.h> + +struct tpm2_info_st { + TSS2_TCTI_CONTEXT *tcti_ctx; + 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; +}; + +#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 = 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) { + 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) { + Esys_Free(capability_data); + return &primary_template_rsa; + } + } + + 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 = 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 = 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 = Esys_Initialize(ctx, info->tcti_ctx, NULL); + if (rc) { + gnutls_assert(); + _gnutls_debug_log("tpm2: Esys_Initialize failed: 0x%x\n", rc); + goto error; + } + + rc = 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 = 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 = 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; + } + 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 = 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 = 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 = 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) { + Esys_FlushContext(*ctx, parent_handle); + } + if (*key_handle != ESYS_TR_NONE) { + Esys_FlushContext(*ctx, *key_handle); + } + *key_handle = ESYS_TR_NONE; + + 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 = 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 = 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: + Esys_Free(tsig); + + if (key_handle != ESYS_TR_NONE) { + Esys_FlushContext(ectx, key_handle); + } + + if (ectx) { + 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 = 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: + Esys_Free(tsig); + + if (key_handle != ESYS_TR_NONE) { + Esys_FlushContext(ectx, key_handle); + } + + if (ectx) { + Esys_Finalize(&ectx); + } + + return ret; +} + +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) +{ + const char *tcti; + const char * const tcti_vars[] = { + "GNUTLS_TPM2_TCTI", + "TPM2TOOLS_TCTI", + "TCTI", + "TEST_TCTI" + }; + size_t i; + TSS2_RC rc; + + 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; + + 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 = Tss2_TctiLdr_Initialize(tcti, &info->tcti_ctx); + if (rc) { + _gnutls_debug_log("tpm2: TSS2_TctiLdr_Initialize failed: 0x%x\n", + rc); + return gnutls_assert_val(GNUTLS_E_TPM_ERROR); + } + } + + rc = 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 = 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)); + if (info->tcti_ctx) { + Tss2_TctiLdr_Finalize(&info->tcti_ctx); + } + 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; +} diff --git a/src/Makefile.am b/src/Makefile.am index 450700a3d0..dc6c661ded 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -350,5 +350,9 @@ systemkey-args.h: systemkey-args.stamp systemkey-args.c: systemkey-args.stamp systemkey-args.stamp: args-std.def +tpm2key-args.h: tpm2key-args.stamp +tpm2key-args.c: tpm2key-args.stamp +tpm2key-args.stamp: args-std.def + mech-list.h: gen-mech-list.sh $(AM_V_GEN) $(srcdir)/gen-mech-list.sh > $@.tmp && mv $@.tmp $@ diff --git a/tests/Makefile.am b/tests/Makefile.am index e830291aa5..05ad97edf3 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -490,6 +490,10 @@ endif dist_check_SCRIPTS = rfc2253-escape-test.sh rsa-md5-collision/rsa-md5-collision.sh systemkey.sh +if ENABLE_TPM2 +dist_check_SCRIPTS += tpm2.sh +endif + if !WINDOWS # diff --git a/tests/tpm2.sh b/tests/tpm2.sh new file mode 100755 index 0000000000..854986c552 --- /dev/null +++ b/tests/tpm2.sh @@ -0,0 +1,221 @@ +#!/bin/sh + +# Copyright (C) 2018-2019 IBM Corporation +# Copyright (C) 2019,2021 Red Hat, Inc. +# +# Author: Stefan Berger, Nikos Mavrogiannopoulos, Daiki Ueno +# +# This file is part of GnuTLS. +# +# GnuTLS is free software; you can redistribute it and/or modify it +# under the terms of the GNU General Public License as published by the +# Free Software Foundation; either version 3 of the License, or (at +# your option) any later version. +# +# GnuTLS 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 +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with GnuTLS; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + +set +e + +: ${srcdir=.} +: ${CERTTOOL=../src/certtool${EXEEXT}} +KEYPEMFILE=tpmkey.$$.key.pem +CTXFILE=tpmkey.$$.ctx + +if ! test -x "${CERTTOOL}"; then + exit 77 +fi + +if [ -z "$(which swtpm 2>/dev/null)" ]; then + echo "Need swtpm package to run this test." + exit 77 +fi + +if [ -z "$(which ncat 2>/dev/null)" ]; then + echo "Need ncat from nmap-ncat package to run this test." + exit 77 +fi + +if [ -z "$(which tpm2_startup 2>/dev/null)" ]; then + echo "Need tpm2_startup from tpm2-tools package to run this test." + exit 77 +fi + +if [ -z "$(which base64 2>/dev/null)" ]; then + echo "Need the base64 tool to run this test." + exit 77 +fi + +if [ -z "$(which tpm2tss-genkey 2>/dev/null)" ]; then + echo "Need tpm2tss-genkey from tpm2-tss-engine package to run this test." + exit 77 +fi + +. "${srcdir}/scripts/common.sh" + +workdir=$(mktemp -d) + +PORT=2321 +SWTPM_SERVER_PORT=$PORT +echo "Server port: $PORT" +SWTPM_CTRL_PORT=$((SWTPM_SERVER_PORT + 1)) # fake port used by ncat only +echo "Ncat port: $SWTPM_CTRL_PORT" +echo "Directory: $workdir" + +SWTPM_PIDFILE=${workdir}/swtpm.pid + +eval "${GETPORT}" + +TCSD_LISTEN_PORT=$PORT +export TSS_TCSD_PORT=$TCSD_LISTEN_PORT +echo "TCSD port: $PORT" + +export TPM2TOOLS_TCTI="mssim:host=127.0.0.1,port=${SWTPM_SERVER_PORT}" +export TPM2TSSENGINE_TCTI="$TPM2TOOLS_TCTI" +export TPM20TEST_TCTI_NAME="socket" +export TPM20TEST_SOCKET_PORT=${SWTPM_SERVER_PORT} +export TPM20TEST_SOCKET_ADDRESS="127.0.0.1" + +cleanup() +{ + echo "Cleaning up" + stop_swtpm + rm -f ${KEYPEMFILE} + if [ -n "$workdir" ]; then + rm -rf $workdir + fi +} + +start_swtpm() +{ + local workdir="$1" + + local res + + echo "" + echo " - Starting swtpm" + + swtpm socket \ + --tpm2 \ + --flags not-need-init \ + --pid file=$SWTPM_PIDFILE \ + --tpmstate dir=$workdir \ + --server type=tcp,bindaddr=127.0.0.1,port=$SWTPM_SERVER_PORT & + + if wait_for_file $SWTPM_PIDFILE 3; then + echo "Starting the swtpm failed" + return 1 + fi + + echo " - Starting ncat" + + SWTPM_PID=$(cat $SWTPM_PIDFILE) + kill -0 ${SWTPM_PID} + if [ $? -ne 0 ]; then + echo "swtpm must have terminated" + return 1 + fi + + ncat -l ${SWTPM_CTRL_PORT} \ + -k -c "xargs --null -n1 printf '\x00\x00\x00\x00'" &>/dev/null & + if [ $? -ne 0 ]; then + echo "Could not start ncat" + stop_swtpm + return 1 + fi + NCAT_PID=$! + sleep 1 + kill -0 ${NCAT_PID} + if [ $? -ne 0 ]; then + echo "ncat must have been terminated" + stop_swtpm + return 1 + fi + + echo " - Running tpm2_startup" + msg=$(tpm2_startup -V -c 2>&1) + if [ $? -ne 0 ]; then + echo "TPM2_Startup() failed" + echo "${msg}" + stop_swtpm + return 1 + fi + + echo " - Startup completed" + sleep 1 + + return 0 +} + +stop_swtpm() +{ + if [ -n "${SWTPM_PID}" ]; then + echo terminate_proc ${SWTPM_PID} + terminate_proc ${SWTPM_PID} + unset SWTPM_PID + fi + + if [ -n "${NCAT_PID}" ]; then + terminate_proc ${NCAT_PID} + unset NCAT_PID + fi +} + +run_tests() +{ + local workdir="$1" + local OPASS=12345678 + local EPASS=23456789 + local LPASS=34567890 +# local OBJPASS=012345 + local kalg=$2 + + [ -z "$workdir" ] && { + echo "No workdir" + return 1 + } + + start_swtpm $workdir + + echo " - Set owner authorization" + tpm2_changeauth -c owner ${OPASS} + echo " - Set endorsement authorization" + tpm2_changeauth -c endorsement ${EPASS} + echo " - Set lockout authorization" + tpm2_changeauth -c lockout ${LPASS} + + echo " - Generating ${KEYPEMFILE}" + tpm2tss-genkey -a ${kalg} -o ${OPASS} ${KEYPEMFILE} + cat ${KEYPEMFILE} + + echo " - Generating certificate based on key" + + export GNUTLS_PIN=${OPASS} + "${CERTTOOL}" --generate-self-signed -d 3 \ + --load-privkey "${KEYPEMFILE}" \ + --template "${srcdir}/cert-tests/templates/template-test.tmpl" + + if test "${kalg}" = "rsa";then + echo " - Generating RSA-PSS certificate based on key" + "${CERTTOOL}" --generate-self-signed -d 3 \ + --load-privkey "${KEYPEMFILE}" \ + --sign-params rsa-pss \ + --template "${srcdir}/cert-tests/templates/template-test.tmpl" + fi + + stop_swtpm + echo "Ok" + + return 0 +} + +trap "cleanup" EXIT QUIT + +run_tests "$workdir" ecdsa +run_tests "$workdir" rsa |