diff options
-rw-r--r-- | Makefile.am | 7 | ||||
-rw-r--r-- | am/cmacros.am | 3 | ||||
-rw-r--r-- | common/homedir.c | 7 | ||||
-rw-r--r-- | common/mapstrings.c | 1 | ||||
-rw-r--r-- | common/util.h | 1 | ||||
-rw-r--r-- | configure.ac | 46 | ||||
-rw-r--r-- | tools/gpgconf-comp.c | 62 | ||||
-rw-r--r-- | tools/gpgconf.h | 3 | ||||
-rw-r--r-- | tpm2d/Makefile.am | 18 | ||||
-rw-r--r-- | tpm2d/command.c | 504 | ||||
-rw-r--r-- | tpm2d/ibm-tss.h | 381 | ||||
-rw-r--r-- | tpm2d/tpm2.c | 987 | ||||
-rw-r--r-- | tpm2d/tpm2.h | 34 | ||||
-rw-r--r-- | tpm2d/tpm2daemon.c | 1289 | ||||
-rw-r--r-- | tpm2d/tpm2daemon.h | 98 |
15 files changed, 3439 insertions, 2 deletions
diff --git a/Makefile.am b/Makefile.am index 4e80102a2..0e1aacad3 100644 --- a/Makefile.am +++ b/Makefile.am @@ -106,10 +106,15 @@ tests = else tests = tests endif +if HAVE_LIBTSS +tpm2d = tpm2d +else +tpm2d = +endif SUBDIRS = m4 common regexp kbx \ ${gpg} ${sm} ${agent} ${scd} ${g13} ${dirmngr} \ - tools po ${doc} ${tests} + tools po ${doc} ${tests} ${tpm2d} dist_doc_DATA = README diff --git a/am/cmacros.am b/am/cmacros.am index 9610e4efe..e71bc4e9d 100644 --- a/am/cmacros.am +++ b/am/cmacros.am @@ -44,6 +44,9 @@ endif if GNUPG_SCDAEMON_PGM AM_CPPFLAGS += -DGNUPG_DEFAULT_SCDAEMON="\"@GNUPG_SCDAEMON_PGM@\"" endif +if GNUPG_TPM2DAEMON_PGM +AM_CPPFLAGS += -DGNUPG_DEFAULT_TPM2DAEMON="\"@GNUPG_TPM2DAEMON_PGM@\"" +endif if GNUPG_DIRMNGR_PGM AM_CPPFLAGS += -DGNUPG_DEFAULT_DIRMNGR="\"@GNUPG_DIRMNGR_PGM@\"" endif diff --git a/common/homedir.c b/common/homedir.c index 830b1e2ce..8b54f9dda 100644 --- a/common/homedir.c +++ b/common/homedir.c @@ -1153,6 +1153,13 @@ gnupg_module_name (int which) X(libexecdir, "scd", "scdaemon"); #endif + case GNUPG_MODULE_NAME_TPM2DAEMON: +#ifdef GNUPG_DEFAULT_TPM2DAEMON + return GNUPG_DEFAULT_TPM2DAEMON; +#else + X(libexecdir, "tpm2d", TPM2DAEMON_NAME); +#endif + case GNUPG_MODULE_NAME_DIRMNGR: #ifdef GNUPG_DEFAULT_DIRMNGR return GNUPG_DEFAULT_DIRMNGR; diff --git a/common/mapstrings.c b/common/mapstrings.c index 614fddd12..318ca5bd1 100644 --- a/common/mapstrings.c +++ b/common/mapstrings.c @@ -50,6 +50,7 @@ static struct { { "GPGSM", GPGSM_NAME }, { "GPG_AGENT", GPG_AGENT_NAME }, { "SCDAEMON", SCDAEMON_NAME }, + { "TPM2DAEMON",TPM2DAEMON_NAME}, { "DIRMNGR", DIRMNGR_NAME }, { "G13", G13_NAME }, { "GPGCONF", GPGCONF_NAME }, diff --git a/common/util.h b/common/util.h index 18217af7b..56187f1e0 100644 --- a/common/util.h +++ b/common/util.h @@ -293,6 +293,7 @@ char *_gnupg_socketdir_internal (int skip_checks, unsigned *r_info); #define GNUPG_MODULE_NAME_DIRMNGR_LDAP 11 #define GNUPG_MODULE_NAME_GPGV 12 #define GNUPG_MODULE_NAME_KEYBOXD 13 +#define GNUPG_MODULE_NAME_TPM2DAEMON 14 const char *gnupg_module_name (int which); void gnupg_module_name_flush_some (void); void gnupg_set_builddir (const char *newdir); diff --git a/configure.ac b/configure.ac index 9be4d472e..c67aaf87d 100644 --- a/configure.ac +++ b/configure.ac @@ -101,6 +101,7 @@ have_gnutls=no have_sqlite=no have_npth=no have_libusb=no +have_libtss=no have_system_resolver=no gnupg_have_ldap="n/a" @@ -184,6 +185,15 @@ show_gnupg_scdaemon_pgm="(default)" test -n "$GNUPG_SCDAEMON_PGM" && show_gnupg_scdaemon_pgm="$GNUPG_SCDAEMON_PGM" +AC_ARG_WITH(tpm2daemon-pgm, + [ --with-tpm2daemon-pgm=PATH Use PATH as the default for the tpm2daemon)], + GNUPG_TPM2DAEMON_PGM="$withval", GNUPG_TPM2DAEMON_PGM="" ) +AC_SUBST(GNUPG_TPM2DAEMON_PGM) +AM_CONDITIONAL(GNUPG_TPM2DAEMON_PGM, test -n "$GNUPG_TPM2DAEMON_PGM") +show_gnupg_tpm2daemon_pgm="(default)" +test -n "$GNUPG_TPM2DAEMON_PGM" && show_gnupg_tpm2daemon_pgm="$GNUPG_TPM2DAEMON_PGM" + + AC_ARG_WITH(dirmngr-pgm, [ --with-dirmngr-pgm=PATH Use PATH as the default for the dirmngr)], GNUPG_DIRMNGR_PGM="$withval", GNUPG_DIRMNGR_PGM="" ) @@ -1581,6 +1591,33 @@ AC_SUBST(NETLIBS) AC_SUBST(W32SOCKLIBS) # +# TPM libtss library .. don't compile TPM support if we don't have it +# +_save_libs="$LIBS" +_save_cflags="$CFLAGS" +LIBS="" +AC_SEARCH_LIBS([TSS_Create], [tss ibmtss],have_libtss=yes,) +if test "$have_libtss" = yes; then + LIBTSS_CFLAGS="-DTPM_POSIX" + CFLAGS="$CFLAGS ${LIBTSS_CFLAGS}" + AC_CHECK_HEADER([tss2/tss.h],[AC_DEFINE(TSS_INCLUDE,tss2, [tss2 include location])], [ + AC_CHECK_HEADER([ibmtss/tss.h],[AC_DEFINE(TSS_INCLUDE,ibmtss, [ibmtss include location])], [ + AC_MSG_WARN([No TSS2 include directory found, disabling TPM support]) + have_libtss=no + ]) + ]) + LIBTSS_LIBS=$LIBS + AC_DEFINE(HAVE_LIBTSS, 1, [Defined if we have TPM2 support library]) + AC_SUBST(TSS_INCLUDE) +fi +LIBS="$_save_libs" +CFLAGS="$_save_cflags" +AC_SUBST(LIBTSS_LIBS) +AC_SUBST(LIBTSS_CFLAGS) +AM_CONDITIONAL(HAVE_LIBTSS, test "$have_libtss" = yes) +AC_SUBST(HAVE_LIBTSS) + +# # Setup gcc specific options # USE_C99_CFLAGS= @@ -1845,6 +1882,10 @@ AC_DEFINE_UNQUOTED(GPG_AGENT_NAME, "gpg-agent", [The name of the agent]) AC_DEFINE_UNQUOTED(GPG_AGENT_DISP_NAME, "GPG Agent", [The displayed name of gpg-agent]) +AC_DEFINE_UNQUOTED(TPM2DAEMON_NAME, "tpm2daemon", [The name of the TPM2 daemon]) +AC_DEFINE_UNQUOTED(TPM2DAEMON_DISP_NAME, "TPM2 Daemon", + [The displayed name of TPM2 daemon]) + AC_DEFINE_UNQUOTED(SCDAEMON_NAME, "scdaemon", [The name of the scdaemon]) AC_DEFINE_UNQUOTED(SCDAEMON_DISP_NAME, "SCDaemon", [The displayed name of scdaemon]) @@ -1880,6 +1921,8 @@ AC_DEFINE_UNQUOTED(SCDAEMON_SOCK_NAME, "S.scdaemon", [The name of the SCdaemon socket]) AC_DEFINE_UNQUOTED(KEYBOXD_SOCK_NAME, "S.keyboxd", [The name of the keyboxd socket]) +AC_DEFINE_UNQUOTED(TPM2DAEMON_SOCK_NAME, "S.tpm2daemon", + [The name of the TPM2 daemon socket]) AC_DEFINE_UNQUOTED(DIRMNGR_SOCK_NAME, "S.dirmngr", [The name of the dirmngr socket]) AC_DEFINE_UNQUOTED(DIRMNGR_DEFAULT_KEYSERVER, @@ -2040,6 +2083,7 @@ g10/Makefile sm/Makefile agent/Makefile scd/Makefile +tpm2d/Makefile g13/Makefile dirmngr/Makefile tools/Makefile @@ -2086,6 +2130,7 @@ echo " Default pinentry: $show_gnupg_pinentry_pgm Default scdaemon: $show_gnupg_scdaemon_pgm Default keyboxd: $show_gnupg_keyboxd_pgm + Default tpm2daemon: $show_gnupg_tpm2daemon_pgm Default dirmngr: $show_gnupg_dirmngr_pgm Dirmngr auto start: $dirmngr_auto_start @@ -2094,6 +2139,7 @@ echo " TLS support: $use_tls_library TOFU support: $use_tofu Tor support: $show_tor_support + TPM support: $have_libtss " if test "x${gpg_config_script_warn}" != x; then cat <<G10EOF diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index 26f213666..8ac7b4e29 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -92,11 +92,11 @@ gc_error (int status, int errnum, const char *fmt, ...) /* Forward declaration. */ static void gpg_agent_runtime_change (int killflag); static void scdaemon_runtime_change (int killflag); +static void tpm2daemon_runtime_change (int killflag); static void dirmngr_runtime_change (int killflag); static void keyboxd_runtime_change (int killflag); - /* STRING_ARRAY is a malloced array with malloced strings. It is used * a space to store strings so that other objects may point to these @@ -378,6 +378,21 @@ static known_option_t known_options_scdaemon[] = { NULL } }; +/* The known options of the GC_COMPONENT_TPM2DAEMON component. */ +static known_option_t known_options_tpm2daemon[] = + { + { "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, + { "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC }, + { "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE }, + { "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED}, + { "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED, + GC_ARG_TYPE_FILENAME }, + { "deny-admin", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC }, + { "parent", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED }, + + { NULL } + }; + /* The known options of the GC_COMPONENT_GPG component. */ static known_option_t known_options_gpg[] = @@ -627,6 +642,10 @@ static struct GNUPG_MODULE_NAME_SCDAEMON, SCDAEMON_NAME ".conf", known_options_scdaemon, scdaemon_runtime_change}, + { TPM2DAEMON_NAME, TPM2DAEMON_DISP_NAME, "gnupg", N_("TPM"), + GNUPG_MODULE_NAME_TPM2DAEMON, TPM2DAEMON_NAME ".conf", + known_options_tpm2daemon, tpm2daemon_runtime_change}, + { DIRMNGR_NAME, DIRMNGR_DISP_NAME, "gnupg", N_("Network"), GNUPG_MODULE_NAME_DIRMNGR, DIRMNGR_NAME ".conf", known_options_dirmngr, dirmngr_runtime_change }, @@ -754,6 +773,47 @@ scdaemon_runtime_change (int killflag) static void +tpm2daemon_runtime_change (int killflag) +{ + gpg_error_t err = 0; + const char *pgmname; + const char *argv[9]; + pid_t pid = (pid_t)(-1); + int i = 0; + + (void)killflag; /* For scdaemon kill and reload are synonyms. */ + + /* We use "GETINFO app_running" to see whether the agent is already + running and kill it only in this case. This avoids an explicit + starting of the agent in case it is not yet running. There is + obviously a race condition but that should not harm too much. */ + + pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT); + if (!gnupg_default_homedir_p ()) + { + argv[i++] = "--homedir"; + argv[i++] = gnupg_homedir (); + } + argv[i++] = "-s"; + argv[i++] = "--no-autostart"; + argv[i++] = "GETINFO tpm2d_running"; + argv[i++] = "/if ${! $?}"; + argv[i++] = "scd killtpm2cd"; + argv[i++] = "/end"; + argv[i++] = NULL; + + if (!err) + err = gnupg_spawn_process_fd (pgmname, argv, -1, -1, -1, &pid); + if (!err) + err = gnupg_wait_process (pgmname, pid, 1, NULL); + if (err) + gc_error (0, 0, "error running '%s %s': %s", + pgmname, argv[4], gpg_strerror (err)); + gnupg_release_process (pid); +} + + +static void dirmngr_runtime_change (int killflag) { gpg_error_t err = 0; diff --git a/tools/gpgconf.h b/tools/gpgconf.h index 79f33da35..e398442f8 100644 --- a/tools/gpgconf.h +++ b/tools/gpgconf.h @@ -67,6 +67,9 @@ typedef enum /* The Smardcard Daemon. */ GC_COMPONENT_SCDAEMON, + /* The TPM2 Daemon. */ + GC_COMPONENT_TPM2DAEMON, + /* The LDAP Directory Manager for CRLs. */ GC_COMPONENT_DIRMNGR, diff --git a/tpm2d/Makefile.am b/tpm2d/Makefile.am new file mode 100644 index 000000000..56a9ab7c9 --- /dev/null +++ b/tpm2d/Makefile.am @@ -0,0 +1,18 @@ +AM_CPPFLAGS = + +include $(top_srcdir)/am/cmacros.am + +libexec_PROGRAMS = tpm2daemon + +AM_CFLAGS = $(LIBGCRYPT_CFLAGS) \ + $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) $(LIBTSS_CFLAGS) + +tpm2daemon_SOURCES = \ + command.c \ + tpm2daemon.c \ + tpm2.c tpm2.h \ + tpm2daemon.h ibm-tss.h + +tpm2daemon_LDADD = $(libcommonpth) \ + $(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \ + $(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV) $(LIBTSS_LIBS) diff --git a/tpm2d/command.c b/tpm2d/command.c new file mode 100644 index 000000000..619bb56e6 --- /dev/null +++ b/tpm2d/command.c @@ -0,0 +1,504 @@ +/* command.c - TPM2daemon command handler + * Copyright (C) 2001, 2002, 2003, 2004, 2005, + * 2007, 2008, 2009, 2011 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG 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. + * + * GnuPG 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 this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <unistd.h> +#include <signal.h> +#ifdef USE_NPTH +# include <npth.h> +#endif + +#include "tpm2daemon.h" +#include "tpm2.h" +#include <assuan.h> +#include <ksba.h> +#include "../common/asshelp.h" +#include "../common/server-help.h" + +/* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN */ +#define MAXLEN_PIN 100 + +/* Maximum allowed size of key data as used in inquiries. */ +#define MAXLEN_KEYDATA 4096 + +/* Maximum allowed total data size for SETDATA. */ +#define MAXLEN_SETDATA 4096 + +/* Maximum allowed size of certificate data as used in inquiries. */ +#define MAXLEN_CERTDATA 16384 + + +#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t)) + +/* Data used to associate an Assuan context with local server data. + This object describes the local properties of one session. */ +struct server_local_s +{ + /* We keep a list of all active sessions with the anchor at + SESSION_LIST (see below). This field is used for linking. */ + struct server_local_s *next_session; + + /* This object is usually assigned to a CTRL object (which is + globally visible). While enumerating all sessions we sometimes + need to access data of the CTRL object; thus we keep a + backpointer here. */ + ctrl_t ctrl_backlink; + + /* The Assuan context used by this session/server. */ + assuan_context_t assuan_ctx; + +#ifdef HAVE_W32_SYSTEM + unsigned long event_signal; /* Or 0 if not used. */ +#else + int event_signal; /* Or 0 if not used. */ +#endif + + /* True if the card has been removed and a reset is required to + continue operation. */ + int card_removed; + + /* If set to true we will be terminate ourself at the end of the + this session. */ + int stopme; + +}; + + +/* To keep track of all running sessions, we link all active server + contexts and the anchor in this variable. */ +static struct server_local_s *session_list; + + +static gpg_error_t +reset_notify (assuan_context_t ctx, char *line) +{ + (void) ctx; + (void) line; + + return 0; +} + + +static gpg_error_t +option_handler (assuan_context_t ctx, const char *key, const char *value) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + if (!strcmp (key, "event-signal")) + { + /* A value of 0 is allowed to reset the event signal. */ +#ifdef HAVE_W32_SYSTEM + if (!*value) + return gpg_error (GPG_ERR_ASS_PARAMETER); + ctrl->server_local->event_signal = strtoul (value, NULL, 16); +#else + int i = *value? atoi (value) : -1; + if (i < 0) + return gpg_error (GPG_ERR_ASS_PARAMETER); + ctrl->server_local->event_signal = i; +#endif + } + + return 0; +} + + +static gpg_error_t +pin_cb (ctrl_t ctrl, const char *info, char **retstr) +{ + assuan_context_t ctx = ctrl->ctx; + char *command; + int rc; + unsigned char *value; + size_t valuelen; + + *retstr = NULL; + log_debug ("asking for PIN '%s'\n", info); + + rc = gpgrt_asprintf (&command, "NEEDPIN %s", info); + if (rc < 0) + return gpg_error (gpg_err_code_from_errno (errno)); + + /* Fixme: Write an inquire function which returns the result in + secure memory and check all further handling of the PIN. */ + rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN); + xfree (command); + if (rc) + return rc; + + if (!valuelen) + { + /* We require that the returned value is an UTF-8 string */ + xfree (value); + return gpg_error (GPG_ERR_INV_RESPONSE); + } + *retstr = (char*)value; + return 0; +} + +static const char hlp_import[] = + "IMPORT\n" + "\n" + "This command is used to convert a public and secret key to tpm format.\n" + "keydata is communicated via an inquire KEYDATA command\n" + "The keydata is expected to be the usual canonical encoded\n" + "S-expression. The return will be a TPM format S-expression\n" + "\n" + "A PIN will be requested."; +static gpg_error_t +cmd_import (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *keydata; + size_t keydatalen; + TSS_CONTEXT *tssc; + gcry_sexp_t s_key; + unsigned char *shadow_info = NULL; + size_t shadow_len; + + line = skip_options (line); + + if (*line) + return set_error (GPG_ERR_ASS_PARAMETER, "additional parameters given"); + + /* Now get the actual keydata. */ + assuan_begin_confidential (ctx); + rc = assuan_inquire (ctx, "KEYDATA", &keydata, &keydatalen, MAXLEN_KEYDATA); + assuan_end_confidential (ctx); + if (rc) + return rc; + + if ((rc = tpm2_start (&tssc))) + goto out; + gcry_sexp_new (&s_key, keydata, keydatalen, 0); + rc = tpm2_import_key (ctrl, tssc, pin_cb, &shadow_info, &shadow_len, + s_key, opt.parent); + gcry_sexp_release (s_key); + tpm2_end (tssc); + if (rc) + goto out; + + rc = assuan_send_data (ctx, shadow_info, shadow_len); + + out: + xfree (shadow_info); + xfree (keydata); + + return rc; +} + +static const char hlp_pksign[] = + "PKSIGN\n" + "\n" + "Get the TPM to produce a signature. KEYDATA will request the TPM\n" + "form S-expression (returned by IMPORT) and EXTRA will be the hash\n" + "to sign. The TPM currently deduces hash type from length.\n" + "\n" + "A PIN will be requested."; +static gpg_error_t +cmd_pksign (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *shadow_info; + size_t len; + TSS_CONTEXT *tssc; + TPM_HANDLE key; + TPMI_ALG_PUBLIC type; + unsigned char *digest; + size_t digestlen; + unsigned char *sig; + size_t siglen; + + line = skip_options (line); + + if (*line) + return set_error (GPG_ERR_ASS_PARAMETER, "additional parameters given"); + + /* Now get the actual keydata. */ + rc = assuan_inquire (ctx, "KEYDATA", &shadow_info, &len, MAXLEN_KEYDATA); + if (rc) + return rc; + + rc = assuan_inquire (ctx, "EXTRA", &digest, &digestlen, MAXLEN_KEYDATA); + if (rc) + goto out_freeshadow; + + rc = tpm2_start (&tssc); + if (rc) + goto out; + + rc = tpm2_load_key (tssc, shadow_info, &key, &type); + if (rc) + goto end_out; + + rc = tpm2_sign (ctrl, tssc, key, pin_cb, type, digest, digestlen, + &sig, &siglen); + + tpm2_flush_handle (tssc, key); + + end_out: + tpm2_end (tssc); + + if (rc) + goto out; + + rc = assuan_send_data (ctx, sig, siglen); + xfree (sig); + + out: + xfree (digest); + out_freeshadow: + xfree (shadow_info); + + return rc; +} + +static const char hlp_pkdecrypt[] = + "PKDECRYPT\n" + "Get the TPM to recover a symmetric key. KEYDATA will request the TPM\n" + "form S-expression (returned by IMPORT) and EXTRA will be the input\n" + "to derive or decrypt. The return will be the symmetric key\n" + "\n" + "\n" + "A PIN will be requested."; +static gpg_error_t +cmd_pkdecrypt (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + int rc; + unsigned char *shadow_info; + size_t len; + TSS_CONTEXT *tssc; + TPM_HANDLE key; + TPMI_ALG_PUBLIC type; + unsigned char *crypto; + size_t cryptolen; + char *buf; + size_t buflen; + + line = skip_options (line); + + if (*line) + return set_error (GPG_ERR_ASS_PARAMETER, "additional parameters given"); + + /* Now get the actual keydata. */ + rc = assuan_inquire (ctx, "KEYDATA", &shadow_info, &len, MAXLEN_KEYDATA); + if (rc) + return rc; + + rc = assuan_inquire (ctx, "EXTRA", &crypto, &cryptolen, MAXLEN_KEYDATA); + if (rc) + goto out_freeshadow; + + rc = tpm2_start (&tssc); + if (rc) + goto out; + + rc = tpm2_load_key (tssc, shadow_info, &key, &type); + if (rc) + goto end_out; + + if (type == TPM_ALG_RSA) + rc = tpm2_rsa_decrypt (ctrl, tssc, key, pin_cb, crypto, + cryptolen, &buf, &buflen); + else if (type == TPM_ALG_ECC) + rc = tpm2_ecc_decrypt (ctrl, tssc, key, pin_cb, crypto, + cryptolen, &buf, &buflen); + + tpm2_flush_handle (tssc, key); + + end_out: + tpm2_end (tssc); + + if (rc) + goto out; + + rc = assuan_send_data (ctx, buf, buflen); + xfree (buf); + + out: + xfree (crypto); + out_freeshadow: + xfree (shadow_info); + + return rc; +} + +static const char hlp_killtpm2d[] = + "KILLTPM2D\n" + "\n" + "Commit suicide."; +static gpg_error_t +cmd_killtpm2d (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + + (void)line; + + ctrl->server_local->stopme = 1; + assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1); + return 0; +} + + + +/* Tell the assuan library about our commands */ +static int +register_commands (assuan_context_t ctx) +{ + static struct { + const char *name; + assuan_handler_t handler; + const char * const help; + } table[] = { + { "IMPORT", cmd_import, hlp_import }, + { "PKSIGN", cmd_pksign, hlp_pksign }, + { "PKDECRYPT", cmd_pkdecrypt, hlp_pkdecrypt }, + { "KILLTPM2D", cmd_killtpm2d, hlp_killtpm2d }, + { NULL } + }; + int i, rc; + + for (i=0; table[i].name; i++) + { + rc = assuan_register_command (ctx, table[i].name, table[i].handler, + table[i].help); + if (rc) + return rc; + } + assuan_set_hello_line (ctx, "GNU Privacy Guard's TPM2 server ready"); + + assuan_register_reset_notify (ctx, reset_notify); + assuan_register_option_handler (ctx, option_handler); + return 0; +} + + +/* Startup the server. If FD is given as -1 this is simple pipe + server, otherwise it is a regular server. Returns true if there + are no more active asessions. */ +int +tpm2d_command_handler (ctrl_t ctrl, int fd) +{ + int rc; + assuan_context_t ctx = NULL; + int stopme; + + rc = assuan_new (&ctx); + if (rc) + { + log_error ("failed to allocate assuan context: %s\n", + gpg_strerror (rc)); + tpm2d_exit (2); + } + + if (fd == -1) + { + assuan_fd_t filedes[2]; + + filedes[0] = assuan_fdopen (0); + filedes[1] = assuan_fdopen (1); + rc = assuan_init_pipe_server (ctx, filedes); + } + else + { + rc = assuan_init_socket_server (ctx, INT2FD (fd), + ASSUAN_SOCKET_SERVER_ACCEPTED); + } + if (rc) + { + log_error ("failed to initialize the server: %s\n", + gpg_strerror (rc)); + tpm2d_exit (2); + } + rc = register_commands (ctx); + if (rc) + { + log_error ("failed to register commands with Assuan: %s\n", + gpg_strerror (rc)); + tpm2d_exit (2); + } + assuan_set_pointer (ctx, ctrl); + ctrl->ctx = ctx; + + /* Allocate and initialize the server object. Put it into the list + of active sessions. */ + ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local); + ctrl->server_local->next_session = session_list; + session_list = ctrl->server_local; + ctrl->server_local->ctrl_backlink = ctrl; + ctrl->server_local->assuan_ctx = ctx; + + /* Command processing loop. */ + for (;;) + { + rc = assuan_accept (ctx); + if (rc == -1) + { + break; + } + else if (rc) + { + log_info ("Assuan accept problem: %s\n", gpg_strerror (rc)); + break; + } + + rc = assuan_process (ctx); + if (rc) + { + log_info ("Assuan processing failed: %s\n", gpg_strerror (rc)); + continue; + } + } + + /* Release the server object. */ + if (session_list == ctrl->server_local) + session_list = ctrl->server_local->next_session; + else + { + struct server_local_s *sl; + + for (sl=session_list; sl->next_session; sl = sl->next_session) + if (sl->next_session == ctrl->server_local) + break; + if (!sl->next_session) + BUG (); + sl->next_session = ctrl->server_local->next_session; + } + stopme = ctrl->server_local->stopme; + xfree (ctrl->server_local); + ctrl->server_local = NULL; + + /* Release the Assuan context. */ + assuan_release (ctx); + + if (stopme) + tpm2d_exit (0); + + /* If there are no more sessions return true. */ + return !session_list; +} diff --git a/tpm2d/ibm-tss.h b/tpm2d/ibm-tss.h new file mode 100644 index 000000000..2a4961359 --- /dev/null +++ b/tpm2d/ibm-tss.h @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2021 James Bottomley <[email protected]> + * + * Supporting TPM routines for the IBM TSS + */ +#ifndef _TPM2_IBM_TSS_H +#define _TPM2_IBM_TSS_H + +#define TSSINCLUDE(x) < TSS_INCLUDE/x > +#include TSSINCLUDE(tss.h) +#include TSSINCLUDE(tssutils.h) +#include TSSINCLUDE(tssresponsecode.h) +#include TSSINCLUDE(tssmarshal.h) +#include TSSINCLUDE(Unmarshal_fp.h) +#include TSSINCLUDE(tsscryptoh.h) + +#define EXT_TPM_RH_OWNER TPM_RH_OWNER + +#define VAL(X) X.val +#define VAL_2B(X, MEMBER) X.t.MEMBER + +static const char *tpm2_dir; + +/* The TPM builds a small database of active files representing key + * parameters used for authentication and session encryption. Make sure + * they're contained in a separate directory to avoid stepping on any + * other application uses of the TPM */ +static inline const char * +tpm2_set_unique_tssdir (void) +{ + char *prefix = getenv ("XDG_RUNTIME_DIR"), *template, + *dir; + int len = 0; + + if (!prefix) + prefix = "/tmp"; + + len = snprintf (NULL, 0, "%s/tss2.XXXXXX", prefix); + if (len <= 0) + return NULL; + template = xtrymalloc (len + 1); + if (!template) + return NULL; + + len++; + len = snprintf (template, len, "%s/tss2.XXXXXX", prefix); + + dir = mkdtemp (template); + + return dir; +} + +static inline void +tpm2_error (TPM_RC rc, const char *prefix) +{ + const char *msg, *submsg, *num; + + TSS_ResponseCode_toString (&msg, &submsg, &num, rc); + log_error ("%s gave TPM2 Error: %s%s%s", prefix, msg, submsg, num); +} + +static inline int +TSS_start (TSS_CONTEXT **tssc) +{ + TPM_RC rc; + + tpm2_dir = tpm2_set_unique_tssdir (); + if (!tpm2_dir) + /* make this non fatal */ + log_error ("Failed to set unique TPM directory\n"); + + rc = TSS_Create (tssc); + if (rc) + { + tpm2_error (rc, "TSS_Create"); + return GPG_ERR_CARD; + } + rc = TSS_SetProperty (*tssc, TPM_DATA_DIR, tpm2_dir); + if (rc) + /* make this non fatal */ + tpm2_error (rc, "TSS_SetProperty"); + + return 0; +} + +static inline TPM_RC +tpm2_CreatePrimary (TSS_CONTEXT *tssContext, TPM_HANDLE primaryHandle, + TPM2B_SENSITIVE_CREATE *inSensitive, + TPM2B_PUBLIC *inPublic, TPM_HANDLE *objectHandle) +{ + CreatePrimary_In in; + CreatePrimary_Out out; + TPM_RC rc; + + in.primaryHandle = primaryHandle; + in.inSensitive = *inSensitive; + in.inPublic = *inPublic; + /* no outside info */ + in.outsideInfo.t.size = 0; + /* no PCR state */ + in.creationPCR.count = 0; + + rc = TSS_Execute (tssContext, + (RESPONSE_PARAMETERS *)&out, + (COMMAND_PARAMETERS *)&in, + NULL, + TPM_CC_CreatePrimary, + TPM_RS_PW, NULL, 0, + TPM_RH_NULL, NULL, 0); + + *objectHandle = out.objectHandle; + + return rc; +} + +static inline TPM_RC +tpm2_FlushContext (TSS_CONTEXT *tssContext, TPM_HANDLE flushHandle) +{ + FlushContext_In in; + TPM_RC rc; + + in.flushHandle = flushHandle; + + rc = TSS_Execute (tssContext, + NULL, + (COMMAND_PARAMETERS *)&in, + NULL, + TPM_CC_FlushContext, + TPM_RH_NULL, NULL, 0); + + return rc; +} + +static inline TPM_RC +tpm2_ReadPublic (TSS_CONTEXT *tssContext, TPM_HANDLE objectHandle, + TPMT_PUBLIC *pub, TPM_HANDLE auth) +{ + ReadPublic_In rin; + ReadPublic_Out rout; + TPM_RC rc; + UINT32 flags = 0; + + if (auth != TPM_RH_NULL) + flags = TPMA_SESSION_ENCRYPT; + + rin.objectHandle = objectHandle; + + rc = TSS_Execute (tssContext, + (RESPONSE_PARAMETERS *)&rout, + (COMMAND_PARAMETERS *)&rin, + NULL, + TPM_CC_ReadPublic, + auth, NULL, flags, + TPM_RH_NULL, NULL, 0); + + if (rc) + { + tpm2_error (rc, "TPM2_ReadPublic"); + return rc; + } + + if (pub) + *pub = rout.outPublic.publicArea; + + return rc; +} + +static inline TPM_RC +tpm2_StartAuthSession (TSS_CONTEXT *tssContext, TPM_HANDLE tpmKey, + TPM_HANDLE bind, TPM_SE sessionType, + TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, + TPM_HANDLE *sessionHandle, + const char *bindPassword) +{ + StartAuthSession_In in; + StartAuthSession_Out out; + StartAuthSession_Extra extra; + TPM_RC rc; + + memset (&in, 0, sizeof(in)); + memset (&extra, 0 , sizeof(extra)); + + extra.bindPassword = bindPassword; + + in.tpmKey = tpmKey; + in.bind = bind; + in.sessionType = sessionType; + in.symmetric = *symmetric; + in.authHash = authHash; + + if (tpmKey != TPM_RH_NULL) + { + /* + * For the TSS to use a key as salt, it must have + * access to the public part. It does this by keeping + * key files, but request the public part just to make + * sure + */ + tpm2_ReadPublic (tssContext, tpmKey, NULL, TPM_RH_NULL); + /* + * don't care what rout returns, the purpose of the + * operation was to get the public key parameters into + * the tss so it can construct the salt + */ + } + + rc = TSS_Execute (tssContext, + (RESPONSE_PARAMETERS *)&out, + (COMMAND_PARAMETERS *)&in, + (EXTRA_PARAMETERS *)&extra, + TPM_CC_StartAuthSession, + TPM_RH_NULL, NULL, 0); + + *sessionHandle = out.sessionHandle; + + return rc; +} + +static inline TPM_RC +tpm2_Sign (TSS_CONTEXT *tssContext, TPM_HANDLE keyHandle, DIGEST_2B *digest, + TPMT_SIG_SCHEME *inScheme, TPMT_SIGNATURE *signature, + TPM_HANDLE auth, const char *authVal) +{ + Sign_In in; + Sign_Out out; + TPM_RC rc; + + in.keyHandle = keyHandle; + in.digest.t = *digest; + in.inScheme = *inScheme; + in.validation.tag = TPM_ST_HASHCHECK; + in.validation.hierarchy = TPM_RH_NULL; + in.validation.digest.t.size = 0; + + rc = TSS_Execute (tssContext, + (RESPONSE_PARAMETERS *)&out, + (COMMAND_PARAMETERS *)&in, + NULL, + TPM_CC_Sign, + auth, authVal, 0, + TPM_RH_NULL, NULL, 0); + + *signature = out.signature; + + return rc; +} + +static inline TPM_RC +tpm2_ECDH_ZGen (TSS_CONTEXT *tssContext, TPM_HANDLE keyHandle, + TPM2B_ECC_POINT *inPoint, TPM2B_ECC_POINT *outPoint, + TPM_HANDLE auth, const char *authVal) +{ + ECDH_ZGen_In in; + ECDH_ZGen_Out out; + TPM_RC rc; + + in.keyHandle = keyHandle; + in.inPoint = *inPoint; + + rc = TSS_Execute (tssContext, + (RESPONSE_PARAMETERS *)&out, + (COMMAND_PARAMETERS *)&in, + NULL, + TPM_CC_ECDH_ZGen, + auth, authVal, TPMA_SESSION_ENCRYPT, + TPM_RH_NULL, NULL, 0); + + *outPoint = out.outPoint; + + return rc; +} + +static inline TPM_RC +tpm2_RSA_Decrypt (TSS_CONTEXT *tssContext, TPM_HANDLE keyHandle, + PUBLIC_KEY_RSA_2B *cipherText, TPMT_RSA_DECRYPT *inScheme, + PUBLIC_KEY_RSA_2B *message, + TPM_HANDLE auth, const char *authVal, int flags) +{ + RSA_Decrypt_In in; + RSA_Decrypt_Out out; + TPM_RC rc; + + in.keyHandle = keyHandle; + in.inScheme = *inScheme; + in.cipherText.t = *cipherText; + in.label.t.size = 0; + + rc = TSS_Execute (tssContext, + (RESPONSE_PARAMETERS *)&out, + (COMMAND_PARAMETERS *)&in, + NULL, + TPM_CC_RSA_Decrypt, + auth, authVal, flags, + TPM_RH_NULL, NULL, 0); + + *message = out.message.t; + + return rc; +} + +static inline TPM_RC +tpm2_Load (TSS_CONTEXT *tssContext, TPM_HANDLE parentHandle, + PRIVATE_2B *inPrivate, TPM2B_PUBLIC *inPublic, + TPM_HANDLE *objectHandle, + TPM_HANDLE auth, const char *authVal) +{ + Load_In in; + Load_Out out; + TPM_RC rc; + + in.parentHandle = parentHandle; + in.inPrivate.t = *inPrivate; + in.inPublic = *inPublic; + + rc = TSS_Execute (tssContext, + (RESPONSE_PARAMETERS *)&out, + (COMMAND_PARAMETERS *)&in, + NULL, + TPM_CC_Load, + auth, authVal, 0, + TPM_RH_NULL, NULL, 0); + + if (rc == TPM_RC_SUCCESS) + *objectHandle = out.objectHandle; + + return rc; +} + +static inline TPM_RC +tpm2_Import (TSS_CONTEXT *tssContext, TPM_HANDLE parentHandle, + DATA_2B *encryptionKey, TPM2B_PUBLIC *objectPublic, + PRIVATE_2B *duplicate, ENCRYPTED_SECRET_2B *inSymSeed, + TPMT_SYM_DEF_OBJECT *symmetricAlg, PRIVATE_2B *outPrivate, + TPM_HANDLE auth, const char *authVal) +{ + Import_In iin; + Import_Out iout; + TPM_RC rc; + + iin.parentHandle = parentHandle; + iin.encryptionKey.t = *encryptionKey; + iin.objectPublic = *objectPublic; + iin.duplicate.t = *duplicate; + iin.inSymSeed.t = *inSymSeed; + iin.symmetricAlg = *symmetricAlg; + + rc = TSS_Execute (tssContext, + (RESPONSE_PARAMETERS *)&iout, + (COMMAND_PARAMETERS *)&iin, + NULL, + TPM_CC_Import, + auth, authVal, TPMA_SESSION_DECRYPT, + TPM_RH_NULL, NULL, 0); + + *outPrivate = iout.outPrivate.t; + + return rc; +} + +static inline TPM_HANDLE +tpm2_handle_int (TSS_CONTEXT *tssContext, TPM_HANDLE h) +{ + (void)tssContext; + return h; +} + +static inline TPM_HANDLE +tpm2_handle_ext (TSS_CONTEXT *tssContext, TPM_HANDLE h) +{ + (void)tssContext; + return h; +} + +static inline int +tpm2_handle_mso (TSS_CONTEXT *tssContext, TPM_HANDLE h, UINT32 mso) +{ + (void)tssContext; + return (h >> 24) == mso; +} + +#endif diff --git a/tpm2d/tpm2.c b/tpm2d/tpm2.c new file mode 100644 index 000000000..2bd3dc177 --- /dev/null +++ b/tpm2d/tpm2.c @@ -0,0 +1,987 @@ +#include <config.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <ctype.h> +#include <assert.h> +#include <unistd.h> +#include <sys/stat.h> +#include <arpa/inet.h> + +#include "tpm2.h" + +#include "../common/i18n.h" +#include "../common/sexp-parse.h" + +int +tpm2_start (TSS_CONTEXT **tssc) +{ + return TSS_start(tssc); +} + +void +tpm2_end (TSS_CONTEXT *tssc) +{ + TSS_Delete (tssc); +} + +static TPM_HANDLE +tpm2_get_parent (TSS_CONTEXT *tssc, TPM_HANDLE p) +{ + TPM_RC rc; + TPM2B_SENSITIVE_CREATE inSensitive; + TPM2B_PUBLIC inPublic; + TPM_HANDLE objectHandle; + + p = tpm2_handle_int(tssc, p); + if (tpm2_handle_mso(tssc, p, TPM_HT_PERSISTENT)) + return p; /* should only be permanent */ + + /* assume no hierarchy auth */ + VAL_2B (inSensitive.sensitive.userAuth, size) = 0; + /* no sensitive date for storage keys */ + VAL_2B (inSensitive.sensitive.data, size) = 0; + + /* public parameters for a P-256 EC key */ + inPublic.publicArea.type = TPM_ALG_ECC; + inPublic.publicArea.nameAlg = TPM_ALG_SHA256; + VAL (inPublic.publicArea.objectAttributes) = + TPMA_OBJECT_NODA | + TPMA_OBJECT_SENSITIVEDATAORIGIN | + TPMA_OBJECT_USERWITHAUTH | + TPMA_OBJECT_DECRYPT | + TPMA_OBJECT_RESTRICTED | + TPMA_OBJECT_FIXEDPARENT | + TPMA_OBJECT_FIXEDTPM; + + inPublic.publicArea.parameters.eccDetail.symmetric.algorithm = TPM_ALG_AES; + inPublic.publicArea.parameters.eccDetail.symmetric.keyBits.aes = 128; + inPublic.publicArea.parameters.eccDetail.symmetric.mode.aes = TPM_ALG_CFB; + inPublic.publicArea.parameters.eccDetail.scheme.scheme = TPM_ALG_NULL; + inPublic.publicArea.parameters.eccDetail.curveID = TPM_ECC_NIST_P256; + inPublic.publicArea.parameters.eccDetail.kdf.scheme = TPM_ALG_NULL; + + VAL_2B (inPublic.publicArea.unique.ecc.x, size) = 0; + VAL_2B (inPublic.publicArea.unique.ecc.y, size) = 0; + VAL_2B (inPublic.publicArea.authPolicy, size) = 0; + + rc = tpm2_CreatePrimary (tssc, p, &inSensitive, &inPublic, &objectHandle); + if (rc) + { + tpm2_error (rc, "TSS_CreatePrimary"); + return 0; + } + return objectHandle; +} + +void +tpm2_flush_handle (TSS_CONTEXT *tssc, TPM_HANDLE h) +{ + /* only flush volatile handles */ + if (tpm2_handle_mso(tssc, h, TPM_HT_PERSISTENT)) + return; + + tpm2_FlushContext(tssc, h); +} + +static int +tpm2_get_hmac_handle (TSS_CONTEXT *tssc, TPM_HANDLE *handle, + TPM_HANDLE salt_key) +{ + TPM_RC rc; + TPMT_SYM_DEF symmetric; + + symmetric.algorithm = TPM_ALG_AES; + symmetric.keyBits.aes = 128; + symmetric.mode.aes = TPM_ALG_CFB; + + rc = tpm2_StartAuthSession(tssc, salt_key, TPM_RH_NULL, TPM_SE_HMAC, + &symmetric, TPM_ALG_SHA256, handle, NULL); + if (rc) + { + tpm2_error (rc, "TPM2_StartAuthSession"); + return GPG_ERR_CARD; + } + + return 0; +} + +static int +tpm2_pre_auth (ctrl_t ctrl, TSS_CONTEXT *tssc, + gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info, + char **retstr), + TPM_HANDLE *ah, char **auth) +{ + TPM_RC rc; + int len; + + rc = pin_cb (ctrl, _("TPM Key Passphrase"), auth); + if (rc) + return rc; + + len = strlen(*auth); + /* + * TPMs can't accept a longer passphrase than the name algorithm. + * We hard code the name algorithm to SHA256 so the max passphrase + * length is 32 + */ + if (len > 32) + { + log_error ("Truncating Passphrase to TPM allowed 32\n"); + (*auth)[32] = '\0'; + } + + rc = tpm2_get_hmac_handle (tssc, ah, TPM_RH_NULL); + + return rc; +} + +static int +tpm2_post_auth (TSS_CONTEXT *tssc, TPM_RC rc, TPM_HANDLE ah, + char **auth, const char *cmd_str) +{ + gcry_free (*auth); + *auth = NULL; + if (rc) + { + tpm2_error (rc, cmd_str); + tpm2_flush_handle (tssc, ah); + switch (rc & 0xFF) + { + case TPM_RC_BAD_AUTH: + case TPM_RC_AUTH_FAIL: + return GPG_ERR_BAD_PASSPHRASE; + default: + return GPG_ERR_CARD; + } + } + return 0; +} + +static unsigned char * +make_tpm2_shadow_info (uint32_t parent, const char *pub, int pub_len, + const char *priv, int priv_len, size_t *len) +{ + gcry_sexp_t s_exp; + char *info; + + gcry_sexp_build (&s_exp, NULL, "(%u%b%b)", parent, pub_len, pub, + priv_len, priv); + + *len = gcry_sexp_sprint (s_exp, GCRYSEXP_FMT_CANON, NULL, 0); + info = xtrymalloc (*len); + if (!info) + goto out; + gcry_sexp_sprint (s_exp, GCRYSEXP_FMT_CANON, info, *len); + + out: + gcry_sexp_release (s_exp); + return (unsigned char *)info; +} + +static gpg_error_t +parse_tpm2_shadow_info (const unsigned char *shadow_info, + uint32_t *parent, + const char **pub, int *pub_len, + const char **priv, int *priv_len) +{ + const unsigned char *s; + size_t n; + int i; + + s = shadow_info; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + *parent = 0; + for (i = 0; i < n; i++) + { + *parent *= 10; + *parent += atoi_1(s+i); + } + + s += n; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + + *pub_len = n; + *pub = s; + + s += n; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + + *priv_len = n; + *priv = s; + + return 0; +} + +int +tpm2_load_key (TSS_CONTEXT *tssc, const unsigned char *shadow_info, + TPM_HANDLE *key, TPMI_ALG_PUBLIC *type) +{ + uint32_t parent; + TPM_HANDLE parentHandle; + PRIVATE_2B inPrivate; + TPM2B_PUBLIC inPublic; + const char *pub, *priv; + int ret, pub_len, priv_len; + TPM_RC rc; + BYTE *buf; + uint32_t size; + + ret = parse_tpm2_shadow_info (shadow_info, &parent, &pub, &pub_len, + &priv, &priv_len); + if (ret) + return ret; + + parentHandle = tpm2_get_parent (tssc, parent); + + buf = (BYTE *)priv; + size = priv_len; + TPM2B_PRIVATE_Unmarshal ((TPM2B_PRIVATE *)&inPrivate, &buf, &size); + + buf = (BYTE *)pub; + size = pub_len; + TPM2B_PUBLIC_Unmarshal (&inPublic, &buf, &size, FALSE); + + *type = inPublic.publicArea.type; + + rc = tpm2_Load (tssc, parentHandle, &inPrivate, &inPublic, key, + TPM_RS_PW, NULL); + + tpm2_flush_handle (tssc, parentHandle); + + if (rc != TPM_RC_SUCCESS) + { + tpm2_error (rc, "TPM2_Load"); + return GPG_ERR_CARD; + } + + return 0; +} + +int +tpm2_sign (ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key, + gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info, + char **retstr), + TPMI_ALG_PUBLIC type, + const unsigned char *digest, size_t digestlen, + unsigned char **r_sig, size_t *r_siglen) +{ + int ret; + DIGEST_2B digest2b; + TPMT_SIG_SCHEME inScheme; + TPMT_SIGNATURE signature; + TPM_HANDLE ah; + char *auth; + + /* The TPM insists on knowing the digest type, so + * calculate that from the size */ + switch (digestlen) + { + case 20: + inScheme.details.rsassa.hashAlg = TPM_ALG_SHA1; + break; + case 32: + inScheme.details.rsassa.hashAlg = TPM_ALG_SHA256; + break; + case 48: + inScheme.details.rsassa.hashAlg = TPM_ALG_SHA384; + break; +#ifdef TPM_ALG_SHA512 + case 64: + inScheme.details.rsassa.hashAlg = TPM_ALG_SHA512; + break; +#endif + default: + log_error ("Unknown signature digest length, cannot deduce hash type for TPM\n"); + return GPG_ERR_NO_SIGNATURE_SCHEME; + } + digest2b.size = digestlen; + memcpy (digest2b.buffer, digest, digestlen); + + if (type == TPM_ALG_RSA) + inScheme.scheme = TPM_ALG_RSASSA; + else if (type == TPM_ALG_ECC) + inScheme.scheme = TPM_ALG_ECDSA; + else + return GPG_ERR_PUBKEY_ALGO; + + ret = tpm2_pre_auth (ctrl, tssc, pin_cb, &ah, &auth); + if (ret) + return ret; + ret = tpm2_Sign (tssc, key, &digest2b, &inScheme, &signature, ah, auth); + ret = tpm2_post_auth (tssc, ret, ah, &auth, "TPM2_Sign"); + if (ret) + return ret; + + if (type == TPM_ALG_RSA) + *r_siglen = VAL_2B (signature.signature.rsassa.sig, size); + else if (type == TPM_ALG_ECC) + *r_siglen = VAL_2B (signature.signature.ecdsa.signatureR, size) + + VAL_2B (signature.signature.ecdsa.signatureS, size); + + *r_sig = xtrymalloc (*r_siglen); + if (!r_sig) + return GPG_ERR_ENOMEM; + + if (type == TPM_ALG_RSA) + { + memcpy (*r_sig, VAL_2B (signature.signature.rsassa.sig, buffer), + *r_siglen); + } + else if (type == TPM_ALG_ECC) + { + memcpy (*r_sig, VAL_2B (signature.signature.ecdsa.signatureR, buffer), + VAL_2B (signature.signature.ecdsa.signatureR, size)); + memcpy (*r_sig + VAL_2B (signature.signature.ecdsa.signatureR, size), + VAL_2B (signature.signature.ecdsa.signatureS, buffer), + VAL_2B (signature.signature.ecdsa.signatureS, size)); + } + + return 0; +} + +static int +sexp_to_tpm2_sensitive_ecc (TPMT_SENSITIVE *s, gcry_sexp_t key) +{ + gcry_mpi_t d; + gcry_sexp_t l; + int rc = -1; + size_t len; + + s->sensitiveType = TPM_ALG_ECC; + VAL_2B (s->seedValue, size) = 0; + + l = gcry_sexp_find_token (key, "d", 0); + if (!l) + return rc; + d = gcry_sexp_nth_mpi (l, 1, GCRYMPI_FMT_USG); + gcry_sexp_release (l); + len = sizeof (VAL_2B (s->sensitive.ecc, buffer)); + rc = gcry_mpi_print (GCRYMPI_FMT_USG, VAL_2B (s->sensitive.ecc, buffer), + len, &len, d); + VAL_2B (s->sensitive.ecc, size) = len; + gcry_mpi_release (d); + + return rc; +} + +/* try to match the libgcrypt curve names to known TPM parameters. + * + * As of 2018 the TCG defined curves are only NIST + * (192,224,256,384,521) Barreto-Naehring (256,638) and the Chinese + * SM2 (256), which means only the NIST ones overlap with libgcrypt */ +static struct { + const char *name; + TPMI_ECC_CURVE c; +} tpm2_curves[] = { + { "NIST P-192", TPM_ECC_NIST_P192 }, + { "prime192v1", TPM_ECC_NIST_P192 }, + { "secp192r1", TPM_ECC_NIST_P192 }, + { "nistp192", TPM_ECC_NIST_P192 }, + { "NIST P-224", TPM_ECC_NIST_P224 }, + { "secp224r1", TPM_ECC_NIST_P224 }, + { "nistp224", TPM_ECC_NIST_P224 }, + { "NIST P-256", TPM_ECC_NIST_P256 }, + { "prime256v1", TPM_ECC_NIST_P256 }, + { "secp256r1", TPM_ECC_NIST_P256 }, + { "nistp256", TPM_ECC_NIST_P256 }, + { "NIST P-384", TPM_ECC_NIST_P384 }, + { "secp384r1", TPM_ECC_NIST_P384 }, + { "nistp384", TPM_ECC_NIST_P384 }, + { "NIST P-521", TPM_ECC_NIST_P521 }, + { "secp521r1", TPM_ECC_NIST_P521 }, + { "nistp521", TPM_ECC_NIST_P521 }, +}; + +static int +tpm2_ecc_curve (const char *curve_name, TPMI_ECC_CURVE *c) +{ + int i; + + for (i = 0; i < DIM (tpm2_curves); i++) + if (strcmp (tpm2_curves[i].name, curve_name) == 0) + break; + if (i == DIM (tpm2_curves)) + { + log_error ("curve %s does not match any available TPM curves\n", curve_name); + return GPG_ERR_UNKNOWN_CURVE; + } + + *c = tpm2_curves[i].c; + + return 0; +} + +static int +sexp_to_tpm2_public_ecc (TPMT_PUBLIC *p, gcry_sexp_t key) +{ + const char *q; + gcry_sexp_t l; + int rc = GPG_ERR_BAD_PUBKEY; + size_t len; + TPMI_ECC_CURVE curve; + char *curve_name; + + l = gcry_sexp_find_token (key, "curve", 0); + if (!l) + return rc; + curve_name = gcry_sexp_nth_string (l, 1); + if (!curve_name) + goto out; + rc = tpm2_ecc_curve (curve_name, &curve); + gcry_free (curve_name); + if (rc) + goto out; + gcry_sexp_release (l); + + l = gcry_sexp_find_token (key, "q", 0); + if (!l) + return rc; + q = gcry_sexp_nth_data (l, 1, &len); + /* This is a point representation, the first byte tells you what + * type. The only format we understand is uncompressed (0x04) + * which has layout 0x04 | x | y */ + if (q[0] != 0x04) + { + log_error ("Point format for q is not uncompressed\n"); + goto out; + } + q++; + len--; + /* now should have to equal sized big endian point numbers */ + if ((len & 0x01) == 1) + { + log_error ("Point format for q has incorrect length\n"); + goto out; + } + + len >>= 1; + + p->type = TPM_ALG_ECC; + p->nameAlg = TPM_ALG_SHA256; + VAL (p->objectAttributes) = TPMA_OBJECT_NODA | + TPMA_OBJECT_SIGN | + TPMA_OBJECT_DECRYPT | + TPMA_OBJECT_USERWITHAUTH; + VAL_2B (p->authPolicy, size) = 0; + p->parameters.eccDetail.symmetric.algorithm = TPM_ALG_NULL; + p->parameters.eccDetail.scheme.scheme = TPM_ALG_NULL; + p->parameters.eccDetail.curveID = curve; + p->parameters.eccDetail.kdf.scheme = TPM_ALG_NULL; + memcpy (VAL_2B (p->unique.ecc.x, buffer), q, len); + VAL_2B (p->unique.ecc.x, size) = len; + memcpy (VAL_2B (p->unique.ecc.y, buffer), q + len, len); + VAL_2B (p->unique.ecc.y, size) = len; + out: + gcry_sexp_release (l); + return rc; +} + +static int +sexp_to_tpm2_sensitive_rsa (TPMT_SENSITIVE *s, gcry_sexp_t key) +{ + gcry_mpi_t p; + gcry_sexp_t l; + int rc = -1; + size_t len; + + s->sensitiveType = TPM_ALG_RSA; + VAL_2B (s->seedValue, size) = 0; + + l = gcry_sexp_find_token (key, "p", 0); + if (!l) + return rc; + p = gcry_sexp_nth_mpi (l, 1, GCRYMPI_FMT_USG); + gcry_sexp_release (l); + len = sizeof (VAL_2B (s->sensitive.rsa, buffer)); + rc = gcry_mpi_print (GCRYMPI_FMT_USG, VAL_2B (s->sensitive.rsa, buffer), + len, &len, p); + VAL_2B (s->sensitive.rsa, size) = len; + gcry_mpi_release (p); + + return rc; +} + +static int +sexp_to_tpm2_public_rsa (TPMT_PUBLIC *p, gcry_sexp_t key) +{ + gcry_mpi_t n, e; + gcry_sexp_t l; + int rc = -1, i; + size_t len; + /* longer than an int */ + unsigned char ebuf[5]; + uint32_t exp = 0; + + p->type = TPM_ALG_RSA; + p->nameAlg = TPM_ALG_SHA256; + VAL (p->objectAttributes) = TPMA_OBJECT_NODA | + TPMA_OBJECT_DECRYPT | + TPMA_OBJECT_SIGN | + TPMA_OBJECT_USERWITHAUTH; + VAL_2B (p->authPolicy, size) = 0; + p->parameters.rsaDetail.symmetric.algorithm = TPM_ALG_NULL; + p->parameters.rsaDetail.scheme.scheme = TPM_ALG_NULL; + + l = gcry_sexp_find_token (key, "n", 0); + if (!l) + return rc; + n = gcry_sexp_nth_mpi (l, 1, GCRYMPI_FMT_USG); + gcry_sexp_release (l); + len = sizeof (VAL_2B (p->unique.rsa, buffer)); + p->parameters.rsaDetail.keyBits = gcry_mpi_get_nbits (n); + rc = gcry_mpi_print (GCRYMPI_FMT_USG, VAL_2B (p->unique.rsa, buffer), + len, &len, n); + VAL_2B (p->unique.rsa, size) = len; + gcry_mpi_release (n); + if (rc) + return rc; + rc = -1; + l = gcry_sexp_find_token (key, "e", 0); + if (!l) + return rc; + e = gcry_sexp_nth_mpi (l, 1, GCRYMPI_FMT_USG); + gcry_sexp_release (l); + len = sizeof (ebuf); + rc = gcry_mpi_print (GCRYMPI_FMT_USG, ebuf, len, &len, e); + gcry_mpi_release (e); + if (rc) + return rc; + if (len > 4) + return -1; + + /* MPI are simply big endian integers, so convert to uint32 */ + for (i = 0; i < len; i++) + { + exp <<= 8; + exp += ebuf[i]; + } + if (exp == 0x10001) + p->parameters.rsaDetail.exponent = 0; + else + p->parameters.rsaDetail.exponent = exp; + return 0; +} + +static int +sexp_to_tpm2(TPMT_PUBLIC *p, TPMT_SENSITIVE *s, gcry_sexp_t s_skey) +{ + gcry_sexp_t l1, l2; + int rc = -1; + + /* find the value of (private-key */ + l1 = gcry_sexp_nth (s_skey, 1); + if (!l1) + return rc; + + l2 = gcry_sexp_find_token (l1, "rsa", 0); + if (l2) + { + rc = sexp_to_tpm2_public_rsa (p, l2); + if (!rc) + rc = sexp_to_tpm2_sensitive_rsa (s, l2); + } + else + { + l2 = gcry_sexp_find_token (l1, "ecc", 0); + if (!l2) + goto out; + rc = sexp_to_tpm2_public_ecc (p, l2); + if (!rc) + rc = sexp_to_tpm2_sensitive_ecc (s, l2); + } + + gcry_sexp_release (l2); + + out: + gcry_sexp_release (l1); + return rc; +} + +/* copied from TPM implementation code */ +static TPM_RC +tpm2_ObjectPublic_GetName (NAME_2B *name, + TPMT_PUBLIC *tpmtPublic) +{ + TPM_RC rc = 0; + uint16_t written = 0; + TPMT_HA digest; + uint32_t sizeInBytes; + uint8_t buffer[MAX_RESPONSE_SIZE]; + + /* marshal the TPMT_PUBLIC */ + if (rc == 0) + { + INT32 size = MAX_RESPONSE_SIZE; + uint8_t *buffer1 = buffer; + rc = TSS_TPMT_PUBLIC_Marshal (tpmtPublic, &written, &buffer1, &size); + } + /* hash the public area */ + if (rc == 0) + { + sizeInBytes = TSS_GetDigestSize (tpmtPublic->nameAlg); + digest.hashAlg = tpmtPublic->nameAlg; /* Name digest algorithm */ + /* generate the TPMT_HA */ + rc = TSS_Hash_Generate (&digest, written, buffer, 0, NULL); + } + if (rc == 0) + { + TPMI_ALG_HASH nameAlgNbo; + + /* copy the digest */ + memcpy (name->name + sizeof (TPMI_ALG_HASH), + (uint8_t *)&digest.digest, sizeInBytes); + /* copy the hash algorithm */ + nameAlgNbo = htons (tpmtPublic->nameAlg); + memcpy (name->name, (uint8_t *)&nameAlgNbo, sizeof (TPMI_ALG_HASH)); + /* set the size */ + name->size = sizeInBytes + sizeof (TPMI_ALG_HASH); + } + return rc; +} + +/* + * Cut down version of Part 4 Supporting Routines 7.6.3.10 + * + * Hard coded to symmetrically encrypt with aes128 as the inner + * wrapper and no outer wrapper but with a prototype that allows + * drop in replacement with a tss equivalent + */ +TPM_RC tpm2_SensitiveToDuplicate (TPMT_SENSITIVE *s, + NAME_2B *name, + TPM_ALG_ID nalg, + TPMT_SYM_DEF_OBJECT *symdef, + DATA_2B *innerkey, + PRIVATE_2B *p) +{ + BYTE *buf = p->buffer; + + p->size = 0; + memset (p, 0, sizeof (*p)); + + /* hard code AES CFB */ + if (symdef->algorithm == TPM_ALG_AES + && symdef->mode.aes == TPM_ALG_CFB) + { + TPMT_HA hash; + const int hlen = TSS_GetDigestSize (nalg); + TPM2B *digest = (TPM2B *)buf; + TPM2B *s2b; + int32_t size; + unsigned char null_iv[AES_128_BLOCK_SIZE_BYTES]; + UINT16 bsize, written = 0; + gcry_cipher_hd_t hd; + + /* WARNING: don't use the static null_iv trick here: + * the AES routines alter the passed in iv */ + memset (null_iv, 0, sizeof (null_iv)); + + /* reserve space for hash before the encrypted sensitive */ + bsize = sizeof (digest->size) + hlen; + buf += bsize; + p->size += bsize; + s2b = (TPM2B *)buf; + + /* marshal the digest size */ + buf = (BYTE *)&digest->size; + bsize = hlen; + size = 2; + TSS_UINT16_Marshal (&bsize, &written, &buf, &size); + + /* marshal the unencrypted sensitive in place */ + size = sizeof (*s); + bsize = 0; + buf = s2b->buffer; + TSS_TPMT_SENSITIVE_Marshal (s, &bsize, &buf, &size); + buf = (BYTE *)&s2b->size; + size = 2; + TSS_UINT16_Marshal (&bsize, &written, &buf, &size); + + bsize = bsize + sizeof (s2b->size); + p->size += bsize; + + /* compute hash of unencrypted marshalled sensitive and + * write to the digest buffer */ + hash.hashAlg = nalg; + TSS_Hash_Generate (&hash, bsize, s2b, + name->size, name->name, + 0, NULL); + memcpy (digest->buffer, &hash.digest, hlen); + gcry_cipher_open (&hd, GCRY_CIPHER_AES128, + GCRY_CIPHER_MODE_CFB, GCRY_CIPHER_SECURE); + gcry_cipher_setiv (hd, null_iv, sizeof (null_iv)); + gcry_cipher_setkey (hd, innerkey->buffer, innerkey->size); + /* encrypt the hash and sensitive in-place */ + gcry_cipher_encrypt (hd, p->buffer, p->size, NULL, 0); + gcry_cipher_close (hd); + + } + else if (symdef->algorithm == TPM_ALG_NULL) + { + /* Code is for debugging only, should never be used in production */ + TPM2B *s2b = (TPM2B *)buf; + int32_t size = sizeof (*s); + UINT16 bsize = 0, written = 0; + + log_error ("Secret key sent to TPM unencrypted\n"); + buf = s2b->buffer; + + /* marshal the unencrypted sensitive in place */ + TSS_TPMT_SENSITIVE_Marshal (s, &bsize, &buf, &size); + buf = (BYTE *)&s2b->size; + size = 2; + TSS_UINT16_Marshal (&bsize, &written, &buf, &size); + + p->size += bsize + sizeof (s2b->size); + } + else + { + log_error ("Unknown symmetric algorithm\n"); + return TPM_RC_SYMMETRIC; + } + + return TPM_RC_SUCCESS; +} + +int +tpm2_import_key (ctrl_t ctrl, TSS_CONTEXT *tssc, + gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info, + char **retstr), + unsigned char **shadow_info, size_t *shadow_len, + gcry_sexp_t s_skey, unsigned long parent) +{ + TPM_HANDLE parentHandle; + DATA_2B encryptionKey; + TPM2B_PUBLIC objectPublic; + PRIVATE_2B duplicate; + ENCRYPTED_SECRET_2B inSymSeed; + TPMT_SYM_DEF_OBJECT symmetricAlg; + PRIVATE_2B outPrivate; + NAME_2B name; + const int aes_key_bits = 128; + const int aes_key_bytes = aes_key_bits/8; + + TPMT_SENSITIVE s; + TPM_HANDLE ah; + TPM_RC rc; + + uint32_t size; + uint16_t len; + BYTE *buffer; + int ret; + char *passphrase; + + char pub[sizeof (TPM2B_PUBLIC)]; + int pub_len; + char priv[sizeof (TPM2B_PRIVATE)]; + int priv_len; + + if (parent == 0) + parent = EXT_TPM_RH_OWNER; + + ret = sexp_to_tpm2 (&objectPublic.publicArea, &s, s_skey); + if (ret) + { + log_error ("Failed to parse Key s-expression: key corrupt?\n"); + return ret; + } + + /* add an authorization password to the key which the TPM will check */ + + ret = pin_cb (ctrl, _("Please enter the TPM Authorization passphrase for the key."), &passphrase); + if (ret) + return ret; + len = strlen(passphrase); + if (len > TSS_GetDigestSize(objectPublic.publicArea.nameAlg)) + { + len = TSS_GetDigestSize(objectPublic.publicArea.nameAlg); + log_error ("Truncating Passphrase to TPM allowed %d\n", len); + } + VAL_2B (s.authValue, size) = len; + memcpy (VAL_2B (s.authValue, buffer), passphrase, len); + + /* We're responsible for securing the data in transmission to the + * TPM here. The TPM provides parameter encryption via a session, + * but only for the first parameter. For TPM2_Import, the first + * parameter is a symmetric key used to encrypt the sensitive data, + * so we must populate this key with random value and encrypt the + * sensitive data with it */ + parentHandle = tpm2_get_parent (tssc, parent); + tpm2_ObjectPublic_GetName (&name, &objectPublic.publicArea); + gcry_randomize (encryptionKey.buffer, + aes_key_bytes, GCRY_STRONG_RANDOM); + encryptionKey.size = aes_key_bytes; + + /* set random symSeed */ + inSymSeed.size = 0; + symmetricAlg.algorithm = TPM_ALG_AES; + symmetricAlg.keyBits.aes = aes_key_bits; + symmetricAlg.mode.aes = TPM_ALG_CFB; + + tpm2_SensitiveToDuplicate (&s, &name, objectPublic.publicArea.nameAlg, + &symmetricAlg, &encryptionKey, &duplicate); + + /* use salted parameter encryption to hide the key. First we read + * the public parameters of the parent key and use them to agree an + * encryption for the first parameter */ + rc = tpm2_get_hmac_handle (tssc, &ah, parentHandle); + if (rc) + { + tpm2_flush_handle (tssc, parentHandle); + return GPG_ERR_CARD; + } + + rc = tpm2_Import (tssc, parentHandle, &encryptionKey, &objectPublic, + &duplicate, &inSymSeed, &symmetricAlg, &outPrivate, + ah, NULL); + tpm2_flush_handle (tssc, parentHandle); + if (rc) + { + tpm2_error (rc, "TPM2_Import"); + /* failure means auth handle is not flushed */ + tpm2_flush_handle (tssc, ah); + + if ((rc & 0xbf) == TPM_RC_VALUE) + { + log_error ("TPM cannot import RSA key: wrong size"); + return GPG_ERR_UNSUPPORTED_ALGORITHM; + } + else if ((rc & 0xbf) == TPM_RC_CURVE) + { + log_error ("TPM cannot import requested curve"); + return GPG_ERR_UNKNOWN_CURVE; + } + return GPG_ERR_CARD; + } + + size = sizeof (pub); + buffer = pub; + len = 0; + TSS_TPM2B_PUBLIC_Marshal (&objectPublic, + &len, &buffer, &size); + pub_len = len; + + size = sizeof (priv); + buffer = priv; + len = 0; + TSS_TPM2B_PRIVATE_Marshal ((TPM2B_PRIVATE *)&outPrivate, + &len, &buffer, &size); + priv_len = len; + + *shadow_info = make_tpm2_shadow_info (parent, pub, pub_len, + priv, priv_len, shadow_len); + return rc; +} + +int +tpm2_ecc_decrypt (ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key, + gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info, + char **retstr), + const char *ciphertext, int ciphertext_len, + char **decrypt, size_t *decrypt_len) +{ + TPM2B_ECC_POINT inPoint; + TPM2B_ECC_POINT outPoint; + TPM_HANDLE ah; + char *auth; + size_t len; + int ret; + + /* This isn't really a decryption per se. The ciphertext actually + * contains an EC Point which we must multiply by the private key number. + * + * The reason is to generate a diffe helman agreement on a shared + * point. This shared point is then used to generate the per + * session encryption key. + */ + if (ciphertext[0] != 0x04) + { + log_error ("Decryption Shared Point format is not uncompressed\n"); + return GPG_ERR_ENCODING_PROBLEM; + } + if ((ciphertext_len & 0x01) != 1) + { + log_error ("Decryption Shared Point has incorrect length\n"); + return GPG_ERR_ENCODING_PROBLEM; + } + len = ciphertext_len >> 1; + + memcpy (VAL_2B (inPoint.point.x, buffer), ciphertext + 1, len); + VAL_2B (inPoint.point.x, size) = len; + memcpy (VAL_2B (inPoint.point.y, buffer), ciphertext + 1 + len, len); + VAL_2B (inPoint.point.y, size) = len; + + ret = tpm2_pre_auth (ctrl, tssc, pin_cb, &ah, &auth); + if (ret) + return ret; + ret = tpm2_ECDH_ZGen (tssc, key, &inPoint, &outPoint, ah, auth); + ret = tpm2_post_auth (tssc, ret, ah, &auth, "TPM2_ECDH_ZGen"); + if (ret) + return ret; + + *decrypt_len = VAL_2B (outPoint.point.x, size) + + VAL_2B (outPoint.point.y, size) + 1; + *decrypt = xtrymalloc (*decrypt_len); + (*decrypt)[0] = 0x04; + memcpy (*decrypt + 1, VAL_2B (outPoint.point.x, buffer), + VAL_2B (outPoint.point.x, size)); + memcpy (*decrypt + 1 + VAL_2B (outPoint.point.x, size), + VAL_2B (outPoint.point.y, buffer), + VAL_2B (outPoint.point.y, size)); + + return 0; +} + +int +tpm2_rsa_decrypt (ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key, + gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info, + char **retstr), + const char *ciphertext, int ciphertext_len, + char **decrypt, size_t *decrypt_len) +{ + int ret; + PUBLIC_KEY_RSA_2B cipherText; + TPMT_RSA_DECRYPT inScheme; + PUBLIC_KEY_RSA_2B message; + TPM_HANDLE ah; + char *auth; + + inScheme.scheme = TPM_ALG_RSAES; + /* + * apparent gcrypt error: occasionally rsa ciphertext will + * be one byte too long and have a leading zero + */ + if ((ciphertext_len & 1) == 1 && ciphertext[0] == 0) + { + log_info ("Fixing Wrong Ciphertext size %d\n", ciphertext_len); + ciphertext_len--; + ciphertext++; + } + cipherText.size = ciphertext_len; + memcpy (cipherText.buffer, ciphertext, ciphertext_len); + + ret = tpm2_pre_auth (ctrl, tssc, pin_cb, &ah, &auth); + if (ret) + return ret; + ret = tpm2_RSA_Decrypt (tssc, key, &cipherText, &inScheme, &message, + ah, auth, TPMA_SESSION_ENCRYPT); + ret = tpm2_post_auth (tssc, ret, ah, &auth, "TPM2_RSA_Decrypt"); + if (ret) + return ret; + + *decrypt_len = message.size; + *decrypt = xtrymalloc (message.size); + memcpy (*decrypt, message.buffer, message.size); + + return 0; +} diff --git a/tpm2d/tpm2.h b/tpm2d/tpm2.h new file mode 100644 index 000000000..a2d3745ea --- /dev/null +++ b/tpm2d/tpm2.h @@ -0,0 +1,34 @@ +#ifndef _TPM2_H +#define _TPM2_H + +#include "../common/util.h" +#include "ibm-tss.h" + +int tpm2_start (TSS_CONTEXT **tssc); +void tpm2_end (TSS_CONTEXT *tssc); +void tpm2_flush_handle (TSS_CONTEXT *tssc, TPM_HANDLE h); +int tpm2_load_key (TSS_CONTEXT *tssc, const unsigned char *shadow_info, + TPM_HANDLE *key, TPMI_ALG_PUBLIC *type); +int tpm2_sign (ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key, + gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info, + char **retstr), + TPMI_ALG_PUBLIC type, + const unsigned char *digest, size_t digestlen, + unsigned char **r_sig, size_t *r_siglen); +int tpm2_import_key (ctrl_t ctrl, TSS_CONTEXT *tssc, + gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info, + char **retstr), + unsigned char **shadow_info, size_t *shadow_len, + gcry_sexp_t s_skey, unsigned long parent); +int tpm2_rsa_decrypt (ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key, + gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info, + char **retstr), + const char *ciphertext, int ciphertext_len, + char **decrypt, size_t *decrypt_len); +int tpm2_ecc_decrypt (ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key, + gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info, + char **retstr), + const char *ciphertext, int ciphertext_len, + char **decrypt, size_t *decrypt_len); + +#endif diff --git a/tpm2d/tpm2daemon.c b/tpm2d/tpm2daemon.c new file mode 100644 index 000000000..4ec6d7959 --- /dev/null +++ b/tpm2d/tpm2daemon.c @@ -0,0 +1,1289 @@ +/* tpm2daemon.c - The GnuPG tpm2 Daemon + * Copyright (C) 2001-2002, 2004-2005, 2007-2009 Free Software Foundation, Inc. + * Copyright (C) 2001-2002, 2004-2005, 2007-2014 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG 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. + * + * GnuPG 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 this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#include <config.h> + +#include <stdio.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdarg.h> +#include <string.h> +#include <errno.h> +#include <assert.h> +#include <time.h> +#include <fcntl.h> +#ifndef HAVE_W32_SYSTEM +#include <sys/socket.h> +#include <sys/un.h> +#endif /*HAVE_W32_SYSTEM*/ +#include <unistd.h> +#include <signal.h> +#include <npth.h> + +#define INCLUDED_BY_MAIN_MODULE 1 +#define GNUPG_COMMON_NEED_AFLOCAL +#include "tpm2daemon.h" +#include <gcrypt.h> + +#include <assuan.h> /* malloc hooks */ + +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/gc-opt-flags.h" +#include "../common/asshelp.h" +#include "../common/exechelp.h" +#include "../common/init.h" + +#ifndef ENAMETOOLONG +# define ENAMETOOLONG EINVAL +#endif + +enum cmd_and_opt_values +{ aNull = 0, + oCsh = 'c', + oQuiet = 'q', + oSh = 's', + oVerbose = 'v', + + oNoVerbose = 500, + aGPGConfList, + aGPGConfTest, + oOptions, + oDebug, + oDebugAll, + oDebugLevel, + oDebugWait, + oDebugAllowCoreDump, + oDebugLogTid, + oDebugAssuanLogCats, + oNoGreeting, + oNoOptions, + oHomedir, + oNoDetach, + oNoGrab, + oLogFile, + oServer, + oMultiServer, + oDaemon, + oListenBacklog, + oParent +}; + + + +static gpgrt_opt_t opts[] = { + ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"), + ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"), + + ARGPARSE_group (301, N_("@Options:\n ")), + + ARGPARSE_s_n (oServer,"server", N_("run in server mode (foreground)")), + ARGPARSE_s_n (oMultiServer, "multi-server", + N_("run in multi server mode (foreground)")), + ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")), + ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")), + ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")), + ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")), + ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")), + ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")), + ARGPARSE_s_s (oDebug, "debug", "@"), + ARGPARSE_s_n (oDebugAll, "debug-all", "@"), + ARGPARSE_s_s (oDebugLevel, "debug-level" , + N_("|LEVEL|set the debugging level to LEVEL")), + ARGPARSE_s_i (oDebugWait, "debug-wait", "@"), + ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"), + ARGPARSE_s_n (oDebugLogTid, "debug-log-tid", "@"), + ARGPARSE_p_u (oDebugAssuanLogCats, "debug-assuan-log-cats", "@"), + ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")), + ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write a log to FILE")), + ARGPARSE_s_s (oHomedir, "homedir", "@"), + ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), + ARGPARSE_p_u (oParent, "tpm2-parent", + N_("Specify tpm2 parent for key")), + + ARGPARSE_end () +}; + + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_MPI_VALUE , "mpi" }, + { DBG_CRYPTO_VALUE , "crypto" }, + { DBG_IPC_VALUE , "ipc" }, + { 0, NULL } + }; + + +/* The timer tick used to check card removal. + + We poll every 500ms to let the user immediately know a status + change. + + For a card reader with an interrupt endpoint, this timer is not + used with the internal CCID driver. + + This is not too good for power saving but given that there is no + easy way to block on card status changes it is the best we can do. + For PC/SC we could in theory use an extra thread to wait for status + changes but that requires a native thread because there is no way + to make the underlying PC/SC card change function block using a Npth + mechanism. Given that a native thread could only be used under W32 + we don't do that at all. */ +#define TIMERTICK_INTERVAL_SEC (0) +#define TIMERTICK_INTERVAL_USEC (500000) + +/* Flag to indicate that a shutdown was requested. */ +static int shutdown_pending; + +/* It is possible that we are currently running under setuid permissions */ +static int maybe_setuid = 1; + +/* Flag telling whether we are running as a pipe server. */ +static int pipe_server; + +/* Name of the communication socket */ +static char *socket_name; +/* Name of the redirected socket or NULL. */ +static char *redir_socket_name; + +/* We need to keep track of the server's nonces (these are dummies for + POSIX systems). */ +static assuan_sock_nonce_t socket_nonce; + +/* Value for the listen() backlog argument. Change at runtime with + * --listen-backlog. */ +static int listen_backlog = 64; + +#ifdef HAVE_W32_SYSTEM +static HANDLE the_event; +#else +/* PID to notify update of usb devices. */ +static pid_t main_thread_pid; +#endif +#ifdef HAVE_PSELECT_NO_EINTR +/* FD to notify changes. */ +static int notify_fd; +#endif + +static char *create_socket_name (char *standard_name); +static gnupg_fd_t create_server_socket (const char *name, + char **r_redir_name, + assuan_sock_nonce_t *nonce); + +static void *start_connection_thread (void *arg); +static void handle_connections (int listen_fd); + +/* Pth wrapper function definitions. */ +ASSUAN_SYSTEM_NPTH_IMPL; + +static int active_connections; + + +static char * +make_libversion (const char *libname, const char *(*getfnc)(const char*)) +{ + const char *s; + char *result; + + if (maybe_setuid) + { + gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */ + maybe_setuid = 0; + } + s = getfnc (NULL); + result = xmalloc (strlen (libname) + 1 + strlen (s) + 1); + strcpy (stpcpy (stpcpy (result, libname), " "), s); + return result; +} + + +static const char * +my_strusage (int level) +{ + static char *ver_gcry; + const char *p; + + switch (level) + { + case 11: p = "@TPM2DAEMON@ (@GNUPG@)"; + break; + case 13: p = VERSION; break; + case 17: p = PRINTABLE_OS_NAME; break; + case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break; + + case 20: + if (!ver_gcry) + ver_gcry = make_libversion ("libgcrypt", gcry_check_version); + p = ver_gcry; + break; + case 1: + case 40: p = _("Usage: @TPM2DAEMON@ [options] (-h for help)"); + break; + case 41: p = _("Syntax: tpm2daemon [options] [command [args]]\n" + "TPM2 daemon for @GNUPG@\n"); + break; + + default: p = NULL; + } + return p; +} + + +static int +tid_log_callback (unsigned long *rvalue) +{ + int len = sizeof (*rvalue); + npth_t thread; + + thread = npth_self (); + if (sizeof (thread) < len) + len = sizeof (thread); + memcpy (rvalue, &thread, len); + + return 2; /* Use use hex representation. */ +} + + +/* Setup the debugging. With a LEVEL of NULL only the active debug + flags are propagated to the subsystems. With LEVEL set, a specific + set of debug flags is set; thus overriding all flags already + set. */ +static void +set_debug (const char *level) +{ + int numok = (level && digitp (level)); + int numlvl = numok? atoi (level) : 0; + + if (!level) + ; + else if (!strcmp (level, "none") || (numok && numlvl < 1)) + opt.debug = 0; + else if (!strcmp (level, "basic") || (numok && numlvl <= 2)) + opt.debug = DBG_IPC_VALUE; + else if (!strcmp (level, "advanced") || (numok && numlvl <= 5)) + opt.debug = DBG_IPC_VALUE; + else if (!strcmp (level, "expert") || (numok && numlvl <= 8)) + opt.debug = DBG_IPC_VALUE; + else if (!strcmp (level, "guru") || numok) + opt.debug = ~0; + else + { + log_error (_("invalid debug-level '%s' given\n"), level); + tpm2d_exit (2); + } + + + if (opt.debug && !opt.verbose) + opt.verbose = 1; + if (opt.debug && opt.quiet) + opt.quiet = 0; + + if (opt.debug & DBG_MPI_VALUE) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2); + if (opt.debug & DBG_CRYPTO_VALUE ) + gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1); + gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose); + + if (opt.debug) + parse_debug_flag (NULL, &opt.debug, debug_flags); +} + + + +static void +cleanup (void) +{ + if (socket_name && *socket_name) + { + char *name; + + name = redir_socket_name? redir_socket_name : socket_name; + + gnupg_remove (name); + *socket_name = 0; + } +} + + + +int +main (int argc, char **argv ) +{ + gpgrt_argparse_t pargs; + int orig_argc; + char **orig_argv; + char *last_configname = NULL; + const char *configname = NULL; + const char *shell; + int parse_debug = 0; + const char *debug_level = NULL; + int greeting = 0; + int nogreeting = 0; + int multi_server = 0; + int is_daemon = 0; + int nodetach = 0; + int csh_style = 0; + char *logfile = NULL; + int debug_wait = 0; + int gpgconf_list = 0; + char *config_filename = NULL; + int allow_coredump = 0; + struct assuan_malloc_hooks malloc_hooks; + int res; + npth_t pipecon_handler; + + early_system_init (); + gpgrt_set_strusage (my_strusage); + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + /* Please note that we may running SUID(ROOT), so be very CAREFUL + when adding any stuff between here and the call to INIT_SECMEM() + somewhere after the option parsing */ + log_set_prefix ("tpm2daemon", GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID); + + /* Make sure that our subsystems are ready. */ + i18n_init (); + init_common_subsystems (&argc, &argv); + + malloc_hooks.malloc = gcry_malloc; + malloc_hooks.realloc = gcry_realloc; + malloc_hooks.free = gcry_free; + assuan_set_malloc_hooks (&malloc_hooks); + assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT); + assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); + assuan_sock_init (); + setup_libassuan_logging (&opt.debug, NULL); + + setup_libgcrypt_logging (); + gcry_control (GCRYCTL_USE_SECURE_RNDPOOL); + + disable_core_dumps (); + + /* Set default options. */ + opt.parent = 0; /* 0 means TPM uses default */ + + shell = getenv ("SHELL"); + if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") ) + csh_style = 1; + + /* Check whether we have a config file on the commandline */ + orig_argc = argc; + orig_argv = argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); + while (gpgrt_argparse (NULL, &pargs, opts)) + { + switch (pargs.r_opt) + { + case oDebug: + case oDebugAll: + parse_debug++; + break; + case oHomedir: + gnupg_set_homedir (pargs.r.ret_str); + break; + } + } + /* Reset the flags. */ + pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION); + + /* initialize the secure memory. */ + gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + maybe_setuid = 0; + + /* + Now we are working under our real uid + */ + + + /* The configuraton directories for use by gpgrt_argparser. */ + gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ()); + gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ()); + + /* We are re-using the struct, thus the reset flag. We OR the + * flags so that the internal intialized flag won't be cleared. */ + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags |= (ARGPARSE_FLAG_RESET + | ARGPARSE_FLAG_KEEP + | ARGPARSE_FLAG_SYS + | ARGPARSE_FLAG_USER); + while (gpgrt_argparser (&pargs, opts, TPM2DAEMON_NAME EXTSEP_S "conf")) + { + switch (pargs.r_opt) + { + case ARGPARSE_CONFFILE: + if (parse_debug) + log_info (_("reading options from '%s'\n"), + pargs.r_type? pargs.r.ret_str: "[cmdline]"); + if (pargs.r_type) + { + xfree (last_configname); + last_configname = xstrdup (pargs.r.ret_str); + configname = last_configname; + } + else + configname = NULL; + break; + + case aGPGConfList: gpgconf_list = 1; break; + case aGPGConfTest: gpgconf_list = 2; break; + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + + case oDebug: + if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags)) + { + pargs.r_opt = ARGPARSE_INVALID_ARG; + pargs.err = ARGPARSE_PRINT_ERROR; + } + break; + case oDebugAll: opt.debug = ~0; break; + case oDebugLevel: debug_level = pargs.r.ret_str; break; + case oDebugWait: debug_wait = pargs.r.ret_int; break; + case oDebugAllowCoreDump: + enable_core_dumps (); + allow_coredump = 1; + break; + case oDebugLogTid: + log_set_pid_suffix_cb (tid_log_callback); + break; + case oDebugAssuanLogCats: + set_libassuan_log_cats (pargs.r.ret_ulong); + break; + + case oNoGreeting: nogreeting = 1; break; + case oNoVerbose: opt.verbose = 0; break; + case oNoOptions: break; /* no-options */ + case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break; + case oNoDetach: nodetach = 1; break; + case oLogFile: logfile = pargs.r.ret_str; break; + case oCsh: csh_style = 1; break; + case oSh: csh_style = 0; break; + case oServer: pipe_server = 1; break; + case oMultiServer: pipe_server = 1; multi_server = 1; break; + case oDaemon: is_daemon = 1; break; + + case oListenBacklog: + listen_backlog = pargs.r.ret_int; + break; + + case oParent: + opt.parent = pargs.r.ret_ulong; + break; + + default: + if (configname) + pargs.err = ARGPARSE_PRINT_WARNING; + else + pargs.err = ARGPARSE_PRINT_ERROR; + break; + } + } + gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */ + + if (!last_configname) + config_filename = gpgrt_fnameconcat (gnupg_homedir (), + TPM2DAEMON_NAME EXTSEP_S "conf", + NULL); + else + { + config_filename = last_configname; + last_configname = NULL; + } + + if (log_get_errorcount (0)) + exit (2); + if (nogreeting ) + greeting = 0; + + if (greeting) + { + es_fprintf (es_stderr, "%s %s; %s\n", + gpgrt_strusage (11), gpgrt_strusage (13), + gpgrt_strusage (14) ); + es_fprintf (es_stderr, "%s\n", gpgrt_strusage (15) ); + } +#ifdef IS_DEVELOPMENT_VERSION + log_info ("NOTE: this is a development version!\n"); +#endif + + /* Print a warning if an argument looks like an option. */ + if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN)) + { + int i; + + for (i=0; i < argc; i++) + if (argv[i][0] == '-' && argv[i][1] == '-') + log_info (_("Note: '%s' is not considered an option\n"), argv[i]); + } + + if (atexit (cleanup)) + { + log_error ("atexit failed\n"); + cleanup (); + exit (1); + } + + set_debug (debug_level); + + if (gpgconf_list == 2) + tpm2d_exit (0); + if (gpgconf_list) + { + es_printf ("verbose:%lu:\n" + "quiet:%lu:\n" + "debug-level:%lu:\"none:\n" + "log-file:%lu:\n", + GC_OPT_FLAG_NONE, + GC_OPT_FLAG_NONE, + GC_OPT_FLAG_DEFAULT, + GC_OPT_FLAG_NONE ); + + tpm2d_exit (0); + } + + /* Now start with logging to a file if this is desired. */ + if (logfile) + { + log_set_file (logfile); + log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); + } + + if (debug_wait && pipe_server) + { + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid ()); + gnupg_sleep (debug_wait); + log_debug ("... okay\n"); + } + + if (pipe_server) + { + /* This is the simple pipe based server */ + ctrl_t ctrl; + npth_attr_t tattr; + int fd = -1; + +#ifndef HAVE_W32_SYSTEM + { + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, NULL); + } +#endif + + npth_init (); + gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); + + /* If --debug-allow-core-dump has been given we also need to + switch the working directory to a place where we can actually + write. */ + if (allow_coredump) + { + if (chdir ("/tmp")) + log_debug ("chdir to '/tmp' failed: %s\n", strerror (errno)); + else + log_debug ("changed working directory to '/tmp'\n"); + } + + /* In multi server mode we need to listen on an additional + socket. Create that socket now before starting the handler + for the pipe connection. This allows that handler to send + back the name of that socket. */ + if (multi_server) + { + socket_name = create_socket_name (TPM2DAEMON_SOCK_NAME); + fd = FD2INT (create_server_socket (socket_name, + &redir_socket_name, + &socket_nonce)); + } + + res = npth_attr_init (&tattr); + if (res) + { + log_error ("error allocating thread attributes: %s\n", + strerror (res)); + tpm2d_exit (2); + } + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + + ctrl = xtrycalloc (1, sizeof *ctrl); + if ( !ctrl ) + { + log_error ("error allocating connection control data: %s\n", + strerror (errno) ); + tpm2d_exit (2); + } + ctrl->thread_startup.fd = GNUPG_INVALID_FD; + res = npth_create (&pipecon_handler, &tattr, start_connection_thread, ctrl); + if (res) + { + log_error ("error spawning pipe connection handler: %s\n", + strerror (res) ); + xfree (ctrl); + tpm2d_exit (2); + } + npth_setname_np (pipecon_handler, "pipe-connection"); + npth_attr_destroy (&tattr); + + /* We run handle_connection to wait for the shutdown signal and + to run the ticker stuff. */ + handle_connections (fd); + if (fd != -1) + close (fd); + } + else if (!is_daemon) + { + log_info (_("please use the option '--daemon'" + " to run the program in the background\n")); + } + else + { /* Regular server mode */ + int fd; +#ifndef HAVE_W32_SYSTEM + pid_t pid; + int i; +#endif + + /* Create the socket. */ + socket_name = create_socket_name (TPM2DAEMON_SOCK_NAME); + fd = FD2INT (create_server_socket (socket_name, + &redir_socket_name, &socket_nonce)); + + + fflush (NULL); +#ifdef HAVE_W32_SYSTEM + (void)csh_style; + (void)nodetach; +#else + pid = fork (); + if (pid == (pid_t)-1) + { + log_fatal ("fork failed: %s\n", strerror (errno) ); + exit (1); + } + else if (pid) + { /* we are the parent */ + char *infostr; + + close (fd); + + /* create the info string: <name>:<pid>:<protocol_version> */ + if (gpgrt_asprintf (&infostr, "TPM2DAEMON_INFO=%s:%lu:1", + socket_name, (ulong) pid) < 0) + { + log_error ("out of core\n"); + kill (pid, SIGTERM); + exit (1); + } + *socket_name = 0; /* don't let cleanup() remove the socket - + the child should do this from now on */ + if (argc) + { /* run the program given on the commandline */ + if (putenv (infostr)) + { + log_error ("failed to set environment: %s\n", + strerror (errno) ); + kill (pid, SIGTERM ); + exit (1); + } + execvp (argv[0], argv); + log_error ("failed to run the command: %s\n", strerror (errno)); + kill (pid, SIGTERM); + exit (1); + } + else + { + /* Print the environment string, so that the caller can use + shell's eval to set it */ + if (csh_style) + { + *strchr (infostr, '=') = ' '; + es_printf ( "setenv %s;\n", infostr); + } + else + { + es_printf ( "%s; export TPM2DAEMON_INFO;\n", infostr); + } + xfree (infostr); + exit (0); + } + /* NOTREACHED */ + } /* end parent */ + + /* This is the child. */ + + npth_init (); + gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); + + /* Detach from tty and put process into a new session. */ + if (!nodetach ) + { + /* Close stdin, stdout and stderr unless it is the log stream. */ + for (i=0; i <= 2; i++) + { + if (!log_test_fd (i) && i != fd ) + { + if ( !close (i) + && open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1) + { + log_error ("failed to open '%s': %s\n", + "/dev/null", strerror (errno)); + cleanup (); + exit (1); + } + } + } + + if (setsid () == -1) + { + log_error ("setsid() failed: %s\n", strerror (errno) ); + cleanup (); + exit (1); + } + } + + { + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, NULL); + } + +#endif /*!HAVE_W32_SYSTEM*/ + + if (gnupg_chdir (gnupg_daemon_rootdir ())) + { + log_error ("chdir to '%s' failed: %s\n", + gnupg_daemon_rootdir (), strerror (errno)); + exit (1); + } + + handle_connections (fd); + + close (fd); + } + + xfree (config_filename); + return 0; +} + +void +tpm2d_exit (int rc) +{ + gcry_control (GCRYCTL_TERM_SECMEM ); + rc = rc? rc : log_get_errorcount (0)? 2 : 0; + exit (rc); +} + + +static void +tpm2d_init_default_ctrl (ctrl_t ctrl) +{ + (void)ctrl; +} + +static void +tpm2d_deinit_default_ctrl (ctrl_t ctrl) +{ + if (!ctrl) + return; + xfree (ctrl->in_data.value); + ctrl->in_data.value = NULL; + ctrl->in_data.valuelen = 0; +} + + +/* Return the name of the socket to be used to connect to this + process. If no socket is available, return NULL. */ +const char * +tpm2d_get_socket_name (void) +{ + if (socket_name && *socket_name) + return socket_name; + return NULL; +} + + +#ifndef HAVE_W32_SYSTEM +static void +handle_signal (int signo) +{ + switch (signo) + { + case SIGHUP: + log_info ("SIGHUP received - " + "re-reading configuration\n"); +/* reread_configuration (); */ + break; + + case SIGUSR1: + log_info ("SIGUSR1 received - printing internal information:\n"); + /* Fixme: We need to see how to integrate pth dumping into our + logging system. */ + /* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */ +#if 0 + app_dump_state (); +#endif + break; + + case SIGUSR2: + log_info ("SIGUSR2 received - no action defined\n"); + break; + + case SIGCONT: + /* Nothing. */ + log_debug ("SIGCONT received - breaking select\n"); + break; + + case SIGTERM: + if (!shutdown_pending) + log_info ("SIGTERM received - shutting down ...\n"); + else + log_info ("SIGTERM received - still %i running threads\n", + active_connections); + shutdown_pending++; + if (shutdown_pending > 2) + { + log_info ("shutdown forced\n"); + log_info ("%s %s stopped\n", gpgrt_strusage (11), + gpgrt_strusage (13) ); + cleanup (); + tpm2d_exit (0); + } + break; + + case SIGINT: + log_info ("SIGINT received - immediate shutdown\n"); + log_info ( "%s %s stopped\n", gpgrt_strusage (11), gpgrt_strusage (13)); + cleanup (); + tpm2d_exit (0); + break; + + default: + log_info ("signal %d received - no action defined\n", signo); + } +} +#endif /*!HAVE_W32_SYSTEM*/ + + +/* Create a name for the socket. We check for valid characters as + well as against a maximum allowed length for a unix domain socket + is done. The function terminates the process in case of an error. + Retunrs: Pointer to an allcoated string with the absolute name of + the socket used. */ +static char * +create_socket_name (char *standard_name) +{ + char *name; + + name = make_filename (gnupg_socketdir (), standard_name, NULL); + if (strchr (name, PATHSEP_C)) + { + log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S); + tpm2d_exit (2); + } + return name; +} + + + +/* Create a Unix domain socket with NAME. Returns the file descriptor + or terminates the process in case of an error. If the socket has + been redirected the name of the real socket is stored as a malloced + string at R_REDIR_NAME. */ +static gnupg_fd_t +create_server_socket (const char *name, char **r_redir_name, + assuan_sock_nonce_t *nonce) +{ + struct sockaddr *addr; + struct sockaddr_un *unaddr; + socklen_t len; + gnupg_fd_t fd; + int rc; + + xfree (*r_redir_name); + *r_redir_name = NULL; + + fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0); + if (fd == GNUPG_INVALID_FD) + { + log_error (_("can't create socket: %s\n"), strerror (errno)); + tpm2d_exit (2); + } + + unaddr = xmalloc (sizeof (*unaddr)); + addr = (struct sockaddr*)unaddr; + + { + int redirected; + + if (assuan_sock_set_sockaddr_un (name, addr, &redirected)) + { + if (errno == ENAMETOOLONG) + log_error (_("socket name '%s' is too long\n"), name); + else + log_error ("error preparing socket '%s': %s\n", + name, gpg_strerror (gpg_error_from_syserror ())); + tpm2d_exit (2); + } + if (redirected) + { + *r_redir_name = xstrdup (unaddr->sun_path); + if (opt.verbose) + log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name); + } + } + + len = SUN_LEN (unaddr); + + rc = assuan_sock_bind (fd, addr, len); + if (rc == -1 && errno == EADDRINUSE) + { + gnupg_remove (unaddr->sun_path); + rc = assuan_sock_bind (fd, addr, len); + } + if (rc != -1 + && (rc=assuan_sock_get_nonce (addr, len, nonce))) + log_error (_("error getting nonce for the socket\n")); + if (rc == -1) + { + log_error (_("error binding socket to '%s': %s\n"), + unaddr->sun_path, + gpg_strerror (gpg_error_from_syserror ())); + assuan_sock_close (fd); + tpm2d_exit (2); + } + + if (gnupg_chmod (unaddr->sun_path, "-rwx")) + log_error (_("can't set permissions of '%s': %s\n"), + unaddr->sun_path, strerror (errno)); + + if (listen (FD2INT (fd), listen_backlog) == -1) + { + log_error ("listen(fd, %d) failed: %s\n", + listen_backlog, gpg_strerror (gpg_error_from_syserror ())); + assuan_sock_close (fd); + tpm2d_exit (2); + } + + if (opt.verbose) + log_info (_("listening on socket '%s'\n"), unaddr->sun_path); + + return fd; +} + + + +/* This is the standard connection thread's main function. */ +static void * +start_connection_thread (void *arg) +{ + ctrl_t ctrl = arg; + + if (ctrl->thread_startup.fd != GNUPG_INVALID_FD + && assuan_sock_check_nonce (ctrl->thread_startup.fd, &socket_nonce)) + { + log_info (_("error reading nonce on fd %d: %s\n"), + FD2INT (ctrl->thread_startup.fd), strerror (errno)); + assuan_sock_close (ctrl->thread_startup.fd); + xfree (ctrl); + return NULL; + } + + active_connections++; + + tpm2d_init_default_ctrl (ctrl); + if (opt.verbose) + log_info (_("handler for fd %d started\n"), + FD2INT (ctrl->thread_startup.fd)); + + /* If this is a pipe server, we request a shutdown if the command + handler asked for it. With the next ticker event and given that + no other connections are running the shutdown will then + happen. */ + if (tpm2d_command_handler (ctrl, FD2INT (ctrl->thread_startup.fd)) + && pipe_server) + shutdown_pending = 1; + + if (opt.verbose) + log_info (_("handler for fd %d terminated\n"), + FD2INT (ctrl->thread_startup.fd)); + + tpm2d_deinit_default_ctrl (ctrl); + xfree (ctrl); + + if (--active_connections == 0) + tpm2d_kick_the_loop (); + + return NULL; +} + + +void +tpm2d_kick_the_loop (void) +{ +#ifdef HAVE_W32_SYSTEM + int ret; + + /* Kick the select loop. */ + ret = SetEvent (the_event); + if (ret == 0) + log_error ("SetEvent for tpm2d_kick_the_loop failed: %s\n", + w32_strerror (-1)); +#elif defined(HAVE_PSELECT_NO_EINTR) + write (notify_fd, "", 1); +#else + int ret; + + ret = kill (main_thread_pid, SIGCONT); + if (ret < 0) + log_error ("SetEvent for tpm2d_kick_the_loop failed: %s\n", + gpg_strerror (gpg_error_from_syserror ())); +#endif +} + +/* Connection handler loop. Wait for connection requests and spawn a + thread after accepting a connection. LISTEN_FD is allowed to be -1 + in which case this code will only do regular timeouts and handle + signals. */ +static void +handle_connections (int listen_fd) +{ + npth_attr_t tattr; + struct sockaddr_un paddr; + socklen_t plen; + fd_set fdset, read_fdset; + int nfd; + int ret; + int fd; + struct timespec timeout; + struct timespec *t; + int saved_errno; +#ifdef HAVE_W32_SYSTEM + HANDLE events[2]; + unsigned int events_set; +#else + int signo; +#endif +#ifdef HAVE_PSELECT_NO_EINTR + int pipe_fd[2]; + + ret = gnupg_create_pipe (pipe_fd); + if (ret) + { + log_error ("pipe creation failed: %s\n", gpg_strerror (ret)); + return; + } + notify_fd = pipe_fd[1]; +#endif + + ret = npth_attr_init (&tattr); + if (ret) + { + log_error ("npth_attr_init failed: %s\n", strerror (ret)); + return; + } + + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + +#ifdef HAVE_W32_SYSTEM + { + HANDLE h, h2; + SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE}; + + events[0] = the_event = INVALID_HANDLE_VALUE; + events[1] = INVALID_HANDLE_VALUE; + h = CreateEvent (&sa, TRUE, FALSE, NULL); + if (!h) + log_error ("can't create tpm2d event: %s\n", w32_strerror (-1) ); + else if (!DuplicateHandle (GetCurrentProcess (), h, + GetCurrentProcess (), &h2, + EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0)) + { + log_error ("setting synchronize for tpm2d_kick_the_loop failed: %s\n", + w32_strerror (-1) ); + CloseHandle (h); + } + else + { + CloseHandle (h); + events[0] = the_event = h2; + } + } +#else + npth_sigev_init (); + npth_sigev_add (SIGHUP); + npth_sigev_add (SIGUSR1); + npth_sigev_add (SIGUSR2); + npth_sigev_add (SIGINT); + npth_sigev_add (SIGCONT); + npth_sigev_add (SIGTERM); + npth_sigev_fini (); + main_thread_pid = getpid (); +#endif + + FD_ZERO (&fdset); + nfd = 0; + if (listen_fd != -1) + { + FD_SET (listen_fd, &fdset); + nfd = listen_fd; + } + + for (;;) + { + int periodical_check; + int max_fd = nfd; + + if (shutdown_pending) + { + if (active_connections == 0) + break; /* ready */ + + /* Do not accept anymore connections but wait for existing + connections to terminate. We do this by clearing out all + file descriptors to wait for, so that the select will be + used to just wait on a signal or timeout event. */ + FD_ZERO (&fdset); + listen_fd = -1; + } + + periodical_check = 0; + + timeout.tv_sec = TIMERTICK_INTERVAL_SEC; + timeout.tv_nsec = TIMERTICK_INTERVAL_USEC * 1000; + + if (shutdown_pending || periodical_check) + t = &timeout; + else + t = NULL; + + /* POSIX says that fd_set should be implemented as a structure, + thus a simple assignment is fine to copy the entire set. */ + read_fdset = fdset; + +#ifdef HAVE_PSELECT_NO_EINTR + FD_SET (pipe_fd[0], &read_fdset); + if (max_fd < pipe_fd[0]) + max_fd = pipe_fd[0]; +#endif + +#ifndef HAVE_W32_SYSTEM + ret = npth_pselect (max_fd+1, &read_fdset, NULL, NULL, t, + npth_sigev_sigmask ()); + saved_errno = errno; + + while (npth_sigev_get_pending (&signo)) + handle_signal (signo); +#else + ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, t, + events, &events_set); + saved_errno = errno; + if (events_set & 1) + continue; +#endif + + if (ret == -1 && saved_errno != EINTR) + { + log_error (_("npth_pselect failed: %s - waiting 1s\n"), + strerror (saved_errno)); + npth_sleep (1); + continue; + } + + if (ret <= 0) + /* Timeout. Will be handled when calculating the next timeout. */ + continue; + +#ifdef HAVE_PSELECT_NO_EINTR + if (FD_ISSET (pipe_fd[0], &read_fdset)) + { + char buf[256]; + + read (pipe_fd[0], buf, sizeof buf); + } +#endif + + if (listen_fd != -1 && FD_ISSET (listen_fd, &read_fdset)) + { + ctrl_t ctrl; + + plen = sizeof paddr; + fd = npth_accept (listen_fd, (struct sockaddr *)&paddr, &plen); + if (fd == -1) + { + log_error ("accept failed: %s\n", strerror (errno)); + } + else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) ) + { + log_error ("error allocating connection control data: %s\n", + strerror (errno) ); + close (fd); + } + else + { + char threadname[50]; + npth_t thread; + + snprintf (threadname, sizeof threadname, "conn fd=%d", fd); + ctrl->thread_startup.fd = INT2FD (fd); + ret = npth_create (&thread, &tattr, start_connection_thread, ctrl); + if (ret) + { + log_error ("error spawning connection handler: %s\n", + strerror (ret)); + xfree (ctrl); + close (fd); + } + else + npth_setname_np (thread, threadname); + } + } + } + +#ifdef HAVE_W32_SYSTEM + if (the_event != INVALID_HANDLE_VALUE) + CloseHandle (the_event); +#endif +#ifdef HAVE_PSELECT_NO_EINTR + close (pipe_fd[0]); + close (pipe_fd[1]); +#endif + cleanup (); + log_info (_("%s %s stopped\n"), gpgrt_strusage (11), gpgrt_strusage (13)); + npth_attr_destroy (&tattr); +} + +/* Return the number of active connections. */ +int +get_active_connection_count (void) +{ + return active_connections; +} diff --git a/tpm2d/tpm2daemon.h b/tpm2d/tpm2daemon.h new file mode 100644 index 000000000..24d56a8dc --- /dev/null +++ b/tpm2d/tpm2daemon.h @@ -0,0 +1,98 @@ +/* tpm2daemon.h - Global definitions for the SCdaemon + * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG 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. + * + * GnuPG 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 this program; if not, see <https://www.gnu.org/licenses/>. + */ + +#ifndef TPM2DAEMON_H +#define TPM2DAEMON_H + +#ifdef GPG_ERR_SOURCE_DEFAULT +#error GPG_ERR_SOURCE_DEFAULT already defined +#endif +/* FIXME: Replace this hard coded value as soon as we require a newer + * libgpg-error. */ +#define GPG_ERR_SOURCE_DEFAULT 16 /* GPG_ERR_SOURCE_TPM2 */ +#include <gpg-error.h> + +#include <time.h> +#include <gcrypt.h> +#include "../common/util.h" +#include "../common/sysutils.h" + +/* Maximum length of a digest. */ +#define MAX_DIGEST_LEN 64 + + + +/* A large struct name "opt" to keep global flags. */ +EXTERN_UNLESS_MAIN_MODULE +struct +{ + unsigned int debug; /* Debug flags (DBG_foo_VALUE). */ + int verbose; /* Verbosity level. */ + int quiet; /* Be as quiet as possible. */ + unsigned long parent; /* TPM parent */ +} opt; + + +#define DBG_MPI_VALUE 2 /* debug mpi details */ +#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ +#define DBG_IPC_VALUE 1024 + +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) +#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) + +struct server_local_s; + +struct server_control_s +{ + /* Private data used to fire up the connection thread. We use this + structure do avoid an extra allocation for just a few bytes. */ + struct { + gnupg_fd_t fd; + } thread_startup; + + /* Local data of the server; used only in command.c. */ + struct server_local_s *server_local; + + /* The application context used with this connection or NULL if none + associated. Note that this is shared with the other connections: + All connections accessing the same reader are using the same + application context. */ + struct assuan_context_s *ctx; + + /* Helper to store the value we are going to sign */ + struct + { + unsigned char *value; + int valuelen; + } in_data; +}; + +typedef struct app_ctx_s *app_t; + +/*-- tpm2daemon.c --*/ +void tpm2d_exit (int rc); + +/*-- command.c --*/ +gpg_error_t initialize_module_command (void); +int tpm2d_command_handler (ctrl_t, int); +void send_client_notifications (app_t app, int removal); +void tpm2d_kick_the_loop (void); +int get_active_connection_count (void); + +#endif /*TPM2DAEMON_H*/ |