Page MenuHome GnuPG

No OneTemporary

diff --git a/AUTHORS b/AUTHORS
index 6980407e5..71644c967 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -1,294 +1,295 @@
Program: GnuPG
Homepage: https://www.gnupg.org
Download: https://gnupg.org/ftp/gcrypt/gnupg/
Repository: git://git.gnupg.org/gnupg.git
Bug reports: https://bugs.gnupg.org
Security related bug reports: <security@gnupg.org>
Maintainer: Werner Koch <wk@gnupg.org>
License: GPLv3+
GnuPG is free software. See the files COPYING for copying conditions.
License copyright years may be listed using range notation, e.g.,
2000-2013, indicating that every year in the range, inclusive, is a
copyrightable year that would otherwise be listed individually.
List of Copyright holders
=========================
Copyright (C) 1997-2019 Werner Koch
Copyright (C) 1994-2021 Free Software Foundation, Inc.
Copyright (C) 2003-2021 g10 Code GmbH
Copyright (C) 2002 Klarälvdalens Datakonsult AB
Copyright (C) 1995-1997, 2000-2007 Ulrich Drepper <drepper@gnu.ai.mit.edu>
Copyright (C) 1994 X Consortium
Copyright (C) 1998 by The Internet Society.
Copyright (C) 1998-2004 The OpenLDAP Foundation
Copyright (C) 1998-2004 Kurt D. Zeilenga.
Copyright (C) 1998-2004 Net Boolean Incorporated.
Copyright (C) 2001-2004 IBM Corporation.
Copyright (C) 1999-2003 Howard Y.H. Chu.
Copyright (C) 1999-2003 Symas Corporation.
Copyright (C) 1998-2003 Hallvard B. Furuseth.
Copyright (C) 1992-1996 Regents of the University of Michigan.
Copyright (C) 2000 Dimitrios Souflis
Copyright (C) 2008,2009,2010,2012-2016 William Ahern
Copyright (C) 2017 Bundesamt für Sicherheit in der Informationstechnik
+ Copyright (C) 2021 James Bottomley <James.Bottomley@HansenPartnership.com>
Authors with a FSF copyright assignment
=======================================
Ales Nyakhaychyk <nyakhaychyk@i1fn.linux.by> Translations [be]
Andrey Jivsov <openpgp@brainhub.org> Assigns past and future changes for ECC.
(g10/ecdh.c. other changes to support ECC)
Ben Kibbey <bjk@luxsci.net> Assigns past and future changes.
Birger Langkjer <birger.langkjer@image.dk> Translations [da]
Maxim Britov <maxim.britov@gmail.com> Translations [ru]
Daniel Resare <daniel@resare.com> Translations [sv]
Per Tunedal <per@clipanish.com> Translations [sv]
Daniel Nylander <po@danielnylander.se> Translations [sv]
Daiki Ueno <ueno@unixuser.org> Assigns Past and Future Changes.
(changed:passphrase.c and related code)
David Shaw <dshaw@jabberwocky.com> Assigns past and future changes.
(all in keyserver/,
a lot of changes in g10/ see the ChangeLog,
bug fixes here and there)
Dokianakis Theofanis <madf@hellug.gr> Translations [el]
Edmund GRIMLEY EVANS <edmundo@rano.org> Translations [eo]
Florian Weimer <fw@deneb.enyo.de> Assigns past and future changes
(changed:g10/parse-packet.c, include/iobuf.h, util/iobuf.c)
g10 Code GmbH <info@g10code.com> Assigns past and future changes
(all work since 2001 as indicated by mail addresses in ChangeLogs)
Assignment for future changes terminated on 2012-12-04
(mail 87boe9x0e3.fsf@vigenere.g10code.de)
Gaël Quéri <gael@lautre.net> Translations [fr]
(fixed a lot of typos)
Gregory Steuck <steuck@iname.com> Translations [ru]
Nagy Ferenc László <nfl@nfllab.com> Translations [hu]
Ivo Timmermans <itimmermans@bigfoot.com> Translations [nl]
Jacobo Tarri'o Barreiro <jtarrio@iname.com> Translations [gl]
Janusz Aleksander Urbanowicz <alex@bofh.torun.pl> Translations [pl]
Jakub Bogusz <qboosh@pld-linux.org> Translations [pl]
Jedi Lin <Jedi@idej.org> Translations [zh-tw]
Jouni Hiltunen <jouni.hiltunen@kolumbus.fi> Translations [fi]
Tommi Vainikainen <Tommi.Vainikainen@iki.fi> Translations [fi]
Laurentiu Buzdugan <lbgnupg@rolix.org> Translations [ro]
Magda Procha'zkova' <magda@math.muni.cz> Translations [cs]
Michael Roth <mroth@nessie.de> Assigns changes.
(wrote cipher/des.c., changes and bug fixes all over the place)
Michal Majer <mmajer@econ.umb.sk> Translations [sk]
Marco d'Itri <md@linux.it> Translations [it]
Marcus Brinkmann <marcus@g10code.de>
(gpgconf and fixes all over the place)
Matthew Skala <mskala@ansuz.sooke.bc.ca> Disclaimer
(wrote cipher/twofish.c)
Moritz Schulte <moritz@g10code.com>
(ssh support gpg-agent)
Niklas Hernaeus <nh@df.lth.se> Disclaimer
(weak key patches)
Nilgun Belma Buguner <nilgun@technologist.com> Translations [tr]
Nils Ellmenreich <nils 'at' infosun.fmi.uni-passau.de>
Assigns past and future changes
(configure.in, cipher/rndlinux.c, FAQ)
Paul Eggert <eggert@twinsun.com>
(configuration macros for LFS)
Pavel I. Shajdo <pshajdo@gmail.com> Translations [ru]
(man pages)
Pedro Morais <morais@poli.org> Translations [pt_PT]
Rémi Guyomarch <rguyom@mail.dotcom.fr> Assigns past and future changes.
(g10/compress.c, g10/encr-data.c,
g10/free-packet.c, g10/mdfilter.c, g10/plaintext.c, util/iobuf.c)
Stefan Bellon <sbellon@sbellon.de> Assigns past and future changes.
(All patches to support RISC OS)
Timo Schulz <twoaday@freakmail.de> Assigns past and future changes.
(util/w32reg.c, g10/passphrase.c, g10/hkp.c)
Tedi Heriyanto <tedi_h@gmx.net> Translations [id]
Thiago Jung Bauermann <jungmann@cwb.matrix.com.br> Translations [pt_BR]
Rafael Caetano dos Santos <rcaetano@linux.ime.usp.br> Translations [pt_BR]
Toomas Soome <tsoome@ut.ee> Translations [et]
Urko Lusa <ulusa@euskalnet.net> Translations [es_ES]
Walter Koch <koch@u32.de> Translations [de]
Werner Koch <wk@gnupg.org> Assigns GNU Privacy Guard and future changes.
(started the whole thing, wrote the S/MIME extensions, the
smartcard daemon and the gpg-agent)
Assignment for future changes terminated on 2013-03-29
(mail 878v6dbut0.fsf@vigenere.g10code.de dated 2013-02-24).
Yosiaki IIDA <iida@ring.gr.jp> Translations [ja]
Yuri Chornoivan, yurchor at ukr dot net: Translations [uk]
Yutaka Niibe Assigns Past and Future Changes
(scd/)
Authors with a DCO
==================
Andre Heinecke <aheinecke@intevation.de>
2014-09-19:4525694.FcpLvWDUFT@esus:
Andreas Schwier <andreas.schwier@cardcontact.de>
2014-07-22:53CED1D8.1010306@cardcontact.de:
Arnaud Fontaine <arnaud.fontaine at ssi.gouv.fr>
2016-10-17:580484F4.8040806@ssi.gouv.fr:
Ben McGinnes <ben@adversary.org>
2017-12-16:20171216002102.l6aejk5xdp6xhtfi@adversary.org:
Christian Aistleitner <christian@quelltextlich.at>
2013-05-26:20130626112332.GA2228@quelltextlich.at:
Damien Goutte-Gattat <dgouttegattat@incenp.org>
2015-01-17:54BA49AA.2040708@incenp.org:
Daniel Kahn Gillmor <dkg@fifthhorseman.net>
2014-09-24:87oau6w9q7.fsf@alice.fifthhorseman.net:
Hans of Guardian <hans@guardianproject.info>
2013-06-26:D84473D7-F3F7-43D5-A9CE-16580B88D574@guardianproject.info:
Ineiev <ineiev@gnu.org>
2017-05-09:20170509121611.GH25850@gnu.org:
James Bottomley <James.Bottomley@HansenPartnership.com>
2018-02-01:1517501629.3145.9.camel@HansenPartnership.com:
Jiri Kerestes <jiri.kerestes@trustica.cz>
2018-07-25:<d77cfcda-bbc3-0620-4e81-10dff33a94ca@trustica.cz>:
Jonas Borgström <jonas@borgstrom.se>
2013-08-29:521F1E7A.5080602@borgstrom.se:
Joshua Rogers <git@internot.info>
2014-12-22:5497FE75.7010503@internot.info:
Jussi Kivilinna <jussi.kivilinna@iki.fi>
2018-02-11:2d8b7014-ff67-1e73-1152-9ff9fb8c10d7@iki.fi:
Kyle Butt <kylebutt@gmail.com>
2013-05-29:CAAODAYLbCtqOG6msLLL0UTdASKWT6u2ptxsgUQ1JpusBESBoNQ@mail.gmail.com:
Michael Haubenwallner <michael.haubenwallner@ssi-schaefer.com>
2018-07-13:c397e637-f1ce-34f0-7e6a-df04a76e1c35@ssi-schaefer.com:
Phil Pennock <phil.pennock@spodhuis.org>
Phil Pennock <phil@pennock-tech.com>
2017-01-19:20170119061225.GA26207@breadbox.private.spodhuis.org:
Rainer Perske <rainer.perske@uni-muenster.de>
2017-10-24:permail-2017102014511105be2aed00002fc6-perske@message-id.uni-muenster.de:
Stefan Tomanek <tomanek@internet-sicherheit.de>
2014-01-30:20140129234449.GY30808@zirkel.wertarbyte.de:
Tobias Mueller <muelli@cryptobitch.de>
2016-11-23:1479937342.11180.3.camel@cryptobitch.de:
Werner Koch <wk@gnupg.org>
2013-03-29:87620ahchj.fsf@vigenere.g10code.de:
William L. Thomson Jr. <wlt@o-sinc.com>
2017-05-23:assp.0316398ca8.20170523093623.00a17d03@o-sinc.com:
Yann E. MORIN <yann.morin.1998@free.fr>
2016-07-10:20160710093202.GA3688@free.fr:
Other authors
=============
The need for copyright assignments to the FSF has been waived on
2013-03-29; the need for copyright disclaimers for translations
already in December 2012.
The RPM specs file scripts/gnupg.spec has been contributed by
several people.
The function build_argv in agent/w32main.c is based on code from
Alexandre Julliard.
The gpg-zip documentation is based on the manpage for gpg-zip, written
by Colin Tuckley and Daniel Leidert for the GNU/Debian distribution.
The DNS resolver code is libdns by William Ahern; see COPYING.other.
The test driver is based on TinySCHEME by Dimitrios Souflis and
available under a permissive license; see COPYING.other.
Regular expression implementation is taken from Jim Tcl, originally
by Henry Spencer, and modified by others; see COPYING.other.
License
========
GnuPG is distributed under the GNU General Public License, version 3
or later (see file COPYING).
Note that some files are under a combination of the GNU Lesser General
Public License, version 3 (see file COPYING.LGPL3) and the GNU General
Public License, version 2 (see file COPYING.GPL2). Some files are
under the GNU Lesser General Public License, version 2.1 (see file
COPYING.LGPL21). A few files carry an all permissive license note as
found at the bottom of this file. A few files are distributed under
permissive licenses as listed in the file COPYING.other. Some other
small files are distributed under the Creative Commons Zero license
(see file COPYING.CC0) which basically puts them into the public
domain.
=========
Copyright 1998-2018 Free Software Foundation, Inc.
Copyright 1997-2018 Werner Koch
This file is free software; as a special exception the author gives
unlimited permission to copy and/or distribute it, with or without
modifications, as long as this notice is preserved.
This file is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/tpm2d/Makefile.am b/tpm2d/Makefile.am
index 56a9ab7c9..bf5acd452 100644
--- a/tpm2d/Makefile.am
+++ b/tpm2d/Makefile.am
@@ -1,18 +1,37 @@
+# Makfile.am - Makefile for tpm2d
+# Copyright (C) 2021 James Bottomley <James.Bottomley@HansenPartnership.com>
+#
+# 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/>.
+# SPDX-License-Identifier: GPL-3.0-or-later
+
AM_CPPFLAGS =
include $(top_srcdir)/am/cmacros.am
libexec_PROGRAMS = tpm2daemon
AM_CFLAGS = $(LIBGCRYPT_CFLAGS) \
$(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) $(LIBTSS_CFLAGS)
tpm2daemon_SOURCES = \
command.c \
tpm2daemon.c \
tpm2.c tpm2.h \
tpm2daemon.h ibm-tss.h
tpm2daemon_LDADD = $(libcommonpth) \
$(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \
$(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV) $(LIBTSS_LIBS)
diff --git a/tpm2d/command.c b/tpm2d/command.c
index 619bb56e6..351781111 100644
--- a/tpm2d/command.c
+++ b/tpm2d/command.c
@@ -1,504 +1,505 @@
/* 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/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#ifdef USE_NPTH
# include <npth.h>
#endif
#include "tpm2daemon.h"
#include "tpm2.h"
#include <assuan.h>
#include <ksba.h>
#include "../common/asshelp.h"
#include "../common/server-help.h"
/* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN */
#define MAXLEN_PIN 100
/* Maximum allowed size of key data as used in inquiries. */
#define MAXLEN_KEYDATA 4096
/* Maximum allowed total data size for SETDATA. */
#define MAXLEN_SETDATA 4096
/* Maximum allowed size of certificate data as used in inquiries. */
#define MAXLEN_CERTDATA 16384
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
/* Data used to associate an Assuan context with local server data.
This object describes the local properties of one session. */
struct server_local_s
{
/* We keep a list of all active sessions with the anchor at
SESSION_LIST (see below). This field is used for linking. */
struct server_local_s *next_session;
/* This object is usually assigned to a CTRL object (which is
globally visible). While enumerating all sessions we sometimes
need to access data of the CTRL object; thus we keep a
backpointer here. */
ctrl_t ctrl_backlink;
/* The Assuan context used by this session/server. */
assuan_context_t assuan_ctx;
#ifdef HAVE_W32_SYSTEM
unsigned long event_signal; /* Or 0 if not used. */
#else
int event_signal; /* Or 0 if not used. */
#endif
/* True if the card has been removed and a reset is required to
continue operation. */
int card_removed;
/* If set to true we will be terminate ourself at the end of the
this session. */
int stopme;
};
/* To keep track of all running sessions, we link all active server
contexts and the anchor in this variable. */
static struct server_local_s *session_list;
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
(void) ctx;
(void) line;
return 0;
}
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
if (!strcmp (key, "event-signal"))
{
/* A value of 0 is allowed to reset the event signal. */
#ifdef HAVE_W32_SYSTEM
if (!*value)
return gpg_error (GPG_ERR_ASS_PARAMETER);
ctrl->server_local->event_signal = strtoul (value, NULL, 16);
#else
int i = *value? atoi (value) : -1;
if (i < 0)
return gpg_error (GPG_ERR_ASS_PARAMETER);
ctrl->server_local->event_signal = i;
#endif
}
return 0;
}
static gpg_error_t
pin_cb (ctrl_t ctrl, const char *info, char **retstr)
{
assuan_context_t ctx = ctrl->ctx;
char *command;
int rc;
unsigned char *value;
size_t valuelen;
*retstr = NULL;
log_debug ("asking for PIN '%s'\n", info);
rc = gpgrt_asprintf (&command, "NEEDPIN %s", info);
if (rc < 0)
return gpg_error (gpg_err_code_from_errno (errno));
/* Fixme: Write an inquire function which returns the result in
secure memory and check all further handling of the PIN. */
rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN);
xfree (command);
if (rc)
return rc;
if (!valuelen)
{
/* We require that the returned value is an UTF-8 string */
xfree (value);
return gpg_error (GPG_ERR_INV_RESPONSE);
}
*retstr = (char*)value;
return 0;
}
static const char hlp_import[] =
"IMPORT\n"
"\n"
"This command is used to convert a public and secret key to tpm format.\n"
"keydata is communicated via an inquire KEYDATA command\n"
"The keydata is expected to be the usual canonical encoded\n"
"S-expression. The return will be a TPM format S-expression\n"
"\n"
"A PIN will be requested.";
static gpg_error_t
cmd_import (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *keydata;
size_t keydatalen;
TSS_CONTEXT *tssc;
gcry_sexp_t s_key;
unsigned char *shadow_info = NULL;
size_t shadow_len;
line = skip_options (line);
if (*line)
return set_error (GPG_ERR_ASS_PARAMETER, "additional parameters given");
/* Now get the actual keydata. */
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, "KEYDATA", &keydata, &keydatalen, MAXLEN_KEYDATA);
assuan_end_confidential (ctx);
if (rc)
return rc;
if ((rc = tpm2_start (&tssc)))
goto out;
gcry_sexp_new (&s_key, keydata, keydatalen, 0);
rc = tpm2_import_key (ctrl, tssc, pin_cb, &shadow_info, &shadow_len,
s_key, opt.parent);
gcry_sexp_release (s_key);
tpm2_end (tssc);
if (rc)
goto out;
rc = assuan_send_data (ctx, shadow_info, shadow_len);
out:
xfree (shadow_info);
xfree (keydata);
return rc;
}
static const char hlp_pksign[] =
"PKSIGN\n"
"\n"
"Get the TPM to produce a signature. KEYDATA will request the TPM\n"
"form S-expression (returned by IMPORT) and EXTRA will be the hash\n"
"to sign. The TPM currently deduces hash type from length.\n"
"\n"
"A PIN will be requested.";
static gpg_error_t
cmd_pksign (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *shadow_info;
size_t len;
TSS_CONTEXT *tssc;
TPM_HANDLE key;
TPMI_ALG_PUBLIC type;
unsigned char *digest;
size_t digestlen;
unsigned char *sig;
size_t siglen;
line = skip_options (line);
if (*line)
return set_error (GPG_ERR_ASS_PARAMETER, "additional parameters given");
/* Now get the actual keydata. */
rc = assuan_inquire (ctx, "KEYDATA", &shadow_info, &len, MAXLEN_KEYDATA);
if (rc)
return rc;
rc = assuan_inquire (ctx, "EXTRA", &digest, &digestlen, MAXLEN_KEYDATA);
if (rc)
goto out_freeshadow;
rc = tpm2_start (&tssc);
if (rc)
goto out;
rc = tpm2_load_key (tssc, shadow_info, &key, &type);
if (rc)
goto end_out;
rc = tpm2_sign (ctrl, tssc, key, pin_cb, type, digest, digestlen,
&sig, &siglen);
tpm2_flush_handle (tssc, key);
end_out:
tpm2_end (tssc);
if (rc)
goto out;
rc = assuan_send_data (ctx, sig, siglen);
xfree (sig);
out:
xfree (digest);
out_freeshadow:
xfree (shadow_info);
return rc;
}
static const char hlp_pkdecrypt[] =
"PKDECRYPT\n"
"Get the TPM to recover a symmetric key. KEYDATA will request the TPM\n"
"form S-expression (returned by IMPORT) and EXTRA will be the input\n"
"to derive or decrypt. The return will be the symmetric key\n"
"\n"
"\n"
"A PIN will be requested.";
static gpg_error_t
cmd_pkdecrypt (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *shadow_info;
size_t len;
TSS_CONTEXT *tssc;
TPM_HANDLE key;
TPMI_ALG_PUBLIC type;
unsigned char *crypto;
size_t cryptolen;
char *buf;
size_t buflen;
line = skip_options (line);
if (*line)
return set_error (GPG_ERR_ASS_PARAMETER, "additional parameters given");
/* Now get the actual keydata. */
rc = assuan_inquire (ctx, "KEYDATA", &shadow_info, &len, MAXLEN_KEYDATA);
if (rc)
return rc;
rc = assuan_inquire (ctx, "EXTRA", &crypto, &cryptolen, MAXLEN_KEYDATA);
if (rc)
goto out_freeshadow;
rc = tpm2_start (&tssc);
if (rc)
goto out;
rc = tpm2_load_key (tssc, shadow_info, &key, &type);
if (rc)
goto end_out;
if (type == TPM_ALG_RSA)
rc = tpm2_rsa_decrypt (ctrl, tssc, key, pin_cb, crypto,
cryptolen, &buf, &buflen);
else if (type == TPM_ALG_ECC)
rc = tpm2_ecc_decrypt (ctrl, tssc, key, pin_cb, crypto,
cryptolen, &buf, &buflen);
tpm2_flush_handle (tssc, key);
end_out:
tpm2_end (tssc);
if (rc)
goto out;
rc = assuan_send_data (ctx, buf, buflen);
xfree (buf);
out:
xfree (crypto);
out_freeshadow:
xfree (shadow_info);
return rc;
}
static const char hlp_killtpm2d[] =
"KILLTPM2D\n"
"\n"
"Commit suicide.";
static gpg_error_t
cmd_killtpm2d (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return 0;
}
/* Tell the assuan library about our commands */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "IMPORT", cmd_import, hlp_import },
{ "PKSIGN", cmd_pksign, hlp_pksign },
{ "PKDECRYPT", cmd_pkdecrypt, hlp_pkdecrypt },
{ "KILLTPM2D", cmd_killtpm2d, hlp_killtpm2d },
{ NULL }
};
int i, rc;
for (i=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
assuan_set_hello_line (ctx, "GNU Privacy Guard's TPM2 server ready");
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_option_handler (ctx, option_handler);
return 0;
}
/* Startup the server. If FD is given as -1 this is simple pipe
server, otherwise it is a regular server. Returns true if there
are no more active asessions. */
int
tpm2d_command_handler (ctrl_t ctrl, int fd)
{
int rc;
assuan_context_t ctx = NULL;
int stopme;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("failed to allocate assuan context: %s\n",
gpg_strerror (rc));
tpm2d_exit (2);
}
if (fd == -1)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else
{
rc = assuan_init_socket_server (ctx, INT2FD (fd),
ASSUAN_SOCKET_SERVER_ACCEPTED);
}
if (rc)
{
log_error ("failed to initialize the server: %s\n",
gpg_strerror (rc));
tpm2d_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error ("failed to register commands with Assuan: %s\n",
gpg_strerror (rc));
tpm2d_exit (2);
}
assuan_set_pointer (ctx, ctrl);
ctrl->ctx = ctx;
/* Allocate and initialize the server object. Put it into the list
of active sessions. */
ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local);
ctrl->server_local->next_session = session_list;
session_list = ctrl->server_local;
ctrl->server_local->ctrl_backlink = ctrl;
ctrl->server_local->assuan_ctx = ctx;
/* Command processing loop. */
for (;;)
{
rc = assuan_accept (ctx);
if (rc == -1)
{
break;
}
else if (rc)
{
log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
break;
}
rc = assuan_process (ctx);
if (rc)
{
log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
continue;
}
}
/* Release the server object. */
if (session_list == ctrl->server_local)
session_list = ctrl->server_local->next_session;
else
{
struct server_local_s *sl;
for (sl=session_list; sl->next_session; sl = sl->next_session)
if (sl->next_session == ctrl->server_local)
break;
if (!sl->next_session)
BUG ();
sl->next_session = ctrl->server_local->next_session;
}
stopme = ctrl->server_local->stopme;
xfree (ctrl->server_local);
ctrl->server_local = NULL;
/* Release the Assuan context. */
assuan_release (ctx);
if (stopme)
tpm2d_exit (0);
/* If there are no more sessions return true. */
return !session_list;
}
diff --git a/tpm2d/ibm-tss.h b/tpm2d/ibm-tss.h
index 2a4961359..4ca49a6cf 100644
--- a/tpm2d/ibm-tss.h
+++ b/tpm2d/ibm-tss.h
@@ -1,381 +1,396 @@
-/*
+/* ibm-tss.h - Supporting TPM routines for the IBM TSS
* Copyright (C) 2021 James Bottomley <James.Bottomley@HansenPartnership.com>
*
- * Supporting TPM routines for the IBM TSS
+ * 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/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
*/
+
#ifndef _TPM2_IBM_TSS_H
#define _TPM2_IBM_TSS_H
#define TSSINCLUDE(x) < TSS_INCLUDE/x >
#include TSSINCLUDE(tss.h)
#include TSSINCLUDE(tssutils.h)
#include TSSINCLUDE(tssresponsecode.h)
#include TSSINCLUDE(tssmarshal.h)
#include TSSINCLUDE(Unmarshal_fp.h)
#include TSSINCLUDE(tsscryptoh.h)
#define EXT_TPM_RH_OWNER TPM_RH_OWNER
#define VAL(X) X.val
#define VAL_2B(X, MEMBER) X.t.MEMBER
static const char *tpm2_dir;
/* The TPM builds a small database of active files representing key
* parameters used for authentication and session encryption. Make sure
* they're contained in a separate directory to avoid stepping on any
* other application uses of the TPM */
static inline const char *
tpm2_set_unique_tssdir (void)
{
char *prefix = getenv ("XDG_RUNTIME_DIR"), *template,
*dir;
int len = 0;
if (!prefix)
prefix = "/tmp";
len = snprintf (NULL, 0, "%s/tss2.XXXXXX", prefix);
if (len <= 0)
return NULL;
template = xtrymalloc (len + 1);
if (!template)
return NULL;
len++;
len = snprintf (template, len, "%s/tss2.XXXXXX", prefix);
dir = mkdtemp (template);
return dir;
}
static inline void
tpm2_error (TPM_RC rc, const char *prefix)
{
const char *msg, *submsg, *num;
TSS_ResponseCode_toString (&msg, &submsg, &num, rc);
log_error ("%s gave TPM2 Error: %s%s%s", prefix, msg, submsg, num);
}
static inline int
TSS_start (TSS_CONTEXT **tssc)
{
TPM_RC rc;
tpm2_dir = tpm2_set_unique_tssdir ();
if (!tpm2_dir)
/* make this non fatal */
log_error ("Failed to set unique TPM directory\n");
rc = TSS_Create (tssc);
if (rc)
{
tpm2_error (rc, "TSS_Create");
return GPG_ERR_CARD;
}
rc = TSS_SetProperty (*tssc, TPM_DATA_DIR, tpm2_dir);
if (rc)
/* make this non fatal */
tpm2_error (rc, "TSS_SetProperty");
return 0;
}
static inline TPM_RC
tpm2_CreatePrimary (TSS_CONTEXT *tssContext, TPM_HANDLE primaryHandle,
TPM2B_SENSITIVE_CREATE *inSensitive,
TPM2B_PUBLIC *inPublic, TPM_HANDLE *objectHandle)
{
CreatePrimary_In in;
CreatePrimary_Out out;
TPM_RC rc;
in.primaryHandle = primaryHandle;
in.inSensitive = *inSensitive;
in.inPublic = *inPublic;
/* no outside info */
in.outsideInfo.t.size = 0;
/* no PCR state */
in.creationPCR.count = 0;
rc = TSS_Execute (tssContext,
(RESPONSE_PARAMETERS *)&out,
(COMMAND_PARAMETERS *)&in,
NULL,
TPM_CC_CreatePrimary,
TPM_RS_PW, NULL, 0,
TPM_RH_NULL, NULL, 0);
*objectHandle = out.objectHandle;
return rc;
}
static inline TPM_RC
tpm2_FlushContext (TSS_CONTEXT *tssContext, TPM_HANDLE flushHandle)
{
FlushContext_In in;
TPM_RC rc;
in.flushHandle = flushHandle;
rc = TSS_Execute (tssContext,
NULL,
(COMMAND_PARAMETERS *)&in,
NULL,
TPM_CC_FlushContext,
TPM_RH_NULL, NULL, 0);
return rc;
}
static inline TPM_RC
tpm2_ReadPublic (TSS_CONTEXT *tssContext, TPM_HANDLE objectHandle,
TPMT_PUBLIC *pub, TPM_HANDLE auth)
{
ReadPublic_In rin;
ReadPublic_Out rout;
TPM_RC rc;
UINT32 flags = 0;
if (auth != TPM_RH_NULL)
flags = TPMA_SESSION_ENCRYPT;
rin.objectHandle = objectHandle;
rc = TSS_Execute (tssContext,
(RESPONSE_PARAMETERS *)&rout,
(COMMAND_PARAMETERS *)&rin,
NULL,
TPM_CC_ReadPublic,
auth, NULL, flags,
TPM_RH_NULL, NULL, 0);
if (rc)
{
tpm2_error (rc, "TPM2_ReadPublic");
return rc;
}
if (pub)
*pub = rout.outPublic.publicArea;
return rc;
}
static inline TPM_RC
tpm2_StartAuthSession (TSS_CONTEXT *tssContext, TPM_HANDLE tpmKey,
TPM_HANDLE bind, TPM_SE sessionType,
TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash,
TPM_HANDLE *sessionHandle,
const char *bindPassword)
{
StartAuthSession_In in;
StartAuthSession_Out out;
StartAuthSession_Extra extra;
TPM_RC rc;
memset (&in, 0, sizeof(in));
memset (&extra, 0 , sizeof(extra));
extra.bindPassword = bindPassword;
in.tpmKey = tpmKey;
in.bind = bind;
in.sessionType = sessionType;
in.symmetric = *symmetric;
in.authHash = authHash;
if (tpmKey != TPM_RH_NULL)
{
/*
* For the TSS to use a key as salt, it must have
* access to the public part. It does this by keeping
* key files, but request the public part just to make
* sure
*/
tpm2_ReadPublic (tssContext, tpmKey, NULL, TPM_RH_NULL);
/*
* don't care what rout returns, the purpose of the
* operation was to get the public key parameters into
* the tss so it can construct the salt
*/
}
rc = TSS_Execute (tssContext,
(RESPONSE_PARAMETERS *)&out,
(COMMAND_PARAMETERS *)&in,
(EXTRA_PARAMETERS *)&extra,
TPM_CC_StartAuthSession,
TPM_RH_NULL, NULL, 0);
*sessionHandle = out.sessionHandle;
return rc;
}
static inline TPM_RC
tpm2_Sign (TSS_CONTEXT *tssContext, TPM_HANDLE keyHandle, DIGEST_2B *digest,
TPMT_SIG_SCHEME *inScheme, TPMT_SIGNATURE *signature,
TPM_HANDLE auth, const char *authVal)
{
Sign_In in;
Sign_Out out;
TPM_RC rc;
in.keyHandle = keyHandle;
in.digest.t = *digest;
in.inScheme = *inScheme;
in.validation.tag = TPM_ST_HASHCHECK;
in.validation.hierarchy = TPM_RH_NULL;
in.validation.digest.t.size = 0;
rc = TSS_Execute (tssContext,
(RESPONSE_PARAMETERS *)&out,
(COMMAND_PARAMETERS *)&in,
NULL,
TPM_CC_Sign,
auth, authVal, 0,
TPM_RH_NULL, NULL, 0);
*signature = out.signature;
return rc;
}
static inline TPM_RC
tpm2_ECDH_ZGen (TSS_CONTEXT *tssContext, TPM_HANDLE keyHandle,
TPM2B_ECC_POINT *inPoint, TPM2B_ECC_POINT *outPoint,
TPM_HANDLE auth, const char *authVal)
{
ECDH_ZGen_In in;
ECDH_ZGen_Out out;
TPM_RC rc;
in.keyHandle = keyHandle;
in.inPoint = *inPoint;
rc = TSS_Execute (tssContext,
(RESPONSE_PARAMETERS *)&out,
(COMMAND_PARAMETERS *)&in,
NULL,
TPM_CC_ECDH_ZGen,
auth, authVal, TPMA_SESSION_ENCRYPT,
TPM_RH_NULL, NULL, 0);
*outPoint = out.outPoint;
return rc;
}
static inline TPM_RC
tpm2_RSA_Decrypt (TSS_CONTEXT *tssContext, TPM_HANDLE keyHandle,
PUBLIC_KEY_RSA_2B *cipherText, TPMT_RSA_DECRYPT *inScheme,
PUBLIC_KEY_RSA_2B *message,
TPM_HANDLE auth, const char *authVal, int flags)
{
RSA_Decrypt_In in;
RSA_Decrypt_Out out;
TPM_RC rc;
in.keyHandle = keyHandle;
in.inScheme = *inScheme;
in.cipherText.t = *cipherText;
in.label.t.size = 0;
rc = TSS_Execute (tssContext,
(RESPONSE_PARAMETERS *)&out,
(COMMAND_PARAMETERS *)&in,
NULL,
TPM_CC_RSA_Decrypt,
auth, authVal, flags,
TPM_RH_NULL, NULL, 0);
*message = out.message.t;
return rc;
}
static inline TPM_RC
tpm2_Load (TSS_CONTEXT *tssContext, TPM_HANDLE parentHandle,
PRIVATE_2B *inPrivate, TPM2B_PUBLIC *inPublic,
TPM_HANDLE *objectHandle,
TPM_HANDLE auth, const char *authVal)
{
Load_In in;
Load_Out out;
TPM_RC rc;
in.parentHandle = parentHandle;
in.inPrivate.t = *inPrivate;
in.inPublic = *inPublic;
rc = TSS_Execute (tssContext,
(RESPONSE_PARAMETERS *)&out,
(COMMAND_PARAMETERS *)&in,
NULL,
TPM_CC_Load,
auth, authVal, 0,
TPM_RH_NULL, NULL, 0);
if (rc == TPM_RC_SUCCESS)
*objectHandle = out.objectHandle;
return rc;
}
static inline TPM_RC
tpm2_Import (TSS_CONTEXT *tssContext, TPM_HANDLE parentHandle,
DATA_2B *encryptionKey, TPM2B_PUBLIC *objectPublic,
PRIVATE_2B *duplicate, ENCRYPTED_SECRET_2B *inSymSeed,
TPMT_SYM_DEF_OBJECT *symmetricAlg, PRIVATE_2B *outPrivate,
TPM_HANDLE auth, const char *authVal)
{
Import_In iin;
Import_Out iout;
TPM_RC rc;
iin.parentHandle = parentHandle;
iin.encryptionKey.t = *encryptionKey;
iin.objectPublic = *objectPublic;
iin.duplicate.t = *duplicate;
iin.inSymSeed.t = *inSymSeed;
iin.symmetricAlg = *symmetricAlg;
rc = TSS_Execute (tssContext,
(RESPONSE_PARAMETERS *)&iout,
(COMMAND_PARAMETERS *)&iin,
NULL,
TPM_CC_Import,
auth, authVal, TPMA_SESSION_DECRYPT,
TPM_RH_NULL, NULL, 0);
*outPrivate = iout.outPrivate.t;
return rc;
}
static inline TPM_HANDLE
tpm2_handle_int (TSS_CONTEXT *tssContext, TPM_HANDLE h)
{
(void)tssContext;
return h;
}
static inline TPM_HANDLE
tpm2_handle_ext (TSS_CONTEXT *tssContext, TPM_HANDLE h)
{
(void)tssContext;
return h;
}
static inline int
tpm2_handle_mso (TSS_CONTEXT *tssContext, TPM_HANDLE h, UINT32 mso)
{
(void)tssContext;
return (h >> 24) == mso;
}
#endif
diff --git a/tpm2d/tpm2.c b/tpm2d/tpm2.c
index 2bd3dc177..3e908ddb1 100644
--- a/tpm2d/tpm2.c
+++ b/tpm2d/tpm2.c
@@ -1,987 +1,1007 @@
+/* tpm2.c - Supporting TPM routines for the IBM TSS
+ * Copyright (C) 2021 James Bottomley <James.Bottomley@HansenPartnership.com>
+ *
+ * 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/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include <arpa/inet.h>
#include "tpm2.h"
#include "../common/i18n.h"
#include "../common/sexp-parse.h"
int
tpm2_start (TSS_CONTEXT **tssc)
{
return TSS_start(tssc);
}
void
tpm2_end (TSS_CONTEXT *tssc)
{
TSS_Delete (tssc);
}
static TPM_HANDLE
tpm2_get_parent (TSS_CONTEXT *tssc, TPM_HANDLE p)
{
TPM_RC rc;
TPM2B_SENSITIVE_CREATE inSensitive;
TPM2B_PUBLIC inPublic;
TPM_HANDLE objectHandle;
p = tpm2_handle_int(tssc, p);
if (tpm2_handle_mso(tssc, p, TPM_HT_PERSISTENT))
return p; /* should only be permanent */
/* assume no hierarchy auth */
VAL_2B (inSensitive.sensitive.userAuth, size) = 0;
/* no sensitive date for storage keys */
VAL_2B (inSensitive.sensitive.data, size) = 0;
/* public parameters for a P-256 EC key */
inPublic.publicArea.type = TPM_ALG_ECC;
inPublic.publicArea.nameAlg = TPM_ALG_SHA256;
VAL (inPublic.publicArea.objectAttributes) =
TPMA_OBJECT_NODA |
TPMA_OBJECT_SENSITIVEDATAORIGIN |
TPMA_OBJECT_USERWITHAUTH |
TPMA_OBJECT_DECRYPT |
TPMA_OBJECT_RESTRICTED |
TPMA_OBJECT_FIXEDPARENT |
TPMA_OBJECT_FIXEDTPM;
inPublic.publicArea.parameters.eccDetail.symmetric.algorithm = TPM_ALG_AES;
inPublic.publicArea.parameters.eccDetail.symmetric.keyBits.aes = 128;
inPublic.publicArea.parameters.eccDetail.symmetric.mode.aes = TPM_ALG_CFB;
inPublic.publicArea.parameters.eccDetail.scheme.scheme = TPM_ALG_NULL;
inPublic.publicArea.parameters.eccDetail.curveID = TPM_ECC_NIST_P256;
inPublic.publicArea.parameters.eccDetail.kdf.scheme = TPM_ALG_NULL;
VAL_2B (inPublic.publicArea.unique.ecc.x, size) = 0;
VAL_2B (inPublic.publicArea.unique.ecc.y, size) = 0;
VAL_2B (inPublic.publicArea.authPolicy, size) = 0;
rc = tpm2_CreatePrimary (tssc, p, &inSensitive, &inPublic, &objectHandle);
if (rc)
{
tpm2_error (rc, "TSS_CreatePrimary");
return 0;
}
return objectHandle;
}
void
tpm2_flush_handle (TSS_CONTEXT *tssc, TPM_HANDLE h)
{
/* only flush volatile handles */
if (tpm2_handle_mso(tssc, h, TPM_HT_PERSISTENT))
return;
tpm2_FlushContext(tssc, h);
}
static int
tpm2_get_hmac_handle (TSS_CONTEXT *tssc, TPM_HANDLE *handle,
TPM_HANDLE salt_key)
{
TPM_RC rc;
TPMT_SYM_DEF symmetric;
symmetric.algorithm = TPM_ALG_AES;
symmetric.keyBits.aes = 128;
symmetric.mode.aes = TPM_ALG_CFB;
rc = tpm2_StartAuthSession(tssc, salt_key, TPM_RH_NULL, TPM_SE_HMAC,
&symmetric, TPM_ALG_SHA256, handle, NULL);
if (rc)
{
tpm2_error (rc, "TPM2_StartAuthSession");
return GPG_ERR_CARD;
}
return 0;
}
static int
tpm2_pre_auth (ctrl_t ctrl, TSS_CONTEXT *tssc,
gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info,
char **retstr),
TPM_HANDLE *ah, char **auth)
{
TPM_RC rc;
int len;
rc = pin_cb (ctrl, _("TPM Key Passphrase"), auth);
if (rc)
return rc;
len = strlen(*auth);
/*
* TPMs can't accept a longer passphrase than the name algorithm.
* We hard code the name algorithm to SHA256 so the max passphrase
* length is 32
*/
if (len > 32)
{
log_error ("Truncating Passphrase to TPM allowed 32\n");
(*auth)[32] = '\0';
}
rc = tpm2_get_hmac_handle (tssc, ah, TPM_RH_NULL);
return rc;
}
static int
tpm2_post_auth (TSS_CONTEXT *tssc, TPM_RC rc, TPM_HANDLE ah,
char **auth, const char *cmd_str)
{
gcry_free (*auth);
*auth = NULL;
if (rc)
{
tpm2_error (rc, cmd_str);
tpm2_flush_handle (tssc, ah);
switch (rc & 0xFF)
{
case TPM_RC_BAD_AUTH:
case TPM_RC_AUTH_FAIL:
return GPG_ERR_BAD_PASSPHRASE;
default:
return GPG_ERR_CARD;
}
}
return 0;
}
static unsigned char *
make_tpm2_shadow_info (uint32_t parent, const char *pub, int pub_len,
const char *priv, int priv_len, size_t *len)
{
gcry_sexp_t s_exp;
char *info;
gcry_sexp_build (&s_exp, NULL, "(%u%b%b)", parent, pub_len, pub,
priv_len, priv);
*len = gcry_sexp_sprint (s_exp, GCRYSEXP_FMT_CANON, NULL, 0);
info = xtrymalloc (*len);
if (!info)
goto out;
gcry_sexp_sprint (s_exp, GCRYSEXP_FMT_CANON, info, *len);
out:
gcry_sexp_release (s_exp);
return (unsigned char *)info;
}
static gpg_error_t
parse_tpm2_shadow_info (const unsigned char *shadow_info,
uint32_t *parent,
const char **pub, int *pub_len,
const char **priv, int *priv_len)
{
const unsigned char *s;
size_t n;
int i;
s = shadow_info;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
*parent = 0;
for (i = 0; i < n; i++)
{
*parent *= 10;
*parent += atoi_1(s+i);
}
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
*pub_len = n;
*pub = s;
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
*priv_len = n;
*priv = s;
return 0;
}
int
tpm2_load_key (TSS_CONTEXT *tssc, const unsigned char *shadow_info,
TPM_HANDLE *key, TPMI_ALG_PUBLIC *type)
{
uint32_t parent;
TPM_HANDLE parentHandle;
PRIVATE_2B inPrivate;
TPM2B_PUBLIC inPublic;
const char *pub, *priv;
int ret, pub_len, priv_len;
TPM_RC rc;
BYTE *buf;
uint32_t size;
ret = parse_tpm2_shadow_info (shadow_info, &parent, &pub, &pub_len,
&priv, &priv_len);
if (ret)
return ret;
parentHandle = tpm2_get_parent (tssc, parent);
buf = (BYTE *)priv;
size = priv_len;
TPM2B_PRIVATE_Unmarshal ((TPM2B_PRIVATE *)&inPrivate, &buf, &size);
buf = (BYTE *)pub;
size = pub_len;
TPM2B_PUBLIC_Unmarshal (&inPublic, &buf, &size, FALSE);
*type = inPublic.publicArea.type;
rc = tpm2_Load (tssc, parentHandle, &inPrivate, &inPublic, key,
TPM_RS_PW, NULL);
tpm2_flush_handle (tssc, parentHandle);
if (rc != TPM_RC_SUCCESS)
{
tpm2_error (rc, "TPM2_Load");
return GPG_ERR_CARD;
}
return 0;
}
int
tpm2_sign (ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key,
gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info,
char **retstr),
TPMI_ALG_PUBLIC type,
const unsigned char *digest, size_t digestlen,
unsigned char **r_sig, size_t *r_siglen)
{
int ret;
DIGEST_2B digest2b;
TPMT_SIG_SCHEME inScheme;
TPMT_SIGNATURE signature;
TPM_HANDLE ah;
char *auth;
/* The TPM insists on knowing the digest type, so
* calculate that from the size */
switch (digestlen)
{
case 20:
inScheme.details.rsassa.hashAlg = TPM_ALG_SHA1;
break;
case 32:
inScheme.details.rsassa.hashAlg = TPM_ALG_SHA256;
break;
case 48:
inScheme.details.rsassa.hashAlg = TPM_ALG_SHA384;
break;
#ifdef TPM_ALG_SHA512
case 64:
inScheme.details.rsassa.hashAlg = TPM_ALG_SHA512;
break;
#endif
default:
log_error ("Unknown signature digest length, cannot deduce hash type for TPM\n");
return GPG_ERR_NO_SIGNATURE_SCHEME;
}
digest2b.size = digestlen;
memcpy (digest2b.buffer, digest, digestlen);
if (type == TPM_ALG_RSA)
inScheme.scheme = TPM_ALG_RSASSA;
else if (type == TPM_ALG_ECC)
inScheme.scheme = TPM_ALG_ECDSA;
else
return GPG_ERR_PUBKEY_ALGO;
ret = tpm2_pre_auth (ctrl, tssc, pin_cb, &ah, &auth);
if (ret)
return ret;
ret = tpm2_Sign (tssc, key, &digest2b, &inScheme, &signature, ah, auth);
ret = tpm2_post_auth (tssc, ret, ah, &auth, "TPM2_Sign");
if (ret)
return ret;
if (type == TPM_ALG_RSA)
*r_siglen = VAL_2B (signature.signature.rsassa.sig, size);
else if (type == TPM_ALG_ECC)
*r_siglen = VAL_2B (signature.signature.ecdsa.signatureR, size)
+ VAL_2B (signature.signature.ecdsa.signatureS, size);
*r_sig = xtrymalloc (*r_siglen);
if (!r_sig)
return GPG_ERR_ENOMEM;
if (type == TPM_ALG_RSA)
{
memcpy (*r_sig, VAL_2B (signature.signature.rsassa.sig, buffer),
*r_siglen);
}
else if (type == TPM_ALG_ECC)
{
memcpy (*r_sig, VAL_2B (signature.signature.ecdsa.signatureR, buffer),
VAL_2B (signature.signature.ecdsa.signatureR, size));
memcpy (*r_sig + VAL_2B (signature.signature.ecdsa.signatureR, size),
VAL_2B (signature.signature.ecdsa.signatureS, buffer),
VAL_2B (signature.signature.ecdsa.signatureS, size));
}
return 0;
}
static int
sexp_to_tpm2_sensitive_ecc (TPMT_SENSITIVE *s, gcry_sexp_t key)
{
gcry_mpi_t d;
gcry_sexp_t l;
int rc = -1;
size_t len;
s->sensitiveType = TPM_ALG_ECC;
VAL_2B (s->seedValue, size) = 0;
l = gcry_sexp_find_token (key, "d", 0);
if (!l)
return rc;
d = gcry_sexp_nth_mpi (l, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l);
len = sizeof (VAL_2B (s->sensitive.ecc, buffer));
rc = gcry_mpi_print (GCRYMPI_FMT_USG, VAL_2B (s->sensitive.ecc, buffer),
len, &len, d);
VAL_2B (s->sensitive.ecc, size) = len;
gcry_mpi_release (d);
return rc;
}
/* try to match the libgcrypt curve names to known TPM parameters.
*
* As of 2018 the TCG defined curves are only NIST
* (192,224,256,384,521) Barreto-Naehring (256,638) and the Chinese
* SM2 (256), which means only the NIST ones overlap with libgcrypt */
static struct {
const char *name;
TPMI_ECC_CURVE c;
} tpm2_curves[] = {
{ "NIST P-192", TPM_ECC_NIST_P192 },
{ "prime192v1", TPM_ECC_NIST_P192 },
{ "secp192r1", TPM_ECC_NIST_P192 },
{ "nistp192", TPM_ECC_NIST_P192 },
{ "NIST P-224", TPM_ECC_NIST_P224 },
{ "secp224r1", TPM_ECC_NIST_P224 },
{ "nistp224", TPM_ECC_NIST_P224 },
{ "NIST P-256", TPM_ECC_NIST_P256 },
{ "prime256v1", TPM_ECC_NIST_P256 },
{ "secp256r1", TPM_ECC_NIST_P256 },
{ "nistp256", TPM_ECC_NIST_P256 },
{ "NIST P-384", TPM_ECC_NIST_P384 },
{ "secp384r1", TPM_ECC_NIST_P384 },
{ "nistp384", TPM_ECC_NIST_P384 },
{ "NIST P-521", TPM_ECC_NIST_P521 },
{ "secp521r1", TPM_ECC_NIST_P521 },
{ "nistp521", TPM_ECC_NIST_P521 },
};
static int
tpm2_ecc_curve (const char *curve_name, TPMI_ECC_CURVE *c)
{
int i;
for (i = 0; i < DIM (tpm2_curves); i++)
if (strcmp (tpm2_curves[i].name, curve_name) == 0)
break;
if (i == DIM (tpm2_curves))
{
log_error ("curve %s does not match any available TPM curves\n", curve_name);
return GPG_ERR_UNKNOWN_CURVE;
}
*c = tpm2_curves[i].c;
return 0;
}
static int
sexp_to_tpm2_public_ecc (TPMT_PUBLIC *p, gcry_sexp_t key)
{
const char *q;
gcry_sexp_t l;
int rc = GPG_ERR_BAD_PUBKEY;
size_t len;
TPMI_ECC_CURVE curve;
char *curve_name;
l = gcry_sexp_find_token (key, "curve", 0);
if (!l)
return rc;
curve_name = gcry_sexp_nth_string (l, 1);
if (!curve_name)
goto out;
rc = tpm2_ecc_curve (curve_name, &curve);
gcry_free (curve_name);
if (rc)
goto out;
gcry_sexp_release (l);
l = gcry_sexp_find_token (key, "q", 0);
if (!l)
return rc;
q = gcry_sexp_nth_data (l, 1, &len);
/* This is a point representation, the first byte tells you what
* type. The only format we understand is uncompressed (0x04)
* which has layout 0x04 | x | y */
if (q[0] != 0x04)
{
log_error ("Point format for q is not uncompressed\n");
goto out;
}
q++;
len--;
/* now should have to equal sized big endian point numbers */
if ((len & 0x01) == 1)
{
log_error ("Point format for q has incorrect length\n");
goto out;
}
len >>= 1;
p->type = TPM_ALG_ECC;
p->nameAlg = TPM_ALG_SHA256;
VAL (p->objectAttributes) = TPMA_OBJECT_NODA |
TPMA_OBJECT_SIGN |
TPMA_OBJECT_DECRYPT |
TPMA_OBJECT_USERWITHAUTH;
VAL_2B (p->authPolicy, size) = 0;
p->parameters.eccDetail.symmetric.algorithm = TPM_ALG_NULL;
p->parameters.eccDetail.scheme.scheme = TPM_ALG_NULL;
p->parameters.eccDetail.curveID = curve;
p->parameters.eccDetail.kdf.scheme = TPM_ALG_NULL;
memcpy (VAL_2B (p->unique.ecc.x, buffer), q, len);
VAL_2B (p->unique.ecc.x, size) = len;
memcpy (VAL_2B (p->unique.ecc.y, buffer), q + len, len);
VAL_2B (p->unique.ecc.y, size) = len;
out:
gcry_sexp_release (l);
return rc;
}
static int
sexp_to_tpm2_sensitive_rsa (TPMT_SENSITIVE *s, gcry_sexp_t key)
{
gcry_mpi_t p;
gcry_sexp_t l;
int rc = -1;
size_t len;
s->sensitiveType = TPM_ALG_RSA;
VAL_2B (s->seedValue, size) = 0;
l = gcry_sexp_find_token (key, "p", 0);
if (!l)
return rc;
p = gcry_sexp_nth_mpi (l, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l);
len = sizeof (VAL_2B (s->sensitive.rsa, buffer));
rc = gcry_mpi_print (GCRYMPI_FMT_USG, VAL_2B (s->sensitive.rsa, buffer),
len, &len, p);
VAL_2B (s->sensitive.rsa, size) = len;
gcry_mpi_release (p);
return rc;
}
static int
sexp_to_tpm2_public_rsa (TPMT_PUBLIC *p, gcry_sexp_t key)
{
gcry_mpi_t n, e;
gcry_sexp_t l;
int rc = -1, i;
size_t len;
/* longer than an int */
unsigned char ebuf[5];
uint32_t exp = 0;
p->type = TPM_ALG_RSA;
p->nameAlg = TPM_ALG_SHA256;
VAL (p->objectAttributes) = TPMA_OBJECT_NODA |
TPMA_OBJECT_DECRYPT |
TPMA_OBJECT_SIGN |
TPMA_OBJECT_USERWITHAUTH;
VAL_2B (p->authPolicy, size) = 0;
p->parameters.rsaDetail.symmetric.algorithm = TPM_ALG_NULL;
p->parameters.rsaDetail.scheme.scheme = TPM_ALG_NULL;
l = gcry_sexp_find_token (key, "n", 0);
if (!l)
return rc;
n = gcry_sexp_nth_mpi (l, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l);
len = sizeof (VAL_2B (p->unique.rsa, buffer));
p->parameters.rsaDetail.keyBits = gcry_mpi_get_nbits (n);
rc = gcry_mpi_print (GCRYMPI_FMT_USG, VAL_2B (p->unique.rsa, buffer),
len, &len, n);
VAL_2B (p->unique.rsa, size) = len;
gcry_mpi_release (n);
if (rc)
return rc;
rc = -1;
l = gcry_sexp_find_token (key, "e", 0);
if (!l)
return rc;
e = gcry_sexp_nth_mpi (l, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l);
len = sizeof (ebuf);
rc = gcry_mpi_print (GCRYMPI_FMT_USG, ebuf, len, &len, e);
gcry_mpi_release (e);
if (rc)
return rc;
if (len > 4)
return -1;
/* MPI are simply big endian integers, so convert to uint32 */
for (i = 0; i < len; i++)
{
exp <<= 8;
exp += ebuf[i];
}
if (exp == 0x10001)
p->parameters.rsaDetail.exponent = 0;
else
p->parameters.rsaDetail.exponent = exp;
return 0;
}
static int
sexp_to_tpm2(TPMT_PUBLIC *p, TPMT_SENSITIVE *s, gcry_sexp_t s_skey)
{
gcry_sexp_t l1, l2;
int rc = -1;
/* find the value of (private-key */
l1 = gcry_sexp_nth (s_skey, 1);
if (!l1)
return rc;
l2 = gcry_sexp_find_token (l1, "rsa", 0);
if (l2)
{
rc = sexp_to_tpm2_public_rsa (p, l2);
if (!rc)
rc = sexp_to_tpm2_sensitive_rsa (s, l2);
}
else
{
l2 = gcry_sexp_find_token (l1, "ecc", 0);
if (!l2)
goto out;
rc = sexp_to_tpm2_public_ecc (p, l2);
if (!rc)
rc = sexp_to_tpm2_sensitive_ecc (s, l2);
}
gcry_sexp_release (l2);
out:
gcry_sexp_release (l1);
return rc;
}
/* copied from TPM implementation code */
static TPM_RC
tpm2_ObjectPublic_GetName (NAME_2B *name,
TPMT_PUBLIC *tpmtPublic)
{
TPM_RC rc = 0;
uint16_t written = 0;
TPMT_HA digest;
uint32_t sizeInBytes;
uint8_t buffer[MAX_RESPONSE_SIZE];
/* marshal the TPMT_PUBLIC */
if (rc == 0)
{
INT32 size = MAX_RESPONSE_SIZE;
uint8_t *buffer1 = buffer;
rc = TSS_TPMT_PUBLIC_Marshal (tpmtPublic, &written, &buffer1, &size);
}
/* hash the public area */
if (rc == 0)
{
sizeInBytes = TSS_GetDigestSize (tpmtPublic->nameAlg);
digest.hashAlg = tpmtPublic->nameAlg; /* Name digest algorithm */
/* generate the TPMT_HA */
rc = TSS_Hash_Generate (&digest, written, buffer, 0, NULL);
}
if (rc == 0)
{
TPMI_ALG_HASH nameAlgNbo;
/* copy the digest */
memcpy (name->name + sizeof (TPMI_ALG_HASH),
(uint8_t *)&digest.digest, sizeInBytes);
/* copy the hash algorithm */
nameAlgNbo = htons (tpmtPublic->nameAlg);
memcpy (name->name, (uint8_t *)&nameAlgNbo, sizeof (TPMI_ALG_HASH));
/* set the size */
name->size = sizeInBytes + sizeof (TPMI_ALG_HASH);
}
return rc;
}
/*
* Cut down version of Part 4 Supporting Routines 7.6.3.10
*
* Hard coded to symmetrically encrypt with aes128 as the inner
* wrapper and no outer wrapper but with a prototype that allows
* drop in replacement with a tss equivalent
*/
TPM_RC tpm2_SensitiveToDuplicate (TPMT_SENSITIVE *s,
NAME_2B *name,
TPM_ALG_ID nalg,
TPMT_SYM_DEF_OBJECT *symdef,
DATA_2B *innerkey,
PRIVATE_2B *p)
{
BYTE *buf = p->buffer;
p->size = 0;
memset (p, 0, sizeof (*p));
/* hard code AES CFB */
if (symdef->algorithm == TPM_ALG_AES
&& symdef->mode.aes == TPM_ALG_CFB)
{
TPMT_HA hash;
const int hlen = TSS_GetDigestSize (nalg);
TPM2B *digest = (TPM2B *)buf;
TPM2B *s2b;
int32_t size;
unsigned char null_iv[AES_128_BLOCK_SIZE_BYTES];
UINT16 bsize, written = 0;
gcry_cipher_hd_t hd;
/* WARNING: don't use the static null_iv trick here:
* the AES routines alter the passed in iv */
memset (null_iv, 0, sizeof (null_iv));
/* reserve space for hash before the encrypted sensitive */
bsize = sizeof (digest->size) + hlen;
buf += bsize;
p->size += bsize;
s2b = (TPM2B *)buf;
/* marshal the digest size */
buf = (BYTE *)&digest->size;
bsize = hlen;
size = 2;
TSS_UINT16_Marshal (&bsize, &written, &buf, &size);
/* marshal the unencrypted sensitive in place */
size = sizeof (*s);
bsize = 0;
buf = s2b->buffer;
TSS_TPMT_SENSITIVE_Marshal (s, &bsize, &buf, &size);
buf = (BYTE *)&s2b->size;
size = 2;
TSS_UINT16_Marshal (&bsize, &written, &buf, &size);
bsize = bsize + sizeof (s2b->size);
p->size += bsize;
/* compute hash of unencrypted marshalled sensitive and
* write to the digest buffer */
hash.hashAlg = nalg;
TSS_Hash_Generate (&hash, bsize, s2b,
name->size, name->name,
0, NULL);
memcpy (digest->buffer, &hash.digest, hlen);
gcry_cipher_open (&hd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_CFB, GCRY_CIPHER_SECURE);
gcry_cipher_setiv (hd, null_iv, sizeof (null_iv));
gcry_cipher_setkey (hd, innerkey->buffer, innerkey->size);
/* encrypt the hash and sensitive in-place */
gcry_cipher_encrypt (hd, p->buffer, p->size, NULL, 0);
gcry_cipher_close (hd);
}
else if (symdef->algorithm == TPM_ALG_NULL)
{
/* Code is for debugging only, should never be used in production */
TPM2B *s2b = (TPM2B *)buf;
int32_t size = sizeof (*s);
UINT16 bsize = 0, written = 0;
log_error ("Secret key sent to TPM unencrypted\n");
buf = s2b->buffer;
/* marshal the unencrypted sensitive in place */
TSS_TPMT_SENSITIVE_Marshal (s, &bsize, &buf, &size);
buf = (BYTE *)&s2b->size;
size = 2;
TSS_UINT16_Marshal (&bsize, &written, &buf, &size);
p->size += bsize + sizeof (s2b->size);
}
else
{
log_error ("Unknown symmetric algorithm\n");
return TPM_RC_SYMMETRIC;
}
return TPM_RC_SUCCESS;
}
int
tpm2_import_key (ctrl_t ctrl, TSS_CONTEXT *tssc,
gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info,
char **retstr),
unsigned char **shadow_info, size_t *shadow_len,
gcry_sexp_t s_skey, unsigned long parent)
{
TPM_HANDLE parentHandle;
DATA_2B encryptionKey;
TPM2B_PUBLIC objectPublic;
PRIVATE_2B duplicate;
ENCRYPTED_SECRET_2B inSymSeed;
TPMT_SYM_DEF_OBJECT symmetricAlg;
PRIVATE_2B outPrivate;
NAME_2B name;
const int aes_key_bits = 128;
const int aes_key_bytes = aes_key_bits/8;
TPMT_SENSITIVE s;
TPM_HANDLE ah;
TPM_RC rc;
uint32_t size;
uint16_t len;
BYTE *buffer;
int ret;
char *passphrase;
char pub[sizeof (TPM2B_PUBLIC)];
int pub_len;
char priv[sizeof (TPM2B_PRIVATE)];
int priv_len;
if (parent == 0)
parent = EXT_TPM_RH_OWNER;
ret = sexp_to_tpm2 (&objectPublic.publicArea, &s, s_skey);
if (ret)
{
log_error ("Failed to parse Key s-expression: key corrupt?\n");
return ret;
}
/* add an authorization password to the key which the TPM will check */
ret = pin_cb (ctrl, _("Please enter the TPM Authorization passphrase for the key."), &passphrase);
if (ret)
return ret;
len = strlen(passphrase);
if (len > TSS_GetDigestSize(objectPublic.publicArea.nameAlg))
{
len = TSS_GetDigestSize(objectPublic.publicArea.nameAlg);
log_error ("Truncating Passphrase to TPM allowed %d\n", len);
}
VAL_2B (s.authValue, size) = len;
memcpy (VAL_2B (s.authValue, buffer), passphrase, len);
/* We're responsible for securing the data in transmission to the
* TPM here. The TPM provides parameter encryption via a session,
* but only for the first parameter. For TPM2_Import, the first
* parameter is a symmetric key used to encrypt the sensitive data,
* so we must populate this key with random value and encrypt the
* sensitive data with it */
parentHandle = tpm2_get_parent (tssc, parent);
tpm2_ObjectPublic_GetName (&name, &objectPublic.publicArea);
gcry_randomize (encryptionKey.buffer,
aes_key_bytes, GCRY_STRONG_RANDOM);
encryptionKey.size = aes_key_bytes;
/* set random symSeed */
inSymSeed.size = 0;
symmetricAlg.algorithm = TPM_ALG_AES;
symmetricAlg.keyBits.aes = aes_key_bits;
symmetricAlg.mode.aes = TPM_ALG_CFB;
tpm2_SensitiveToDuplicate (&s, &name, objectPublic.publicArea.nameAlg,
&symmetricAlg, &encryptionKey, &duplicate);
/* use salted parameter encryption to hide the key. First we read
* the public parameters of the parent key and use them to agree an
* encryption for the first parameter */
rc = tpm2_get_hmac_handle (tssc, &ah, parentHandle);
if (rc)
{
tpm2_flush_handle (tssc, parentHandle);
return GPG_ERR_CARD;
}
rc = tpm2_Import (tssc, parentHandle, &encryptionKey, &objectPublic,
&duplicate, &inSymSeed, &symmetricAlg, &outPrivate,
ah, NULL);
tpm2_flush_handle (tssc, parentHandle);
if (rc)
{
tpm2_error (rc, "TPM2_Import");
/* failure means auth handle is not flushed */
tpm2_flush_handle (tssc, ah);
if ((rc & 0xbf) == TPM_RC_VALUE)
{
log_error ("TPM cannot import RSA key: wrong size");
return GPG_ERR_UNSUPPORTED_ALGORITHM;
}
else if ((rc & 0xbf) == TPM_RC_CURVE)
{
log_error ("TPM cannot import requested curve");
return GPG_ERR_UNKNOWN_CURVE;
}
return GPG_ERR_CARD;
}
size = sizeof (pub);
buffer = pub;
len = 0;
TSS_TPM2B_PUBLIC_Marshal (&objectPublic,
&len, &buffer, &size);
pub_len = len;
size = sizeof (priv);
buffer = priv;
len = 0;
TSS_TPM2B_PRIVATE_Marshal ((TPM2B_PRIVATE *)&outPrivate,
&len, &buffer, &size);
priv_len = len;
*shadow_info = make_tpm2_shadow_info (parent, pub, pub_len,
priv, priv_len, shadow_len);
return rc;
}
int
tpm2_ecc_decrypt (ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key,
gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info,
char **retstr),
const char *ciphertext, int ciphertext_len,
char **decrypt, size_t *decrypt_len)
{
TPM2B_ECC_POINT inPoint;
TPM2B_ECC_POINT outPoint;
TPM_HANDLE ah;
char *auth;
size_t len;
int ret;
/* This isn't really a decryption per se. The ciphertext actually
* contains an EC Point which we must multiply by the private key number.
*
* The reason is to generate a diffe helman agreement on a shared
* point. This shared point is then used to generate the per
* session encryption key.
*/
if (ciphertext[0] != 0x04)
{
log_error ("Decryption Shared Point format is not uncompressed\n");
return GPG_ERR_ENCODING_PROBLEM;
}
if ((ciphertext_len & 0x01) != 1)
{
log_error ("Decryption Shared Point has incorrect length\n");
return GPG_ERR_ENCODING_PROBLEM;
}
len = ciphertext_len >> 1;
memcpy (VAL_2B (inPoint.point.x, buffer), ciphertext + 1, len);
VAL_2B (inPoint.point.x, size) = len;
memcpy (VAL_2B (inPoint.point.y, buffer), ciphertext + 1 + len, len);
VAL_2B (inPoint.point.y, size) = len;
ret = tpm2_pre_auth (ctrl, tssc, pin_cb, &ah, &auth);
if (ret)
return ret;
ret = tpm2_ECDH_ZGen (tssc, key, &inPoint, &outPoint, ah, auth);
ret = tpm2_post_auth (tssc, ret, ah, &auth, "TPM2_ECDH_ZGen");
if (ret)
return ret;
*decrypt_len = VAL_2B (outPoint.point.x, size) +
VAL_2B (outPoint.point.y, size) + 1;
*decrypt = xtrymalloc (*decrypt_len);
(*decrypt)[0] = 0x04;
memcpy (*decrypt + 1, VAL_2B (outPoint.point.x, buffer),
VAL_2B (outPoint.point.x, size));
memcpy (*decrypt + 1 + VAL_2B (outPoint.point.x, size),
VAL_2B (outPoint.point.y, buffer),
VAL_2B (outPoint.point.y, size));
return 0;
}
int
tpm2_rsa_decrypt (ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key,
gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info,
char **retstr),
const char *ciphertext, int ciphertext_len,
char **decrypt, size_t *decrypt_len)
{
int ret;
PUBLIC_KEY_RSA_2B cipherText;
TPMT_RSA_DECRYPT inScheme;
PUBLIC_KEY_RSA_2B message;
TPM_HANDLE ah;
char *auth;
inScheme.scheme = TPM_ALG_RSAES;
/*
* apparent gcrypt error: occasionally rsa ciphertext will
* be one byte too long and have a leading zero
*/
if ((ciphertext_len & 1) == 1 && ciphertext[0] == 0)
{
log_info ("Fixing Wrong Ciphertext size %d\n", ciphertext_len);
ciphertext_len--;
ciphertext++;
}
cipherText.size = ciphertext_len;
memcpy (cipherText.buffer, ciphertext, ciphertext_len);
ret = tpm2_pre_auth (ctrl, tssc, pin_cb, &ah, &auth);
if (ret)
return ret;
ret = tpm2_RSA_Decrypt (tssc, key, &cipherText, &inScheme, &message,
ah, auth, TPMA_SESSION_ENCRYPT);
ret = tpm2_post_auth (tssc, ret, ah, &auth, "TPM2_RSA_Decrypt");
if (ret)
return ret;
*decrypt_len = message.size;
*decrypt = xtrymalloc (message.size);
memcpy (*decrypt, message.buffer, message.size);
return 0;
}
diff --git a/tpm2d/tpm2.h b/tpm2d/tpm2.h
index a2d3745ea..f2fa89ed9 100644
--- a/tpm2d/tpm2.h
+++ b/tpm2d/tpm2.h
@@ -1,34 +1,54 @@
-#ifndef _TPM2_H
-#define _TPM2_H
+/* tpm2.h - Definitions for supporting TPM routines for the IBM TSS
+ * Copyright (C) 2021 James Bottomley <James.Bottomley@HansenPartnership.com>
+ *
+ * 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/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#ifndef _GNUPG_TPM2_H
+#define _GNUPG_TPM2_H
#include "../common/util.h"
#include "ibm-tss.h"
int tpm2_start (TSS_CONTEXT **tssc);
void tpm2_end (TSS_CONTEXT *tssc);
void tpm2_flush_handle (TSS_CONTEXT *tssc, TPM_HANDLE h);
int tpm2_load_key (TSS_CONTEXT *tssc, const unsigned char *shadow_info,
TPM_HANDLE *key, TPMI_ALG_PUBLIC *type);
int tpm2_sign (ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key,
gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info,
char **retstr),
TPMI_ALG_PUBLIC type,
const unsigned char *digest, size_t digestlen,
unsigned char **r_sig, size_t *r_siglen);
int tpm2_import_key (ctrl_t ctrl, TSS_CONTEXT *tssc,
gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info,
char **retstr),
unsigned char **shadow_info, size_t *shadow_len,
gcry_sexp_t s_skey, unsigned long parent);
int tpm2_rsa_decrypt (ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key,
gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info,
char **retstr),
const char *ciphertext, int ciphertext_len,
char **decrypt, size_t *decrypt_len);
int tpm2_ecc_decrypt (ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key,
gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info,
char **retstr),
const char *ciphertext, int ciphertext_len,
char **decrypt, size_t *decrypt_len);
#endif
diff --git a/tpm2d/tpm2daemon.c b/tpm2d/tpm2daemon.c
index 4ec6d7959..261896cc2 100644
--- a/tpm2d/tpm2daemon.c
+++ b/tpm2d/tpm2daemon.c
@@ -1,1289 +1,1290 @@
/* tpm2daemon.c - The GnuPG tpm2 Daemon
* Copyright (C) 2001-2002, 2004-2005, 2007-2009 Free Software Foundation, Inc.
* Copyright (C) 2001-2002, 2004-2005, 2007-2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <time.h>
#include <fcntl.h>
#ifndef HAVE_W32_SYSTEM
#include <sys/socket.h>
#include <sys/un.h>
#endif /*HAVE_W32_SYSTEM*/
#include <unistd.h>
#include <signal.h>
#include <npth.h>
#define INCLUDED_BY_MAIN_MODULE 1
#define GNUPG_COMMON_NEED_AFLOCAL
#include "tpm2daemon.h"
#include <gcrypt.h>
#include <assuan.h> /* malloc hooks */
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/gc-opt-flags.h"
#include "../common/asshelp.h"
#include "../common/exechelp.h"
#include "../common/init.h"
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
enum cmd_and_opt_values
{ aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
oOptions,
oDebug,
oDebugAll,
oDebugLevel,
oDebugWait,
oDebugAllowCoreDump,
oDebugLogTid,
oDebugAssuanLogCats,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oNoGrab,
oLogFile,
oServer,
oMultiServer,
oDaemon,
oListenBacklog,
oParent
};
static gpgrt_opt_t opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_group (301, N_("@Options:\n ")),
ARGPARSE_s_n (oServer,"server", N_("run in server mode (foreground)")),
ARGPARSE_s_n (oMultiServer, "multi-server",
N_("run in multi server mode (foreground)")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level" ,
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_s_n (oDebugLogTid, "debug-log-tid", "@"),
ARGPARSE_p_u (oDebugAssuanLogCats, "debug-assuan-log-cats", "@"),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write a log to FILE")),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_p_u (oParent, "tpm2-parent",
N_("Specify tpm2 parent for key")),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_IPC_VALUE , "ipc" },
{ 0, NULL }
};
/* The timer tick used to check card removal.
We poll every 500ms to let the user immediately know a status
change.
For a card reader with an interrupt endpoint, this timer is not
used with the internal CCID driver.
This is not too good for power saving but given that there is no
easy way to block on card status changes it is the best we can do.
For PC/SC we could in theory use an extra thread to wait for status
changes but that requires a native thread because there is no way
to make the underlying PC/SC card change function block using a Npth
mechanism. Given that a native thread could only be used under W32
we don't do that at all. */
#define TIMERTICK_INTERVAL_SEC (0)
#define TIMERTICK_INTERVAL_USEC (500000)
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* It is possible that we are currently running under setuid permissions */
static int maybe_setuid = 1;
/* Flag telling whether we are running as a pipe server. */
static int pipe_server;
/* Name of the communication socket */
static char *socket_name;
/* Name of the redirected socket or NULL. */
static char *redir_socket_name;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Value for the listen() backlog argument. Change at runtime with
* --listen-backlog. */
static int listen_backlog = 64;
#ifdef HAVE_W32_SYSTEM
static HANDLE the_event;
#else
/* PID to notify update of usb devices. */
static pid_t main_thread_pid;
#endif
#ifdef HAVE_PSELECT_NO_EINTR
/* FD to notify changes. */
static int notify_fd;
#endif
static char *create_socket_name (char *standard_name);
static gnupg_fd_t create_server_socket (const char *name,
char **r_redir_name,
assuan_sock_nonce_t *nonce);
static void *start_connection_thread (void *arg);
static void handle_connections (int listen_fd);
/* Pth wrapper function definitions. */
ASSUAN_SYSTEM_NPTH_IMPL;
static int active_connections;
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
static const char *
my_strusage (int level)
{
static char *ver_gcry;
const char *p;
switch (level)
{
case 11: p = "@TPM2DAEMON@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 1:
case 40: p = _("Usage: @TPM2DAEMON@ [options] (-h for help)");
break;
case 41: p = _("Syntax: tpm2daemon [options] [command [args]]\n"
"TPM2 daemon for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
static int
tid_log_callback (unsigned long *rvalue)
{
int len = sizeof (*rvalue);
npth_t thread;
thread = npth_self ();
if (sizeof (thread) < len)
len = sizeof (thread);
memcpy (rvalue, &thread, len);
return 2; /* Use use hex representation. */
}
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (const char *level)
{
int numok = (level && digitp (level));
int numlvl = numok? atoi (level) : 0;
if (!level)
;
else if (!strcmp (level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "expert") || (numok && numlvl <= 8))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "guru") || numok)
opt.debug = ~0;
else
{
log_error (_("invalid debug-level '%s' given\n"), level);
tpm2d_exit (2);
}
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
cleanup (void)
{
if (socket_name && *socket_name)
{
char *name;
name = redir_socket_name? redir_socket_name : socket_name;
gnupg_remove (name);
*socket_name = 0;
}
}
int
main (int argc, char **argv )
{
gpgrt_argparse_t pargs;
int orig_argc;
char **orig_argv;
char *last_configname = NULL;
const char *configname = NULL;
const char *shell;
int parse_debug = 0;
const char *debug_level = NULL;
int greeting = 0;
int nogreeting = 0;
int multi_server = 0;
int is_daemon = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
int debug_wait = 0;
int gpgconf_list = 0;
char *config_filename = NULL;
int allow_coredump = 0;
struct assuan_malloc_hooks malloc_hooks;
int res;
npth_t pipecon_handler;
early_system_init ();
gpgrt_set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to INIT_SECMEM()
somewhere after the option parsing */
log_set_prefix ("tpm2daemon", GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH);
assuan_sock_init ();
setup_libassuan_logging (&opt.debug, NULL);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
disable_core_dumps ();
/* Set default options. */
opt.parent = 0; /* 0 means TPM uses default */
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oDebug:
case oDebugAll:
parse_debug++;
break;
case oHomedir:
gnupg_set_homedir (pargs.r.ret_str);
break;
}
}
/* Reset the flags. */
pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
/* initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
maybe_setuid = 0;
/*
Now we are working under our real uid
*/
/* The configuraton directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
/* We are re-using the struct, thus the reset flag. We OR the
* flags so that the internal intialized flag won't be cleared. */
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags |= (ARGPARSE_FLAG_RESET
| ARGPARSE_FLAG_KEEP
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, TPM2DAEMON_NAME EXTSEP_S "conf"))
{
switch (pargs.r_opt)
{
case ARGPARSE_CONFFILE:
if (parse_debug)
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
if (pargs.r_type)
{
xfree (last_configname);
last_configname = xstrdup (pargs.r.ret_str);
configname = last_configname;
}
else
configname = NULL;
break;
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oDebugAllowCoreDump:
enable_core_dumps ();
allow_coredump = 1;
break;
case oDebugLogTid:
log_set_pid_suffix_cb (tid_log_callback);
break;
case oDebugAssuanLogCats:
set_libassuan_log_cats (pargs.r.ret_ulong);
break;
case oNoGreeting: nogreeting = 1; break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oServer: pipe_server = 1; break;
case oMultiServer: pipe_server = 1; multi_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
case oParent:
opt.parent = pargs.r.ret_ulong;
break;
default:
if (configname)
pargs.err = ARGPARSE_PRINT_WARNING;
else
pargs.err = ARGPARSE_PRINT_ERROR;
break;
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (!last_configname)
config_filename = gpgrt_fnameconcat (gnupg_homedir (),
TPM2DAEMON_NAME EXTSEP_S "conf",
NULL);
else
{
config_filename = last_configname;
last_configname = NULL;
}
if (log_get_errorcount (0))
exit (2);
if (nogreeting )
greeting = 0;
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
gpgrt_strusage (11), gpgrt_strusage (13),
gpgrt_strusage (14) );
es_fprintf (es_stderr, "%s\n", gpgrt_strusage (15) );
}
#ifdef IS_DEVELOPMENT_VERSION
log_info ("NOTE: this is a development version!\n");
#endif
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
set_debug (debug_level);
if (gpgconf_list == 2)
tpm2d_exit (0);
if (gpgconf_list)
{
es_printf ("verbose:%lu:\n"
"quiet:%lu:\n"
"debug-level:%lu:\"none:\n"
"log-file:%lu:\n",
GC_OPT_FLAG_NONE,
GC_OPT_FLAG_NONE,
GC_OPT_FLAG_DEFAULT,
GC_OPT_FLAG_NONE );
tpm2d_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (debug_wait && pipe_server)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid ());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
npth_attr_t tattr;
int fd = -1;
#ifndef HAVE_W32_SYSTEM
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif
npth_init ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* If --debug-allow-core-dump has been given we also need to
switch the working directory to a place where we can actually
write. */
if (allow_coredump)
{
if (chdir ("/tmp"))
log_debug ("chdir to '/tmp' failed: %s\n", strerror (errno));
else
log_debug ("changed working directory to '/tmp'\n");
}
/* In multi server mode we need to listen on an additional
socket. Create that socket now before starting the handler
for the pipe connection. This allows that handler to send
back the name of that socket. */
if (multi_server)
{
socket_name = create_socket_name (TPM2DAEMON_SOCK_NAME);
fd = FD2INT (create_server_socket (socket_name,
&redir_socket_name,
&socket_nonce));
}
res = npth_attr_init (&tattr);
if (res)
{
log_error ("error allocating thread attributes: %s\n",
strerror (res));
tpm2d_exit (2);
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
ctrl = xtrycalloc (1, sizeof *ctrl);
if ( !ctrl )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
tpm2d_exit (2);
}
ctrl->thread_startup.fd = GNUPG_INVALID_FD;
res = npth_create (&pipecon_handler, &tattr, start_connection_thread, ctrl);
if (res)
{
log_error ("error spawning pipe connection handler: %s\n",
strerror (res) );
xfree (ctrl);
tpm2d_exit (2);
}
npth_setname_np (pipecon_handler, "pipe-connection");
npth_attr_destroy (&tattr);
/* We run handle_connection to wait for the shutdown signal and
to run the ticker stuff. */
handle_connections (fd);
if (fd != -1)
close (fd);
}
else if (!is_daemon)
{
log_info (_("please use the option '--daemon'"
" to run the program in the background\n"));
}
else
{ /* Regular server mode */
int fd;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
int i;
#endif
/* Create the socket. */
socket_name = create_socket_name (TPM2DAEMON_SOCK_NAME);
fd = FD2INT (create_server_socket (socket_name,
&redir_socket_name, &socket_nonce));
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
#else
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* we are the parent */
char *infostr;
close (fd);
/* create the info string: <name>:<pid>:<protocol_version> */
if (gpgrt_asprintf (&infostr, "TPM2DAEMON_INFO=%s:%lu:1",
socket_name, (ulong) pid) < 0)
{
log_error ("out of core\n");
kill (pid, SIGTERM);
exit (1);
}
*socket_name = 0; /* don't let cleanup() remove the socket -
the child should do this from now on */
if (argc)
{ /* run the program given on the commandline */
if (putenv (infostr))
{
log_error ("failed to set environment: %s\n",
strerror (errno) );
kill (pid, SIGTERM );
exit (1);
}
execvp (argv[0], argv);
log_error ("failed to run the command: %s\n", strerror (errno));
kill (pid, SIGTERM);
exit (1);
}
else
{
/* Print the environment string, so that the caller can use
shell's eval to set it */
if (csh_style)
{
*strchr (infostr, '=') = ' ';
es_printf ( "setenv %s;\n", infostr);
}
else
{
es_printf ( "%s; export TPM2DAEMON_INFO;\n", infostr);
}
xfree (infostr);
exit (0);
}
/* NOTREACHED */
} /* end parent */
/* This is the child. */
npth_init ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Detach from tty and put process into a new session. */
if (!nodetach )
{
/* Close stdin, stdout and stderr unless it is the log stream. */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( !close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid () == -1)
{
log_error ("setsid() failed: %s\n", strerror (errno) );
cleanup ();
exit (1);
}
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
handle_connections (fd);
close (fd);
}
xfree (config_filename);
return 0;
}
void
tpm2d_exit (int rc)
{
gcry_control (GCRYCTL_TERM_SECMEM );
rc = rc? rc : log_get_errorcount (0)? 2 : 0;
exit (rc);
}
static void
tpm2d_init_default_ctrl (ctrl_t ctrl)
{
(void)ctrl;
}
static void
tpm2d_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
xfree (ctrl->in_data.value);
ctrl->in_data.value = NULL;
ctrl->in_data.valuelen = 0;
}
/* Return the name of the socket to be used to connect to this
process. If no socket is available, return NULL. */
const char *
tpm2d_get_socket_name (void)
{
if (socket_name && *socket_name)
return socket_name;
return NULL;
}
#ifndef HAVE_W32_SYSTEM
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
log_info ("SIGHUP received - "
"re-reading configuration\n");
/* reread_configuration (); */
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
#if 0
app_dump_state ();
#endif
break;
case SIGUSR2:
log_info ("SIGUSR2 received - no action defined\n");
break;
case SIGCONT:
/* Nothing. */
log_debug ("SIGCONT received - breaking select\n");
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i running threads\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", gpgrt_strusage (11),
gpgrt_strusage (13) );
cleanup ();
tpm2d_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info ( "%s %s stopped\n", gpgrt_strusage (11), gpgrt_strusage (13));
cleanup ();
tpm2d_exit (0);
break;
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif /*!HAVE_W32_SYSTEM*/
/* Create a name for the socket. We check for valid characters as
well as against a maximum allowed length for a unix domain socket
is done. The function terminates the process in case of an error.
Retunrs: Pointer to an allcoated string with the absolute name of
the socket used. */
static char *
create_socket_name (char *standard_name)
{
char *name;
name = make_filename (gnupg_socketdir (), standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
tpm2d_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
or terminates the process in case of an error. If the socket has
been redirected the name of the real socket is stored as a malloced
string at R_REDIR_NAME. */
static gnupg_fd_t
create_server_socket (const char *name, char **r_redir_name,
assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
xfree (*r_redir_name);
*r_redir_name = NULL;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == GNUPG_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
tpm2d_exit (2);
}
unaddr = xmalloc (sizeof (*unaddr));
addr = (struct sockaddr*)unaddr;
{
int redirected;
if (assuan_sock_set_sockaddr_un (name, addr, &redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
tpm2d_exit (2);
}
if (redirected)
{
*r_redir_name = xstrdup (unaddr->sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name);
}
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
if (rc == -1 && errno == EADDRINUSE)
{
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1
&& (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path,
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
tpm2d_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT (fd), listen_backlog) == -1)
{
log_error ("listen(fd, %d) failed: %s\n",
listen_backlog, gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
tpm2d_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
return fd;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread (void *arg)
{
ctrl_t ctrl = arg;
if (ctrl->thread_startup.fd != GNUPG_INVALID_FD
&& assuan_sock_check_nonce (ctrl->thread_startup.fd, &socket_nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD2INT (ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return NULL;
}
active_connections++;
tpm2d_init_default_ctrl (ctrl);
if (opt.verbose)
log_info (_("handler for fd %d started\n"),
FD2INT (ctrl->thread_startup.fd));
/* If this is a pipe server, we request a shutdown if the command
handler asked for it. With the next ticker event and given that
no other connections are running the shutdown will then
happen. */
if (tpm2d_command_handler (ctrl, FD2INT (ctrl->thread_startup.fd))
&& pipe_server)
shutdown_pending = 1;
if (opt.verbose)
log_info (_("handler for fd %d terminated\n"),
FD2INT (ctrl->thread_startup.fd));
tpm2d_deinit_default_ctrl (ctrl);
xfree (ctrl);
if (--active_connections == 0)
tpm2d_kick_the_loop ();
return NULL;
}
void
tpm2d_kick_the_loop (void)
{
#ifdef HAVE_W32_SYSTEM
int ret;
/* Kick the select loop. */
ret = SetEvent (the_event);
if (ret == 0)
log_error ("SetEvent for tpm2d_kick_the_loop failed: %s\n",
w32_strerror (-1));
#elif defined(HAVE_PSELECT_NO_EINTR)
write (notify_fd, "", 1);
#else
int ret;
ret = kill (main_thread_pid, SIGCONT);
if (ret < 0)
log_error ("SetEvent for tpm2d_kick_the_loop failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
#endif
}
/* Connection handler loop. Wait for connection requests and spawn a
thread after accepting a connection. LISTEN_FD is allowed to be -1
in which case this code will only do regular timeouts and handle
signals. */
static void
handle_connections (int listen_fd)
{
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int nfd;
int ret;
int fd;
struct timespec timeout;
struct timespec *t;
int saved_errno;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#else
int signo;
#endif
#ifdef HAVE_PSELECT_NO_EINTR
int pipe_fd[2];
ret = gnupg_create_pipe (pipe_fd);
if (ret)
{
log_error ("pipe creation failed: %s\n", gpg_strerror (ret));
return;
}
notify_fd = pipe_fd[1];
#endif
ret = npth_attr_init (&tattr);
if (ret)
{
log_error ("npth_attr_init failed: %s\n", strerror (ret));
return;
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifdef HAVE_W32_SYSTEM
{
HANDLE h, h2;
SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
events[0] = the_event = INVALID_HANDLE_VALUE;
events[1] = INVALID_HANDLE_VALUE;
h = CreateEvent (&sa, TRUE, FALSE, NULL);
if (!h)
log_error ("can't create tpm2d event: %s\n", w32_strerror (-1) );
else if (!DuplicateHandle (GetCurrentProcess (), h,
GetCurrentProcess (), &h2,
EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0))
{
log_error ("setting synchronize for tpm2d_kick_the_loop failed: %s\n",
w32_strerror (-1) );
CloseHandle (h);
}
else
{
CloseHandle (h);
events[0] = the_event = h2;
}
}
#else
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGCONT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
main_thread_pid = getpid ();
#endif
FD_ZERO (&fdset);
nfd = 0;
if (listen_fd != -1)
{
FD_SET (listen_fd, &fdset);
nfd = listen_fd;
}
for (;;)
{
int periodical_check;
int max_fd = nfd;
if (shutdown_pending)
{
if (active_connections == 0)
break; /* ready */
/* Do not accept anymore connections but wait for existing
connections to terminate. We do this by clearing out all
file descriptors to wait for, so that the select will be
used to just wait on a signal or timeout event. */
FD_ZERO (&fdset);
listen_fd = -1;
}
periodical_check = 0;
timeout.tv_sec = TIMERTICK_INTERVAL_SEC;
timeout.tv_nsec = TIMERTICK_INTERVAL_USEC * 1000;
if (shutdown_pending || periodical_check)
t = &timeout;
else
t = NULL;
/* POSIX says that fd_set should be implemented as a structure,
thus a simple assignment is fine to copy the entire set. */
read_fdset = fdset;
#ifdef HAVE_PSELECT_NO_EINTR
FD_SET (pipe_fd[0], &read_fdset);
if (max_fd < pipe_fd[0])
max_fd = pipe_fd[0];
#endif
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (max_fd+1, &read_fdset, NULL, NULL, t,
npth_sigev_sigmask ());
saved_errno = errno;
while (npth_sigev_get_pending (&signo))
handle_signal (signo);
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, t,
events, &events_set);
saved_errno = errno;
if (events_set & 1)
continue;
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
npth_sleep (1);
continue;
}
if (ret <= 0)
/* Timeout. Will be handled when calculating the next timeout. */
continue;
#ifdef HAVE_PSELECT_NO_EINTR
if (FD_ISSET (pipe_fd[0], &read_fdset))
{
char buf[256];
read (pipe_fd[0], buf, sizeof buf);
}
#endif
if (listen_fd != -1 && FD_ISSET (listen_fd, &read_fdset))
{
ctrl_t ctrl;
plen = sizeof paddr;
fd = npth_accept (listen_fd, (struct sockaddr *)&paddr, &plen);
if (fd == -1)
{
log_error ("accept failed: %s\n", strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
close (fd);
}
else
{
char threadname[50];
npth_t thread;
snprintf (threadname, sizeof threadname, "conn fd=%d", fd);
ctrl->thread_startup.fd = INT2FD (fd);
ret = npth_create (&thread, &tattr, start_connection_thread, ctrl);
if (ret)
{
log_error ("error spawning connection handler: %s\n",
strerror (ret));
xfree (ctrl);
close (fd);
}
else
npth_setname_np (thread, threadname);
}
}
}
#ifdef HAVE_W32_SYSTEM
if (the_event != INVALID_HANDLE_VALUE)
CloseHandle (the_event);
#endif
#ifdef HAVE_PSELECT_NO_EINTR
close (pipe_fd[0]);
close (pipe_fd[1]);
#endif
cleanup ();
log_info (_("%s %s stopped\n"), gpgrt_strusage (11), gpgrt_strusage (13));
npth_attr_destroy (&tattr);
}
/* Return the number of active connections. */
int
get_active_connection_count (void)
{
return active_connections;
}
diff --git a/tpm2d/tpm2daemon.h b/tpm2d/tpm2daemon.h
index 24d56a8dc..095978a5b 100644
--- a/tpm2d/tpm2daemon.h
+++ b/tpm2d/tpm2daemon.h
@@ -1,98 +1,100 @@
-/* tpm2daemon.h - Global definitions for the SCdaemon
- * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
+/* tpm2daemon.h - Global definitions for the TPM2D
+ * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc.
+ * Copyright (C) 2021 James Bottomley <James.Bottomley@HansenPartnership.com>
*
* 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/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
*/
#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*/

File Metadata

Mime Type
text/x-diff
Expires
Thu, Mar 19, 5:22 AM (1 d, 16 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
33/f9/6c061f30fc4b8dbba61061abf6d5

Event Timeline