1
0
mirror of git://git.gnupg.org/gnupg.git synced 2024-12-22 10:19:57 +01:00

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 <James.Bottomley@HansenPartnership.com>

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 <wk@gnupg.org>
This commit is contained in:
James Bottomley 2021-03-09 13:50:28 -08:00 committed by Werner Koch
parent 0f191a0707
commit 62a7854816
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
15 changed files with 3439 additions and 2 deletions

View File

@ -106,10 +106,15 @@ tests =
else else
tests = tests tests = tests
endif endif
if HAVE_LIBTSS
tpm2d = tpm2d
else
tpm2d =
endif
SUBDIRS = m4 common regexp kbx \ SUBDIRS = m4 common regexp kbx \
${gpg} ${sm} ${agent} ${scd} ${g13} ${dirmngr} \ ${gpg} ${sm} ${agent} ${scd} ${g13} ${dirmngr} \
tools po ${doc} ${tests} tools po ${doc} ${tests} ${tpm2d}
dist_doc_DATA = README dist_doc_DATA = README

View File

@ -44,6 +44,9 @@ endif
if GNUPG_SCDAEMON_PGM if GNUPG_SCDAEMON_PGM
AM_CPPFLAGS += -DGNUPG_DEFAULT_SCDAEMON="\"@GNUPG_SCDAEMON_PGM@\"" AM_CPPFLAGS += -DGNUPG_DEFAULT_SCDAEMON="\"@GNUPG_SCDAEMON_PGM@\""
endif endif
if GNUPG_TPM2DAEMON_PGM
AM_CPPFLAGS += -DGNUPG_DEFAULT_TPM2DAEMON="\"@GNUPG_TPM2DAEMON_PGM@\""
endif
if GNUPG_DIRMNGR_PGM if GNUPG_DIRMNGR_PGM
AM_CPPFLAGS += -DGNUPG_DEFAULT_DIRMNGR="\"@GNUPG_DIRMNGR_PGM@\"" AM_CPPFLAGS += -DGNUPG_DEFAULT_DIRMNGR="\"@GNUPG_DIRMNGR_PGM@\""
endif endif

View File

@ -1153,6 +1153,13 @@ gnupg_module_name (int which)
X(libexecdir, "scd", "scdaemon"); X(libexecdir, "scd", "scdaemon");
#endif #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: case GNUPG_MODULE_NAME_DIRMNGR:
#ifdef GNUPG_DEFAULT_DIRMNGR #ifdef GNUPG_DEFAULT_DIRMNGR
return GNUPG_DEFAULT_DIRMNGR; return GNUPG_DEFAULT_DIRMNGR;

View File

@ -50,6 +50,7 @@ static struct {
{ "GPGSM", GPGSM_NAME }, { "GPGSM", GPGSM_NAME },
{ "GPG_AGENT", GPG_AGENT_NAME }, { "GPG_AGENT", GPG_AGENT_NAME },
{ "SCDAEMON", SCDAEMON_NAME }, { "SCDAEMON", SCDAEMON_NAME },
{ "TPM2DAEMON",TPM2DAEMON_NAME},
{ "DIRMNGR", DIRMNGR_NAME }, { "DIRMNGR", DIRMNGR_NAME },
{ "G13", G13_NAME }, { "G13", G13_NAME },
{ "GPGCONF", GPGCONF_NAME }, { "GPGCONF", GPGCONF_NAME },

View File

@ -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_DIRMNGR_LDAP 11
#define GNUPG_MODULE_NAME_GPGV 12 #define GNUPG_MODULE_NAME_GPGV 12
#define GNUPG_MODULE_NAME_KEYBOXD 13 #define GNUPG_MODULE_NAME_KEYBOXD 13
#define GNUPG_MODULE_NAME_TPM2DAEMON 14
const char *gnupg_module_name (int which); const char *gnupg_module_name (int which);
void gnupg_module_name_flush_some (void); void gnupg_module_name_flush_some (void);
void gnupg_set_builddir (const char *newdir); void gnupg_set_builddir (const char *newdir);

View File

@ -101,6 +101,7 @@ have_gnutls=no
have_sqlite=no have_sqlite=no
have_npth=no have_npth=no
have_libusb=no have_libusb=no
have_libtss=no
have_system_resolver=no have_system_resolver=no
gnupg_have_ldap="n/a" 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" 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, AC_ARG_WITH(dirmngr-pgm,
[ --with-dirmngr-pgm=PATH Use PATH as the default for the dirmngr)], [ --with-dirmngr-pgm=PATH Use PATH as the default for the dirmngr)],
GNUPG_DIRMNGR_PGM="$withval", GNUPG_DIRMNGR_PGM="" ) GNUPG_DIRMNGR_PGM="$withval", GNUPG_DIRMNGR_PGM="" )
@ -1580,6 +1590,33 @@ fi
AC_SUBST(NETLIBS) AC_SUBST(NETLIBS)
AC_SUBST(W32SOCKLIBS) 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 # 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", AC_DEFINE_UNQUOTED(GPG_AGENT_DISP_NAME, "GPG Agent",
[The displayed name of 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_NAME, "scdaemon", [The name of the scdaemon])
AC_DEFINE_UNQUOTED(SCDAEMON_DISP_NAME, "SCDaemon", AC_DEFINE_UNQUOTED(SCDAEMON_DISP_NAME, "SCDaemon",
[The displayed name of scdaemon]) [The displayed name of scdaemon])
@ -1880,6 +1921,8 @@ AC_DEFINE_UNQUOTED(SCDAEMON_SOCK_NAME, "S.scdaemon",
[The name of the SCdaemon socket]) [The name of the SCdaemon socket])
AC_DEFINE_UNQUOTED(KEYBOXD_SOCK_NAME, "S.keyboxd", AC_DEFINE_UNQUOTED(KEYBOXD_SOCK_NAME, "S.keyboxd",
[The name of the keyboxd socket]) [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", AC_DEFINE_UNQUOTED(DIRMNGR_SOCK_NAME, "S.dirmngr",
[The name of the dirmngr socket]) [The name of the dirmngr socket])
AC_DEFINE_UNQUOTED(DIRMNGR_DEFAULT_KEYSERVER, AC_DEFINE_UNQUOTED(DIRMNGR_DEFAULT_KEYSERVER,
@ -2040,6 +2083,7 @@ g10/Makefile
sm/Makefile sm/Makefile
agent/Makefile agent/Makefile
scd/Makefile scd/Makefile
tpm2d/Makefile
g13/Makefile g13/Makefile
dirmngr/Makefile dirmngr/Makefile
tools/Makefile tools/Makefile
@ -2086,6 +2130,7 @@ echo "
Default pinentry: $show_gnupg_pinentry_pgm Default pinentry: $show_gnupg_pinentry_pgm
Default scdaemon: $show_gnupg_scdaemon_pgm Default scdaemon: $show_gnupg_scdaemon_pgm
Default keyboxd: $show_gnupg_keyboxd_pgm Default keyboxd: $show_gnupg_keyboxd_pgm
Default tpm2daemon: $show_gnupg_tpm2daemon_pgm
Default dirmngr: $show_gnupg_dirmngr_pgm Default dirmngr: $show_gnupg_dirmngr_pgm
Dirmngr auto start: $dirmngr_auto_start Dirmngr auto start: $dirmngr_auto_start
@ -2094,6 +2139,7 @@ echo "
TLS support: $use_tls_library TLS support: $use_tls_library
TOFU support: $use_tofu TOFU support: $use_tofu
Tor support: $show_tor_support Tor support: $show_tor_support
TPM support: $have_libtss
" "
if test "x${gpg_config_script_warn}" != x; then if test "x${gpg_config_script_warn}" != x; then
cat <<G10EOF cat <<G10EOF

View File

@ -92,11 +92,11 @@ gc_error (int status, int errnum, const char *fmt, ...)
/* Forward declaration. */ /* Forward declaration. */
static void gpg_agent_runtime_change (int killflag); static void gpg_agent_runtime_change (int killflag);
static void scdaemon_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 dirmngr_runtime_change (int killflag);
static void keyboxd_runtime_change (int killflag); static void keyboxd_runtime_change (int killflag);
/* STRING_ARRAY is a malloced array with malloced strings. It is used /* 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 * 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 } { 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. */ /* The known options of the GC_COMPONENT_GPG component. */
static known_option_t known_options_gpg[] = static known_option_t known_options_gpg[] =
@ -627,6 +642,10 @@ static struct
GNUPG_MODULE_NAME_SCDAEMON, SCDAEMON_NAME ".conf", GNUPG_MODULE_NAME_SCDAEMON, SCDAEMON_NAME ".conf",
known_options_scdaemon, scdaemon_runtime_change}, 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"), { DIRMNGR_NAME, DIRMNGR_DISP_NAME, "gnupg", N_("Network"),
GNUPG_MODULE_NAME_DIRMNGR, DIRMNGR_NAME ".conf", GNUPG_MODULE_NAME_DIRMNGR, DIRMNGR_NAME ".conf",
known_options_dirmngr, dirmngr_runtime_change }, known_options_dirmngr, dirmngr_runtime_change },
@ -753,6 +772,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 static void
dirmngr_runtime_change (int killflag) dirmngr_runtime_change (int killflag)
{ {

View File

@ -67,6 +67,9 @@ typedef enum
/* The Smardcard Daemon. */ /* The Smardcard Daemon. */
GC_COMPONENT_SCDAEMON, GC_COMPONENT_SCDAEMON,
/* The TPM2 Daemon. */
GC_COMPONENT_TPM2DAEMON,
/* The LDAP Directory Manager for CRLs. */ /* The LDAP Directory Manager for CRLs. */
GC_COMPONENT_DIRMNGR, GC_COMPONENT_DIRMNGR,

18
tpm2d/Makefile.am Normal file
View File

@ -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)

504
tpm2d/command.c Normal file
View File

@ -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;
}

381
tpm2d/ibm-tss.h Normal file
View File

@ -0,0 +1,381 @@
/*
* Copyright (C) 2021 James Bottomley <James.Bottomley@HansenPartnership.com>
*
* 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

987
tpm2d/tpm2.c Normal file
View File

@ -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;
}

34
tpm2d/tpm2.h Normal file
View File

@ -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

1289
tpm2d/tpm2daemon.c Normal file

File diff suppressed because it is too large Load Diff

98
tpm2d/tpm2daemon.h Normal file
View File

@ -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*/