Browse Source

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>
gniibe/pcsc-get-status
James Bottomley 7 months ago
committed by Werner Koch
parent
commit
62a7854816
No known key found for this signature in database GPG Key ID: E3FDFF218E45B72B
  1. 7
      Makefile.am
  2. 3
      am/cmacros.am
  3. 7
      common/homedir.c
  4. 1
      common/mapstrings.c
  5. 1
      common/util.h
  6. 46
      configure.ac
  7. 62
      tools/gpgconf-comp.c
  8. 3
      tools/gpgconf.h
  9. 18
      tpm2d/Makefile.am
  10. 504
      tpm2d/command.c
  11. 381
      tpm2d/ibm-tss.h
  12. 987
      tpm2d/tpm2.c
  13. 34
      tpm2d/tpm2.h
  14. 1289
      tpm2d/tpm2daemon.c
  15. 98
      tpm2d/tpm2daemon.h

7
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

3
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

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

1
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 },

1
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);

46
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 <<G10EOF

62
tools/gpgconf-comp.c

@ -92,11 +92,11 @@ gc_error (int status, int errnum, const char *fmt, ...)
/* Forward declaration. */
static void gpg_agent_runtime_change (int killflag);
static void scdaemon_runtime_change (int killflag);
static void tpm2daemon_runtime_change (int killflag);
static void dirmngr_runtime_change (int killflag);
static void keyboxd_runtime_change (int killflag);
/* STRING_ARRAY is a malloced array with malloced strings. It is used
* a space to store strings so that other objects may point to these
@ -378,6 +378,21 @@ static known_option_t known_options_scdaemon[] =
{ NULL }
};
/* The known options of the GC_COMPONENT_TPM2DAEMON component. */
static known_option_t known_options_tpm2daemon[] =
{
{ "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED},
{ "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
GC_ARG_TYPE_FILENAME },
{ "deny-admin", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC },
{ "parent", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED },
{ NULL }
};
/* The known options of the GC_COMPONENT_GPG component. */
static known_option_t known_options_gpg[] =
@ -627,6 +642,10 @@ static struct
GNUPG_MODULE_NAME_SCDAEMON, SCDAEMON_NAME ".conf",
known_options_scdaemon, scdaemon_runtime_change},
{ TPM2DAEMON_NAME, TPM2DAEMON_DISP_NAME, "gnupg", N_("TPM"),
GNUPG_MODULE_NAME_TPM2DAEMON, TPM2DAEMON_NAME ".conf",
known_options_tpm2daemon, tpm2daemon_runtime_change},
{ DIRMNGR_NAME, DIRMNGR_DISP_NAME, "gnupg", N_("Network"),
GNUPG_MODULE_NAME_DIRMNGR, DIRMNGR_NAME ".conf",
known_options_dirmngr, dirmngr_runtime_change },
@ -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
dirmngr_runtime_change (int killflag)
{

3
tools/gpgconf.h

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

18
tpm2d/Makefile.am

@ -0,0 +1,18 @@
AM_CPPFLAGS =
include $(top_srcdir)/am/cmacros.am
libexec_PROGRAMS = tpm2daemon
AM_CFLAGS = $(LIBGCRYPT_CFLAGS) \
$(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) $(LIBTSS_CFLAGS)
tpm2daemon_SOURCES = \
command.c \
tpm2daemon.c \
tpm2.c tpm2.h \
tpm2daemon.h ibm-tss.h
tpm2daemon_LDADD = $(libcommonpth) \
$(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \
$(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV) $(LIBTSS_LIBS)

504
tpm2d/command.c

@ -0,0 +1,504 @@
/* command.c - TPM2daemon command handler
* Copyright (C) 2001, 2002, 2003, 2004, 2005,
* 2007, 2008, 2009, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#ifdef USE_NPTH
# include <npth.h>
#endif
#include "tpm2daemon.h"
#include "tpm2.h"
#include <assuan.h>
#include <ksba.h>
#include "../common/asshelp.h"
#include "../common/server-help.h"
/* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN */
#define MAXLEN_PIN 100
/* Maximum allowed size of key data as used in inquiries. */
#define MAXLEN_KEYDATA 4096
/* Maximum allowed total data size for SETDATA. */
#define MAXLEN_SETDATA 4096
/* Maximum allowed size of certificate data as used in inquiries. */
#define MAXLEN_CERTDATA 16384
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
/* Data used to associate an Assuan context with local server data.
This object describes the local properties of one session. */
struct server_local_s
{
/* We keep a list of all active sessions with the anchor at
SESSION_LIST (see below). This field is used for linking. */
struct server_local_s *next_session;
/* This object is usually assigned to a CTRL object (which is
globally visible). While enumerating all sessions we sometimes
need to access data of the CTRL object; thus we keep a
backpointer here. */
ctrl_t ctrl_backlink;
/* The Assuan context used by this session/server. */
assuan_context_t assuan_ctx;
#ifdef HAVE_W32_SYSTEM
unsigned long event_signal; /* Or 0 if not used. */
#else
int event_signal; /* Or 0 if not used. */
#endif
/* True if the card has been removed and a reset is required to
continue operation. */
int card_removed;
/* If set to true we will be terminate ourself at the end of the
this session. */
int stopme;
};
/* To keep track of all running sessions, we link all active server
contexts and the anchor in this variable. */
static struct server_local_s *session_list;
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
(void) ctx;
(void) line;
return 0;
}
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
if (!strcmp (key, "event-signal"))
{
/* A value of 0 is allowed to reset the event signal. */
#ifdef HAVE_W32_SYSTEM
if (!*value)
return gpg_error (GPG_ERR_ASS_PARAMETER);
ctrl->server_local->event_signal = strtoul (value, NULL, 16);
#else
int i = *value? atoi (value) : -1;
if (i < 0)
return gpg_error (GPG_ERR_ASS_PARAMETER);
ctrl->server_local->event_signal = i;
#endif
}
return 0;
}
static gpg_error_t
pin_cb (ctrl_t ctrl, const char *info, char **retstr)
{
assuan_context_t ctx = ctrl->ctx;
char *command;
int rc;
unsigned char *value;
size_t valuelen;
*retstr = NULL;
log_debug ("asking for PIN '%s'\n", info);
rc = gpgrt_asprintf (&command, "NEEDPIN %s", info);
if (rc < 0)
return gpg_error (gpg_err_code_from_errno (errno));
/* Fixme: Write an inquire function which returns the result in
secure memory and check all further handling of the PIN. */
rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN);
xfree (command);
if (rc)
return rc;
if (!valuelen)
{
/* We require that the returned value is an UTF-8 string */
xfree (value);
return gpg_error (GPG_ERR_INV_RESPONSE);
}
*retstr = (char*)value;
return 0;
}
static const char hlp_import[] =
"IMPORT\n"
"\n"
"This command is used to convert a public and secret key to tpm format.\n"
"keydata is communicated via an inquire KEYDATA command\n"
"The keydata is expected to be the usual canonical encoded\n"
"S-expression. The return will be a TPM format S-expression\n"
"\n"
"A PIN will be requested.";
static gpg_error_t
cmd_import (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *keydata;
size_t keydatalen;
TSS_CONTEXT *tssc;
gcry_sexp_t s_key;
unsigned char *shadow_info = NULL;
size_t shadow_len;
line = skip_options (line);
if (*line)
return set_error (GPG_ERR_ASS_PARAMETER, "additional parameters given");
/* Now get the actual keydata. */
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, "KEYDATA", &keydata, &keydatalen, MAXLEN_KEYDATA);
assuan_end_confidential (ctx);
if (rc)
return rc;
if ((rc = tpm2_start (&tssc)))
goto out;
gcry_sexp_new (&s_key, keydata, keydatalen, 0);
rc = tpm2_import_key (ctrl, tssc, pin_cb, &shadow_info, &shadow_len,
s_key, opt.parent);
gcry_sexp_release (s_key);
tpm2_end (tssc);
if (rc)
goto out;
rc = assuan_send_data (ctx, shadow_info, shadow_len);
out:
xfree (shadow_info);
xfree (keydata);
return rc;
}
static const char hlp_pksign[] =
"PKSIGN\n"
"\n"
"Get the TPM to produce a signature. KEYDATA will request the TPM\n"
"form S-expression (returned by IMPORT) and EXTRA will be the hash\n"
"to sign. The TPM currently deduces hash type from length.\n"
"\n"
"A PIN will be requested.";
static gpg_error_t
cmd_pksign (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *shadow_info;
size_t len;
TSS_CONTEXT *tssc;
TPM_HANDLE key;
TPMI_ALG_PUBLIC type;
unsigned char *digest;
size_t digestlen;
unsigned char *sig;
size_t siglen;
line = skip_options (line);
if (*line)
return set_error (GPG_ERR_ASS_PARAMETER, "additional parameters given");
/* Now get the actual keydata. */
rc = assuan_inquire (ctx, "KEYDATA", &shadow_info, &len, MAXLEN_KEYDATA);
if (rc)
return rc;
rc = assuan_inquire (ctx, "EXTRA", &digest, &digestlen, MAXLEN_KEYDATA);
if (rc)
goto out_freeshadow;
rc = tpm2_start (&tssc);
if (rc)
goto out;
rc = tpm2_load_key (tssc, shadow_info, &key, &type);
if (rc)
goto end_out;
rc = tpm2_sign (ctrl, tssc, key, pin_cb, type, digest, digestlen,
&sig, &siglen);
tpm2_flush_handle (tssc, key);
end_out:
tpm2_end (tssc);
if (rc)
goto out;
rc = assuan_send_data (ctx, sig, siglen);
xfree (sig);
out:
xfree (digest);
out_freeshadow:
xfree (shadow_info);
return rc;
}
static const char hlp_pkdecrypt[] =
"PKDECRYPT\n"
"Get the TPM to recover a symmetric key. KEYDATA will request the TPM\n"
"form S-expression (returned by IMPORT) and EXTRA will be the input\n"
"to derive or decrypt. The return will be the symmetric key\n"
"\n"
"\n"
"A PIN will be requested.";
static gpg_error_t
cmd_pkdecrypt (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *shadow_info;
size_t len;
TSS_CONTEXT *tssc;
TPM_HANDLE key;
TPMI_ALG_PUBLIC type;
unsigned char *crypto;
size_t cryptolen;
char *buf;
size_t buflen;
line = skip_options (line);
if (*line)
return set_error (GPG_ERR_ASS_PARAMETER, "additional parameters given");
/* Now get the actual keydata. */
rc = assuan_inquire (ctx, "KEYDATA", &shadow_info, &len, MAXLEN_KEYDATA);
if (rc)
return rc;
rc = assuan_inquire (ctx, "EXTRA", &crypto, &cryptolen, MAXLEN_KEYDATA);
if (rc)
goto out_freeshadow;
rc = tpm2_start (&tssc);
if (rc)
goto out;
rc = tpm2_load_key (tssc, shadow_info, &key, &type);
if (rc)
goto end_out;
if (type == TPM_ALG_RSA)
rc = tpm2_rsa_decrypt (ctrl, tssc, key, pin_cb, crypto,
cryptolen, &buf, &buflen);
else if (type == TPM_ALG_ECC)
rc = tpm2_ecc_decrypt (ctrl, tssc, key, pin_cb, crypto,
cryptolen, &buf, &buflen);
tpm2_flush_handle (tssc, key);
end_out:
tpm2_end (tssc);
if (rc)
goto out;
rc = assuan_send_data (ctx, buf, buflen);
xfree (buf);
out:
xfree (crypto);
out_freeshadow:
xfree (shadow_info);
return rc;
}
static const char hlp_killtpm2d[] =
"KILLTPM2D\n"
"\n"
"Commit suicide.";
static gpg_error_t
cmd_killtpm2d (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return 0;
}
/* Tell the assuan library about our commands */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "IMPORT", cmd_import, hlp_import },
{ "PKSIGN", cmd_pksign, hlp_pksign },
{ "PKDECRYPT", cmd_pkdecrypt, hlp_pkdecrypt },
{ "KILLTPM2D", cmd_killtpm2d, hlp_killtpm2d },
{ NULL }
};
int i, rc;
for (i=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
assuan_set_hello_line (ctx, "GNU Privacy Guard's TPM2 server ready");
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_option_handler (ctx, option_handler);
return 0;
}
/* Startup the server. If FD is given as -1 this is simple pipe
server, otherwise it is a regular server. Returns true if there
are no more active asessions. */
int
tpm2d_command_handler (ctrl_t ctrl, int fd)
{
int rc;
assuan_context_t ctx = NULL;
int stopme;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("failed to allocate assuan context: %s\n",
gpg_strerror (rc));
tpm2d_exit (2);
}
if (fd == -1)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else
{
rc = assuan_init_socket_server (ctx, INT2FD (fd),
ASSUAN_SOCKET_SERVER_ACCEPTED);
}
if (rc)
{
log_error ("failed to initialize the server: %s\n",
gpg_strerror (rc));
tpm2d_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error ("failed to register commands with Assuan: %s\n",
gpg_strerror (rc));
tpm2d_exit (2);
}
assuan_set_pointer (ctx, ctrl);
ctrl->ctx = ctx;
/* Allocate and initialize the server object. Put it into the list
of active sessions. */
ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local);
ctrl->server_local->next_session = session_list;
session_list = ctrl->server_local;
ctrl->server_local->ctrl_backlink = ctrl;
ctrl->server_local->assuan_ctx = ctx;
/* Command processing loop. */
for (;;)
{
rc = assuan_accept (ctx);
if (rc == -1)
{
break;
}
else if (rc)
{
log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
break;
}
rc = assuan_process (ctx);
if (rc)
{
log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
continue;
}
}
/* Release the server object. */
if (session_list == ctrl->server_local)
session_list = ctrl->server_local->next_session;
else
{
struct server_local_s *sl;
for (sl=session_list; sl->next_session; sl = sl->next_session)
if (sl->next_session == ctrl->server_local)
break;
if (!sl->next_session)
BUG ();
sl->next_session = ctrl->server_local->next_session;
}
stopme = ctrl->server_local->stopme;
xfree (ctrl->server_local);
ctrl->server_local = NULL;
/* Release the Assuan context. */
assuan_release (ctx);
if (stopme)
tpm2d_exit (0);
/* If there are no more sessions return true. */
return !session_list;
}

381
tpm2d/ibm-tss.h

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

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