]> git.ipfire.org Git - thirdparty/gnutls.git/commitdiff
Port openconnect TPM2 code
authorNikos Mavrogiannopoulos <nmav@redhat.com>
Fri, 22 Mar 2019 13:52:10 +0000 (14:52 +0100)
committerDaiki Ueno <ueno@gnu.org>
Sat, 13 Nov 2021 11:59:37 +0000 (12:59 +0100)
This introduces transparent loading of TPM2 keys which are in PEM
form by gnutls_privkey_import_x509_raw() and higher level functions
which wrap it.

Signed-off-by: Nikos Mavrogiannopoulos <nmav@redhat.com>
Co-authored-by: David Woodhouse <dwmw2@infradead.org>
Co-authored-by: Daiki Ueno <ueno@gnu.org>
17 files changed:
NEWS
README.md
configure.ac
lib/Makefile.am
lib/abstract_int.h
lib/crypto-backend.h
lib/gnutls.asn
lib/gnutls_asn1_tab.c
lib/nettle/Makefile.am
lib/nettle/int/rsa-pad.c [new file with mode: 0644]
lib/privkey.c
lib/tpm2.c [new file with mode: 0644]
lib/tpm2.h [new file with mode: 0644]
lib/tpm2_esys.c [new file with mode: 0644]
src/Makefile.am
tests/Makefile.am
tests/tpm2.sh [new file with mode: 0755]

diff --git a/NEWS b/NEWS
index de57ed5dc154f4364d03c7167102e0970f3904d0..42c2be012409c19f34d9601c62bb91f7009a05ba 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -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
index 857acaefc200c4ead9edde63217971b58e3c0ecf..0b31a8591de97e07d1f7f918fd9e6b14e6a1b4f7 100644 (file)
--- a/README.md
+++ b/README.md
@@ -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
 ```
 
index c22acef7c388b22b2a929d4bd39197110798b57d..b689a5f94aae876ea3ee0cb7c29c2f3997c2f66d 100644 (file)
@@ -851,11 +851,33 @@ 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)
index 1a6a7f963cc94737b8cfa5637bcc359a5ace06d3..50cd3dbf7c1dd223907a25b9ee68d8b2011f7a54 100644 (file)
@@ -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)
index 8f436b0a9f45221dbf8cb372ffcbfbe40135b501..cc839ccf843a6827eae95d688f34ced9dedfa057 100644 (file)
 
 #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;
index 6cc1853cbefc74e83ce7a2de2e5e2aa40845178c..9874033221d1ee3f52461d5e9f2e7e3083c7c5e4 100644 (file)
@@ -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 */
index aca39fd2966806f03b9a346903344adc8bf45efd..e1076fe6f7b9cabfb24e34e6274ce72640231ca0 100644 (file)
@@ -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
index 0f566195598bb9f48244838fcf442778ba38728d..90bbf0bd42afe94a2c796bfbad736eb01c793478 100644 (file)
@@ -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 }
 };
index a3aa22a64ddd7493f4ec2583bfb23c3f35019530..bea7c7eda92efc58af7e8ea54ff15c8fbd401c47 100644 (file)
@@ -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 (file)
index 0000000..401bad3
--- /dev/null
@@ -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;
+}
index 7b983b145d1c47992541863150e716663cda08f2..0b774430c1dd2272b2209358a0797d6913cb59e6 100644 (file)
@@ -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 (file)
index 0000000..5c293a5
--- /dev/null
@@ -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 (file)
index 0000000..b55c2e1
--- /dev/null
@@ -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 (file)
index 0000000..d219faf
--- /dev/null
@@ -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, &params);
+               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,
+                                                        &params);
+               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(&params, 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(&params->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(&params->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(&params->params[ECC_X],
+                                      point->x.buffer, point->x.size);
+       if (ret < 0) {
+               return gnutls_assert_val(ret);
+       }
+       ret = _gnutls_mpi_init_scan_nz(&params->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;
+}
index 450700a3d0d0c7d91f8db11c8630eadb733f2aa4..dc6c661ded7d05cc449027d89d2db1192d490846 100644 (file)
@@ -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 $@
index e830291aa581aa4c8178d3f1a60297c553a90022..05ad97edf32785af3a630ecb3601d508046686ad 100644 (file)
@@ -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 (executable)
index 0000000..854986c
--- /dev/null
@@ -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