From 62a7854816b8f3661fb41f05463289e5b96663ee Mon Sep 17 00:00:00 2001 From: James Bottomley Date: Tue, 9 Mar 2021 13:50:28 -0800 Subject: [PATCH] tpm2d: Add tpm2daemon code * tpm2d: New directory. * Makefile.am (SUBDIRS): Add directory. * configure.ac: Detect libtss and decide whether to build tpm2d. * am/cmacros.am: Add a define. * util.h (GNUPG_MODULE_NAME_TPM2DAEMON): New. * common/homedir.c (gnupg_module_name): Add tpm2d. * common/mapstrings.c (macros): Add "TPM2DAEMON". * tools/gpgconf.h (GC_COMPONENT_TPM2DAEMON): New. * tools/gpgconf-comp.c (known_options_tpm2daemon): New. (gc_component): Add TPM2. (tpm2daemon_runtime_change): New. * tpm2d/Makefile.am: New. * tpm2d/command.c: New. * tpm2d/ibm-tss.h: New. * tpm2d/tpm2.c: New. * tpm2d/tpm2.h: New. * tpm2d/tpm2daemon.c: New. * tpm2d/tpm2daemon.h: New. --- This commit adds and plumbs in a tpm2daemon to the build to mirror the operation of scdaemon. The architecture of the code is that tpm2daemon.c itself is pretty much a clone of scd/scdaemon.c just with updated function prefixes (this argues there could be some further consolidation of the daemon handling code). Note that although this commit causes the daemon to be built and installed, nothing actually starts it or uses it yet. Command handling ---------------- command.c is copied from the command handler in scd.c except that the command implementation is now done in terms of tpm2 commands and the wire protocol is far simpler. The tpm2daemon only responds to 4 commands IMPORT: import a standard s-expression private key and export it to TPM2 format. This conversion cannot be undone and the private key now can *only* be used by the TPM2. To anyone who gets hold of the private key now, it's just an encrypted binary blob. PKSIGN: create a signature from the tpm2 key. The TPM2 form private key is retrieved by KEYDATA and the hash to be signed by EXTRA. Note there is no hash specifier because the tpm2 tss deduces the hash type from the length of the EXTRA data. This is actually a limitation of the tpm2 command API and it will be interesting to see how this fares if the tpm2 ever supports say sha3-256 hashes. PKDECRYPT: decrypt (RSA case) or derive (ECC case) a symmetric key. The tpm2 for private key is retrieved by KEYDATA and the information used to create the symmetric key by EXTRA. KILLTPM2D: stop the daemon All the tpm2 primitives used by command.c are in tpm2.h and all the tpm2 specific gunk is confined to tpm2.c, which is the only piece of this that actually does calls into the tss library. Signed-off-by: James Bottomley Changes from James' patch: - gpgconf: The displayed name is "TPM" and not "TPM2". That string is used by GUIs and should be something the user understands. For example we also use "network" instead of "Dirmngr". - Removed some commented includes. - Use 16 as emulation of GPG_ERR_SOURCE_TPM2. - Silenced a C90 compiler warning and flags unused parameters. - Removed "if HAVE_LIBS" from tpm2/Makefile.am and add missing files so that make distcheck works. Signed-off-by: Werner Koch --- Makefile.am | 7 +- am/cmacros.am | 3 + common/homedir.c | 7 + common/mapstrings.c | 1 + common/util.h | 1 + configure.ac | 46 ++ tools/gpgconf-comp.c | 62 +- tools/gpgconf.h | 3 + tpm2d/Makefile.am | 18 + tpm2d/command.c | 504 +++++++++++++++++ tpm2d/ibm-tss.h | 381 +++++++++++++ tpm2d/tpm2.c | 987 ++++++++++++++++++++++++++++++++ tpm2d/tpm2.h | 34 ++ tpm2d/tpm2daemon.c | 1289 ++++++++++++++++++++++++++++++++++++++++++ tpm2d/tpm2daemon.h | 98 ++++ 15 files changed, 3439 insertions(+), 2 deletions(-) create mode 100644 tpm2d/Makefile.am create mode 100644 tpm2d/command.c create mode 100644 tpm2d/ibm-tss.h create mode 100644 tpm2d/tpm2.c create mode 100644 tpm2d/tpm2.h create mode 100644 tpm2d/tpm2daemon.c create mode 100644 tpm2d/tpm2daemon.h 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="" ) @@ -1580,6 +1590,33 @@ fi 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 # @@ -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 <. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_NPTH +# include +#endif + +#include "tpm2daemon.h" +#include "tpm2.h" +#include +#include +#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 + * + * 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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 . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef HAVE_W32_SYSTEM +#include +#include +#endif /*HAVE_W32_SYSTEM*/ +#include +#include +#include + +#define INCLUDED_BY_MAIN_MODULE 1 +#define GNUPG_COMMON_NEED_AFLOCAL +#include "tpm2daemon.h" +#include + +#include /* 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: :: */ + 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 . + */ + +#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 + +#include +#include +#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*/