From fedora.dm0 at gmail.com Mon Jun 1 16:24:53 2020 From: fedora.dm0 at gmail.com (David Michael) Date: Mon, 01 Jun 2020 10:24:53 -0400 Subject: [PATCH libgpg-error] build: Fix cross-compiling into a separate build dir. Message-ID: <87v9kakgne.fsf@gmail.com> * configure.ac: Create the src directory before writing into it. * src/Makefile.am (EXTRA_DIST): Add gen-lock-obj.sh. -- Signed-off-by: David Michael --- (resending since this apparently didn't get delivered) Hi, I tried to cross-compile libgpg-error-1.38 configured in a separate directory, and it fails at two points. First, it can't redirect output to src/lock-obj-pub.native.h because src doesn't exist. Second, the script gen-lock-obj.sh was not included in the release archive. Can something like this be applied to fix it? Thanks. David configure.ac | 1 + src/Makefile.am | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/configure.ac b/configure.ac index 6bd634b..1dd6489 100644 --- a/configure.ac +++ b/configure.ac @@ -598,6 +598,7 @@ if test x$cross_compiling = xyes; then case $host in *-*-linux*) lock_obj_h_generated=yes + mkdir src LOCK_ABI_VERSION=1 host=$host host_alias=$host_alias \ CC=$CC OBJDUMP=$host_alias-objdump \ ac_ext=$ac_ext ac_objext=$ac_objext \ diff --git a/src/Makefile.am b/src/Makefile.am index 7e36d69..b2bffd2 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -102,7 +102,7 @@ EXTRA_DIST = mkstrtable.awk err-sources.h.in err-codes.h.in \ gpg-error.vers gpg-error.def.in \ versioninfo.rc.in gpg-error.w32-manifest.in \ gpg-error-config-test.sh gpg-error.pc.in \ - $(lock_obj_pub) + gen-lock-obj.sh $(lock_obj_pub) BUILT_SOURCES = $(srcdir)/err-sources.h $(srcdir)/err-codes.h \ code-to-errno.h code-from-errno.h \ -- 2.26.2 From steffen at sdaoden.eu Mon Jun 1 16:56:13 2020 From: steffen at sdaoden.eu (Steffen Nurpmeso) Date: Mon, 01 Jun 2020 16:56:13 +0200 Subject: libgcrypt: do not test for /dev/* files if getentropy is available Message-ID: <20200601145613.JM0At%steffen@sdaoden.eu> Hello. Ha, i just realized that i am indeed subscribed to gnupg-devel, so i copy over the patch i had posted to gnupg-users@ in thread libgcrypt: random source via library on Linux? but all united in one. Ciao, --steffen | |Der Kragenbaer, The moon bear, |der holt sich munter he cheerfully and one by one |einen nach dem anderen runter wa.ks himself off |(By Robert Gernhardt) -------------- next part -------------- A non-text attachment was scrubbed... Name: libgcrypt.diff Type: text/x-diff Size: 10252 bytes Desc: not available URL: From steffen at sdaoden.eu Tue Jun 2 14:03:35 2020 From: steffen at sdaoden.eu (Steffen Nurpmeso) Date: Tue, 02 Jun 2020 14:03:35 +0200 Subject: libgcrypt: random source via library on Linux? In-Reply-To: <20200601145613.JM0At%steffen@sdaoden.eu> Message-ID: <20200602120335.YC2OY%steffen@sdaoden.eu> Werner Koch via Gnupg-users wrote in <87y2p5pzkl.fsf at wheatstone.g10code.de>: |On Fri, 29 May 2020 17:54, Steffen Nurpmeso said: |> Looking at the source it seems libgcrypt knows about the Linux |> getrandom systemcall. Yet it does not seem to know about glibc's |> getrandom library function. | |Which was not available back then when I implemented support for |getrandom. Further; there is no guarantee that getrandom(2) is |supported on all machines. We care a lot about backward compatibility |and can't simply demand a certain Linux kernel or glibc version. That was understood even before seeing the file history. Sure. |> i would change, maybe with a new call-in to rndlinux.c which |> should be made responsible for Linux-only environmental detections | |You don't change audited RNG code if there is not a very good reason for |that. Well, sometimes you do :) Getting rid of any file descriptors when the actual kernel allows to do so is i think an improvement that should be thought about. Localizing module-specific availability tests to the module itself is also a step in a good direction. I personally think that taking out the only optional available getentropy thing out of the random read loop to its own top-level code block is also a good thing, it was foreign matter and seems to have been patched in (it does not anticipate in block locals and uses continue; to skip over the originally sole loop content). The loop condition is "length != 0", this condition is tested multiple times in this function. Also, in the past getentropy has been tried each and every time the function was called, even if it was actually not available. Each time the loop ticked, to be more exact. That is really hard. With the patch this is a setup condition just as the availability test for /dev/*random. (Which is not even tried occasionally.) The change in _gcry_rngdrbg_close_fds() is of course totally unrelated, but i want to point out that all other functions which call _gcry_rndlinux_gather_random() directly do the locking cycle even if USE_RNDLINUX is not defined. (In fact i personally would move all the locks into the #ifdef but i have no idea of this codebase :-).) I admit that RANDOM_CONF_ONLY_URANDOM functionality is not restored, but for that the autotools stuff had to be adjusted, and the code would become more complex (getrandom has to be called directly on Linux, then, which needs sys/random.h to reach out for GRND_NONBLOCK). On OpenBSD however, getentropy() is identical to getrandom(,, GRND_RANDOM) on Linux. And i personally think [17f246c7] is not needed, the system call guarantees atomic delivery if <256 are requested, OpenBSD even forces this maximum. These sytem calls fail if the number of bytes cannot be delivered (atomically). Ciao from and to Germany! And to Japan and all the rest, too, of course, --steffen | |Der Kragenbaer, The moon bear, |der holt sich munter he cheerfully and one by one |einen nach dem anderen runter wa.ks himself off |(By Robert Gernhardt) From gniibe at fsij.org Wed Jun 3 02:11:44 2020 From: gniibe at fsij.org (NIIBE Yutaka) Date: Wed, 03 Jun 2020 09:11:44 +0900 Subject: [PATCH libgpg-error] build: Fix cross-compiling into a separate build dir. In-Reply-To: <87v9kakgne.fsf@gmail.com> References: <87v9kakgne.fsf@gmail.com> Message-ID: <87367df1of.fsf@iwagami.gniibe.org> Hello, David Michael wrote: > I tried to cross-compile libgpg-error-1.38 configured in a separate > directory, and it fails at two points. First, it can't redirect output > to src/lock-obj-pub.native.h because src doesn't exist. Second, the > script gen-lock-obj.sh was not included in the release archive. That's my fault. Indeed, those two are needed. I applied your change and pushed it to master. Thank you. -- From gnupg-devel at spodhuis.org Wed Jun 3 02:27:02 2020 From: gnupg-devel at spodhuis.org (Phil Pennock) Date: Tue, 2 Jun 2020 20:27:02 -0400 Subject: regression handling keyserver directives In-Reply-To: <87h7yc4q22.fsf_-_@wheatstone.g10code.de> References: <87h7yj7ywy.fsf@wheatstone.g10code.de> <20200322231046.GA15059@fullerene> <87v9mu98fa.fsf@fifthhorseman.net> <20200323221441.GA18635@fullerene> <87h7ye5dcg.fsf@wheatstone.g10code.de> <20200325022742.GA32589@fullerene> <87h7yc4q22.fsf_-_@wheatstone.g10code.de> Message-ID: <20200603002702.GA11913@fullerene> On 2020-03-25 at 12:44 +0100, Werner Koch wrote: > A candidate for GPG_ERR_INV_URI ("Invalid URI") is: > > - A HTTP proxy using "https:". We support only "http:", "socks4:", and > "socks5h". I revisited this late last night, building with the new libgpg-error. There's one more scenario which can lead to this error: building without TLS support. My build flow is a little too quiet, so I did not get to see the complaint that GnuTLS support was being disabled, because `nettle.pc` could not be found in the pkgconfig path. And that was because on sufficiently new OS releases, the `.pc` files of nettle (and hogweed) get installed into PREFIX/lib64/pkgconfig/ instead of PREFIX/lib/pkgconfig/. So with a $PKG_CONFIG_PATH which only included the `lib` form, GnuPG's configure script missed finding `nettle.pc`, auto-disabled TLS support without failing the configure, and so when keyservers respond with HTTP redirects to the `https:` schema, the build GnuPG errors out cryptically. I'm half sorry for the noise and half thinking that this highlights a couple of places for UX improvement. Thanks for the debugging assistance, -Phil From wk at gnupg.org Wed Jun 3 12:06:51 2020 From: wk at gnupg.org (Werner Koch) Date: Wed, 03 Jun 2020 12:06:51 +0200 Subject: regression handling keyserver directives In-Reply-To: <20200603002702.GA11913@fullerene> (Phil Pennock via Gnupg-devel's message of "Tue, 2 Jun 2020 20:27:02 -0400") References: <87h7yj7ywy.fsf@wheatstone.g10code.de> <20200322231046.GA15059@fullerene> <87v9mu98fa.fsf@fifthhorseman.net> <20200323221441.GA18635@fullerene> <87h7ye5dcg.fsf@wheatstone.g10code.de> <20200325022742.GA32589@fullerene> <87h7yc4q22.fsf_-_@wheatstone.g10code.de> <20200603002702.GA11913@fullerene> Message-ID: <87o8q0mpj8.fsf@wheatstone.g10code.de> On Tue, 2 Jun 2020 20:27, Phil Pennock said: > I'm half sorry for the noise and half thinking that this highlights a > couple of places for UX improvement. I see. One of the problems here is that most installations are using ntbtls and not gnutls. Thus this is the major test platform for us and we don't always test gnutls support. It might be better for maintenance if we do not allow to choose between gnutls and ntbtls. One problem on Unices is that OpenLDAP uses GnuTLS (or another system provided cryto lib) so we currently still end up with two complete crypto stacks in dirmngr. Shalom-Salam, Werner -- Die Gedanken sind frei. Ausnahmen regelt ein Bundesgesetz. -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 227 bytes Desc: not available URL: From James.Bottomley at HansenPartnership.com Sun Jun 14 19:26:46 2020 From: James.Bottomley at HansenPartnership.com (James Bottomley) Date: Sun, 14 Jun 2020 10:26:46 -0700 Subject: [PATCH 2/5] agent: separate out daemon handling infrastructure for reuse In-Reply-To: <20200614172649.23749-1-James.Bottomley@HansenPartnership.com> References: <20200614172649.23749-1-James.Bottomley@HansenPartnership.com> Message-ID: <20200614172649.23749-3-James.Bottomley@HansenPartnership.com> The model I'm using for a TPM daemon is the current scdaemon. That includes start and stop handlers plus liveness checks and an assuan socket generator. To avoid massive code duplication (and save me a lot of effort), I've elected to strip this code out of call-scd.c into a generic framework which can then be reused as is by the TPM handling daemon. Signed-off-by: James Bottomley --- v3 - add kill to daemon handling --- agent/Makefile.am | 1 + agent/agent.h | 37 ++- agent/call-daemon.c | 582 ++++++++++++++++++++++++++++++++++++++++++++ agent/call-scd.c | 552 ++--------------------------------------- agent/command-ssh.c | 10 +- agent/command.c | 4 +- agent/gpg-agent.c | 22 +- 7 files changed, 651 insertions(+), 557 deletions(-) create mode 100644 agent/call-daemon.c diff --git a/agent/Makefile.am b/agent/Makefile.am index ce29462b2..46770291a 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -53,6 +53,7 @@ gpg_agent_SOURCES = \ divert-scd.c \ cvt-openpgp.c cvt-openpgp.h \ call-scd.c \ + call-daemon.c \ learncard.c common_libs = $(libcommon) diff --git a/agent/agent.h b/agent/agent.h index c19c3efdc..e35ee1c91 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -29,6 +29,7 @@ #define map_assuan_err(a) \ map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a)) #include +#include #include #include "../common/util.h" @@ -53,6 +54,12 @@ this shouldn't be a problem in practice. */ #define MAX_PASSPHRASE_LEN 255 +/* The daemons we support. When you add a new daemon, add to + both the daemon_type and the daemon_modules array */ +enum daemon_type { + DAEMON_SCD, + DAEMON_MAX_TYPE, +}; /* A large struct name "opt" to keep global flags */ EXTERN_UNLESS_MAIN_MODULE @@ -79,10 +86,10 @@ struct /* Filename of the program to start as pinentry. */ const char *pinentry_program; - /* Filename of the program to handle smartcard tasks. */ - const char *scdaemon_program; + /* Filename of the program to handle daemon tasks. */ + const char *daemon_program[DAEMON_MAX_TYPE]; - int disable_scdaemon; /* Never use the SCdaemon. */ + int disable_daemon[DAEMON_MAX_TYPE]; /* Never use the daemon. */ int no_grab; /* Don't let the pinentry grab the keyboard */ @@ -203,7 +210,7 @@ struct ssh_control_file_s; typedef struct ssh_control_file_s *ssh_control_file_t; /* Forward reference for local definitions in call-scd.c. */ -struct scd_local_s; +struct daemon_local_s; /* Collection of data per session (aka connection). */ struct server_control_s @@ -223,8 +230,8 @@ struct server_control_s /* Private data of the server (command.c). */ struct server_local_s *server_local; - /* Private data of the SCdaemon (call-scd.c). */ - struct scd_local_s *scd_local; + /* Private data of the daemon (call-XXX.c). */ + struct daemon_local_s *d_local[DAEMON_MAX_TYPE]; /* Environment settings for the connection. */ session_env_t session_env; @@ -367,7 +374,7 @@ const char *get_agent_socket_name (void); const char *get_agent_ssh_socket_name (void); int get_agent_active_connection_count (void); #ifdef HAVE_W32_SYSTEM -void *get_agent_scd_notify_event (void); +void *get_agent_daemon_notify_event (void); #endif void agent_sighup_action (void); int map_pk_openpgp_to_gcry (int openpgp_algo); @@ -558,13 +565,18 @@ int divert_generic_cmd (ctrl_t ctrl, int divert_writekey (ctrl_t ctrl, int force, const char *serialno, const char *id, const char *keydata, size_t keydatalen); +/*-- call-daemon.c --*/ +int daemon_start (enum daemon_type type, ctrl_t ctrl); +assuan_context_t daemon_type_ctx (enum daemon_type type, ctrl_t ctrl); +int daemon_unlock (enum daemon_type type, ctrl_t ctrl, int rc); +void initialize_module_daemon (void); +void agent_daemon_dump_state (void); +int agent_daemon_check_running (enum daemon_type type); +void agent_daemon_check_aliveness (void); +void agent_reset_daemon (ctrl_t ctrl); +void agent_kill_daemon (enum daemon_type type); /*-- call-scd.c --*/ -void initialize_module_call_scd (void); -void agent_scd_dump_state (void); -int agent_scd_check_running (void); -void agent_scd_check_aliveness (void); -int agent_reset_scd (ctrl_t ctrl); int agent_card_learn (ctrl_t ctrl, void (*kpinfo_cb)(void*, const char *), void *kpinfo_cb_arg, @@ -606,7 +618,6 @@ int agent_card_scd (ctrl_t ctrl, const char *cmdline, int (*getpin_cb)(void *, const char *, const char *, char*, size_t), void *getpin_cb_arg, void *assuan_context); -void agent_card_killscd (void); /*-- learncard.c --*/ diff --git a/agent/call-daemon.c b/agent/call-daemon.c new file mode 100644 index 000000000..977228194 --- /dev/null +++ b/agent/call-daemon.c @@ -0,0 +1,582 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SIGNAL_H +# include +#endif +#include +#include +#ifndef HAVE_W32_SYSTEM +#include +#endif +#include + +#include "agent.h" +#include +#include "../common/strlist.h" + +/* daemon type to module mapping */ +static const int daemon_modules[DAEMON_MAX_TYPE] = { + [DAEMON_SCD] = GNUPG_MODULE_NAME_SCDAEMON, +}; + +/* Definition of module local data of the CTRL structure. */ +struct daemon_local_s +{ + /* We keep a list of all allocated context with an anchor at + DAEMON_LOCAL_LIST (see below). */ + struct daemon_local_s *next_local; + + /* We need to get back to the ctrl object actually referencing this + structure. This is really an awkward way of enumerating the local + contexts. A much cleaner way would be to keep a global list of + ctrl objects to enumerate them. */ + ctrl_t ctrl_backlink; + + /* and also to get back to the global structure */ + struct daemon_global_s *g; + + assuan_context_t ctx; /* NULL or session context for the daemon + used with this connection. */ + int locked; /* This flag is used to assert proper use of + start_daemon and unlock_daemon. */ + +}; + + +/* Primary holder of all the started daemons */ +struct daemon_global_s +{ + /* To keep track of all active daemon contexts, we keep a linked list + anchored at this variable. */ + struct daemon_local_s *local_list; + /* A malloced string with the name of the socket to be used for + additional connections. May be NULL if not provided by + daemon. */ + char *socket_name; + + /* The context of the primary connection. This is also used as a flag + to indicate whether the daemon has been started. */ + assuan_context_t primary_ctx; + + /* To allow reuse of the primary connection, the following flag is set + to true if the primary context has been reset and is not in use by + any connection. */ + int primary_ctx_reusable; +}; + +static struct daemon_global_s daemon_global[DAEMON_MAX_TYPE]; + + +/* A Mutex used inside the start_daemon function. */ +static npth_mutex_t start_daemon_lock; + +/* The unlock_daemon function shall be called after having accessed the + daemon. It is currently not very useful but gives an opportunity to + keep track of connections currently calling daemon. Note that the + "lock" operation is done by the start_daemon() function which must be + called and error checked before any daemon operation. CTRL is the + usual connection context and RC the error code to be passed trhough + the function. */ +int +daemon_unlock (enum daemon_type type, ctrl_t ctrl, int rc) +{ + if (ctrl->d_local[type]->locked != 1) + { + log_error ("unlock_daemon: invalid lock count (%d)\n", + ctrl->d_local[type]->locked); + if (!rc) + rc = gpg_error (GPG_ERR_INTERNAL); + } + ctrl->d_local[type]->locked = 0; + return rc; +} + +/* To make sure we leave no secrets in our image after forking of the + daemon, we use this callback. */ +static void +atfork_cb (void *opaque, int where) +{ + (void)opaque; + + if (!where) + gcry_control (GCRYCTL_TERM_SECMEM); +} + +/* Fork off the daemon if this has not already been done. Lock the + daemon and make sure that a proper context has been setup in CTRL. + This function might also lock the daemon, which means that the + caller must call unlock_daemon after this function has returned + success and the actual Assuan transaction been done. */ +int +daemon_start (enum daemon_type type, ctrl_t ctrl) +{ + gpg_error_t err = 0; + const char *pgmname; + assuan_context_t ctx = NULL; + const char *argv[5]; + assuan_fd_t no_close_list[3]; + int i; + int rc; + char *abs_homedir = NULL; + struct daemon_global_s *g = &daemon_global[type]; + const char *name = gnupg_module_name (daemon_modules[type]); + + assert (type < DAEMON_MAX_TYPE); + /* if this fails, you forgot to add your new type to daemon_modules */ + assert (DAEMON_MAX_TYPE == DIM (daemon_modules)); + + if (opt.disable_daemon[type]) + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + /* If this is the first call for this session, setup the local data + structure. */ + if (!ctrl->d_local[type]) + { + ctrl->d_local[type] = xtrycalloc (1, sizeof *ctrl->d_local[type]); + if (!ctrl->d_local[type]) + return gpg_error_from_syserror (); + ctrl->d_local[type]->ctrl_backlink = ctrl; + ctrl->d_local[type]->g = g; + ctrl->d_local[type]->next_local = g->local_list; + g->local_list = ctrl->d_local[type]; + } + + + /* Assert that the lock count is as expected. */ + if (ctrl->d_local[type]->locked) + { + log_error ("start_daemon: invalid lock count (%d)\n", + ctrl->d_local[type]->locked); + return gpg_error (GPG_ERR_INTERNAL); + } + ctrl->d_local[type]->locked++; + + if (ctrl->d_local[type]->ctx) + return 0; /* Okay, the context is fine. We used to test for an + alive context here and do an disconnect. Now that we + have a ticker function to check for it, it is easier + not to check here but to let the connection run on an + error instead. */ + + + /* We need to protect the following code. */ + rc = npth_mutex_lock (&start_daemon_lock); + if (rc) + { + log_error ("failed to acquire the start_daemon lock: %s\n", + strerror (rc)); + return gpg_error (GPG_ERR_INTERNAL); + } + + /* Check whether the pipe server has already been started and in + this case either reuse a lingering pipe connection or establish a + new socket based one. */ + if (g->primary_ctx && g->primary_ctx_reusable) + { + ctx = g->primary_ctx; + g->primary_ctx_reusable = 0; + if (opt.verbose) + log_info ("new connection to %s daemon established (reusing)\n", + name); + goto leave; + } + + rc = assuan_new (&ctx); + if (rc) + { + log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); + err = rc; + goto leave; + } + + if (g->socket_name) + { + rc = assuan_socket_connect (ctx, g->socket_name, 0, 0); + if (rc) + { + log_error ("can't connect to socket '%s': %s\n", + g->socket_name, gpg_strerror (rc)); + err = gpg_error (GPG_ERR_NO_SCDAEMON); + goto leave; + } + + if (opt.verbose) + log_info ("new connection to %s daemon established\n", + name); + goto leave; + } + + if (g->primary_ctx) + { + log_info ("%s daemon is running but won't accept further connections\n", + name); + err = gpg_error (GPG_ERR_NO_SCDAEMON); + goto leave; + } + + /* Nope, it has not been started. Fire it up now. */ + if (opt.verbose) + log_info ("no running %s daemon - starting it\n", name); + + if (fflush (NULL)) + { +#ifndef HAVE_W32_SYSTEM + err = gpg_error_from_syserror (); +#endif + log_error ("error flushing pending output: %s\n", strerror (errno)); + /* At least Windows XP fails here with EBADF. According to docs + and Wine an fflush(NULL) is the same as _flushall. However + the Wime implementation does not flush stdin,stdout and stderr + - see above. Lets try to ignore the error. */ +#ifndef HAVE_W32_SYSTEM + goto leave; +#endif + } + + if (!opt.daemon_program[type] || !*opt.daemon_program[type]) + opt.daemon_program[type] = gnupg_module_name (daemon_modules[type]); + + if ( !(pgmname = strrchr (opt.daemon_program[type], '/'))) + pgmname = opt.daemon_program[type]; + else + pgmname++; + + argv[0] = pgmname; + argv[1] = "--multi-server"; + if (gnupg_default_homedir_p ()) + argv[2] = NULL; + else + { + abs_homedir = make_absfilename_try (gnupg_homedir (), NULL); + if (!abs_homedir) + { + log_error ("error building filename: %s\n", + gpg_strerror (gpg_error_from_syserror ())); + goto leave; + } + + argv[2] = "--homedir"; + argv[3] = abs_homedir; + argv[4] = NULL; + } + + i=0; + if (!opt.running_detached) + { + if (log_get_fd () != -1) + no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); + no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); + } + no_close_list[i] = ASSUAN_INVALID_FD; + + /* Connect to the daemon and perform initial handshaking. Use + detached flag so that under Windows DAEMON does not show up a + new window. */ + rc = assuan_pipe_connect (ctx, opt.daemon_program[type], argv, + no_close_list, atfork_cb, NULL, + ASSUAN_PIPE_CONNECT_DETACHED); + if (rc) + { + log_error ("can't connect to the daemon %s: %s\n", + name, gpg_strerror (rc)); + err = gpg_error (GPG_ERR_NO_SCDAEMON); + goto leave; + } + + if (opt.verbose) + log_debug ("first connection to daemon %s established\n", name); + + + /* Get the name of the additional socket opened by daemon. */ + { + membuf_t data; + unsigned char *databuf; + size_t datalen; + + xfree (g->socket_name); + g->socket_name = NULL; + init_membuf (&data, 256); + assuan_transact (ctx, "GETINFO socket_name", + put_membuf_cb, &data, NULL, NULL, NULL, NULL); + + databuf = get_membuf (&data, &datalen); + if (databuf && datalen) + { + g->socket_name = xtrymalloc (datalen + 1); + if (!g->socket_name) + log_error ("warning: can't store socket name: %s\n", + strerror (errno)); + else + { + memcpy (g->socket_name, databuf, datalen); + g->socket_name[datalen] = 0; + if (DBG_IPC) + log_debug ("additional connections at '%s'\n", g->socket_name); + } + } + xfree (databuf); + } + + /* Tell the daemon we want him to send us an event signal. We + don't support this for W32CE. */ +#ifndef HAVE_W32CE_SYSTEM + if (opt.sigusr2_enabled) + { + char buf[100]; + +#ifdef HAVE_W32_SYSTEM + snprintf (buf, sizeof buf, "OPTION event-signal=%lx", + (unsigned long)get_agent_daemon_notify_event ()); +#else + snprintf (buf, sizeof buf, "OPTION event-signal=%d", SIGUSR2); +#endif + assuan_transact (ctx, buf, NULL, NULL, NULL, NULL, NULL, NULL); + } +#endif /*HAVE_W32CE_SYSTEM*/ + + g->primary_ctx = ctx; + g->primary_ctx_reusable = 0; + + leave: + xfree (abs_homedir); + if (err) + { + daemon_unlock (type, ctrl, err); + if (ctx) + assuan_release (ctx); + } + else + { + ctrl->d_local[type]->ctx = ctx; + } + rc = npth_mutex_unlock (&start_daemon_lock); + if (rc) + log_error ("failed to release the start_daemon lock: %s\n", strerror (rc)); + return err; +} + + +/* This function must be called once to initialize this module. This + has to be done before a second thread is spawned. We can't do the + static initialization because NPth emulation code might not be able + to do a static init; in particular, it is not possible for W32. */ +void +initialize_module_daemon (void) +{ + static int initialized; + int err; + + if (!initialized) + { + err = npth_mutex_init (&start_daemon_lock, NULL); + if (err) + log_fatal ("error initializing mutex: %s\n", strerror (err)); + initialized = 1; + } +} + + +/* This function may be called to print information pertaining to the + current state of this module to the log. */ +void +agent_daemon_dump_state (void) +{ + int i; + + for (i = 0; i < DAEMON_MAX_TYPE; i++) { + struct daemon_global_s *g = &daemon_global[i]; + + log_info ("agent_daemon_dump_state: name %s primary_ctx=%p pid=%ld reusable=%d\n", + gnupg_module_name (daemon_modules[i]), + g->primary_ctx, + (long)assuan_get_pid (g->primary_ctx), + g->primary_ctx_reusable); + if (g->socket_name) + log_info ("agent_daemon_dump_state: socket='%s'\n", g->socket_name); + } +} + + +/* Check whether the daemon is active. This is a fast check without + any locking and might give a wrong result if another thread is about + to start the daemon or the daemon is about to be stopped.. */ +int +agent_daemon_check_running (enum daemon_type type) +{ + return !!daemon_global[type].primary_ctx; +} + + +/* Check whether the daemons are still alive and clean it up if not. */ +void +agent_daemon_check_aliveness (void) +{ + pid_t pid; +#ifdef HAVE_W32_SYSTEM + DWORD rc; +#else + int rc; +#endif + struct timespec abstime; + int err; + int i; + + for (i = 0; i < DAEMON_MAX_TYPE; i++) { + struct daemon_global_s *g = &daemon_global[i]; + + if (!g->primary_ctx) + continue; /* No daemon running. */ + + /* This is not a critical function so we use a short timeout while + acquiring the lock. */ + npth_clock_gettime (&abstime); + abstime.tv_sec += 1; + err = npth_mutex_timedlock (&start_daemon_lock, &abstime); + if (err) + { + if (err == ETIMEDOUT) + { + if (opt.verbose > 1) + log_info ("failed to acquire the start_daemon lock while" + " doing an aliveness check: %s\n", strerror (err)); + } + else + log_error ("failed to acquire the start_daemon lock while" + " doing an aliveness check: %s\n", strerror (err)); + return; + } + + if (g->primary_ctx) + { + pid = assuan_get_pid (g->primary_ctx); +#ifdef HAVE_W32_SYSTEM + /* If we have a PID we disconnect if either GetExitProcessCode + fails or if ir returns the exit code of the daemon. 259 is + the error code for STILL_ALIVE. */ + if (pid != (pid_t)(void*)(-1) && pid + && (!GetExitCodeProcess ((HANDLE)pid, &rc) || rc != 259)) +#else + if (pid != (pid_t)(-1) && pid + && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) +#endif + { + /* Okay, daemon died. Disconnect the primary connection + now but take care that it won't do another wait. Also + cleanup all other connections and release their + resources. The next use will start a new daemon then. + Due to the use of the START_SCD_LOCAL we are sure that + none of these context are actually in use. */ + struct daemon_local_s *sl; + + assuan_set_flag (g->primary_ctx, ASSUAN_NO_WAITPID, 1); + assuan_release (g->primary_ctx); + + for (sl=g->local_list; sl; sl = sl->next_local) + { + if (sl->ctx) + { + if (sl->ctx != g->primary_ctx) + assuan_release (sl->ctx); + sl->ctx = NULL; + } + } + + g->primary_ctx = NULL; + g->primary_ctx_reusable = 0; + + xfree (g->socket_name); + g->socket_name = NULL; + } + } + + err = npth_mutex_unlock (&start_daemon_lock); + if (err) + log_error ("failed to release the start_daemon lock while" + " doing the aliveness check: %s\n", strerror (err)); + } +} + +/* send a kill command to the daemon */ +void +agent_kill_daemon (enum daemon_type type) +{ + struct daemon_global_s *g = &daemon_global[type]; + + if (g->primary_ctx == NULL) + return; + assuan_transact (g->primary_ctx, "KILLSCD", + NULL, NULL, NULL, NULL, NULL, NULL); +} + +/* Reset the daemon if it has been used. Actually it is not a reset but + a cleanup of resources used by the current connection. */ +void +agent_reset_daemon (ctrl_t ctrl) +{ + int i; + + for (i = 0; i < DAEMON_MAX_TYPE; i++) { + if (ctrl->d_local[i]) + { + struct daemon_global_s *g = ctrl->d_local[i]->g; + + if (ctrl->d_local[i]->ctx) + { + /* We can't disconnect the primary context because libassuan + does a waitpid on it and thus the system would hang. + Instead we send a reset and keep that connection for + reuse. */ + if (ctrl->d_local[i]->ctx == g->primary_ctx) + { + /* Send a RESTART to the daemon. This is required for the + primary connection as a kind of virtual EOF; we don't + have another way to tell it that the next command + should be viewed as if a new connection has been + made. For the non-primary connections this is not + needed as we simply close the socket. We don't check + for an error here because the RESTART may fail for + example if the daemon has already been terminated. + Anyway, we need to set the reusable flag to make sure + that the aliveness check can clean it up. */ + assuan_transact (g->primary_ctx, "RESTART", + NULL, NULL, NULL, NULL, NULL, NULL); + g->primary_ctx_reusable = 1; + } + else + assuan_release (ctrl->d_local[i]->ctx); + ctrl->d_local[i]->ctx = NULL; + } + + /* Remove the local context from our list and release it. */ + if (!g->local_list) + BUG (); + else if (g->local_list == ctrl->d_local[i]) + g->local_list = ctrl->d_local[i]->next_local; + else + { + struct daemon_local_s *sl; + + for (sl=g->local_list; sl->next_local; sl = sl->next_local) + if (sl->next_local == ctrl->d_local[i]) + break; + if (!sl->next_local) + BUG (); + sl->next_local = ctrl->d_local[i]->next_local; + } + xfree (ctrl->d_local[i]); + ctrl->d_local[i] = NULL; + } + } +} + +assuan_context_t +daemon_type_ctx (enum daemon_type type, ctrl_t ctrl) +{ + return ctrl->d_local[type]->ctx; +} diff --git a/agent/call-scd.c b/agent/call-scd.c index 6438693af..5aaab20a5 100644 --- a/agent/call-scd.c +++ b/agent/call-scd.c @@ -47,27 +47,6 @@ #define MAX_OPEN_FDS 20 #endif -/* Definition of module local data of the CTRL structure. */ -struct scd_local_s -{ - /* We keep a list of all allocated context with an anchor at - SCD_LOCAL_LIST (see below). */ - struct scd_local_s *next_local; - - /* We need to get back to the ctrl object actually referencing this - structure. This is really an awkward way of enumerating the local - contexts. A much cleaner way would be to keep a global list of - ctrl objects to enumerate them. */ - ctrl_t ctrl_backlink; - - assuan_context_t ctx; /* NULL or session context for the SCdaemon - used with this connection. */ - int locked; /* This flag is used to assert proper use of - start_scd and unlock_scd. */ - -}; - - /* Callback parameter for learn card */ struct learn_parm_s { @@ -96,495 +75,25 @@ struct inq_needpin_parm_s }; -/* To keep track of all active SCD contexts, we keep a linked list - anchored at this variable. */ -static struct scd_local_s *scd_local_list; - -/* A Mutex used inside the start_scd function. */ -static npth_mutex_t start_scd_lock; - -/* A malloced string with the name of the socket to be used for - additional connections. May be NULL if not provided by - SCdaemon. */ -static char *socket_name; - -/* The context of the primary connection. This is also used as a flag - to indicate whether the scdaemon has been started. */ -static assuan_context_t primary_scd_ctx; - -/* To allow reuse of the primary connection, the following flag is set - to true if the primary context has been reset and is not in use by - any connection. */ -static int primary_scd_ctx_reusable; - /* Local prototypes. */ - - - - -/* This function must be called once to initialize this module. This - has to be done before a second thread is spawned. We can't do the - static initialization because NPth emulation code might not be able - to do a static init; in particular, it is not possible for W32. */ -void -initialize_module_call_scd (void) -{ - static int initialized; - int err; - - if (!initialized) - { - err = npth_mutex_init (&start_scd_lock, NULL); - if (err) - log_fatal ("error initializing mutex: %s\n", strerror (err)); - initialized = 1; - } -} - - -/* This function may be called to print information pertaining to the - current state of this module to the log. */ -void -agent_scd_dump_state (void) -{ - log_info ("agent_scd_dump_state: primary_scd_ctx=%p pid=%ld reusable=%d\n", - primary_scd_ctx, - (long)assuan_get_pid (primary_scd_ctx), - primary_scd_ctx_reusable); - if (socket_name) - log_info ("agent_scd_dump_state: socket='%s'\n", socket_name); -} - - -/* The unlock_scd function shall be called after having accessed the - SCD. It is currently not very useful but gives an opportunity to - keep track of connections currently calling SCD. Note that the - "lock" operation is done by the start_scd() function which must be - called and error checked before any SCD operation. CTRL is the - usual connection context and RC the error code to be passed trhough - the function. */ -static int -unlock_scd (ctrl_t ctrl, int rc) -{ - if (ctrl->scd_local->locked != 1) - { - log_error ("unlock_scd: invalid lock count (%d)\n", - ctrl->scd_local->locked); - if (!rc) - rc = gpg_error (GPG_ERR_INTERNAL); - } - ctrl->scd_local->locked = 0; - return rc; -} - -/* To make sure we leave no secrets in our image after forking of the - scdaemon, we use this callback. */ -static void -atfork_cb (void *opaque, int where) -{ - (void)opaque; - - if (!where) - gcry_control (GCRYCTL_TERM_SECMEM); -} - - -/* Fork off the SCdaemon if this has not already been done. Lock the - daemon and make sure that a proper context has been setup in CTRL. - This function might also lock the daemon, which means that the - caller must call unlock_scd after this function has returned - success and the actual Assuan transaction been done. */ static int start_scd (ctrl_t ctrl) { - gpg_error_t err = 0; - const char *pgmname; - assuan_context_t ctx = NULL; - const char *argv[5]; - assuan_fd_t no_close_list[3]; - int i; - int rc; - char *abs_homedir = NULL; - - if (opt.disable_scdaemon) - return gpg_error (GPG_ERR_NOT_SUPPORTED); - - /* If this is the first call for this session, setup the local data - structure. */ - if (!ctrl->scd_local) - { - ctrl->scd_local = xtrycalloc (1, sizeof *ctrl->scd_local); - if (!ctrl->scd_local) - return gpg_error_from_syserror (); - ctrl->scd_local->ctrl_backlink = ctrl; - ctrl->scd_local->next_local = scd_local_list; - scd_local_list = ctrl->scd_local; - } - - - /* Assert that the lock count is as expected. */ - if (ctrl->scd_local->locked) - { - log_error ("start_scd: invalid lock count (%d)\n", - ctrl->scd_local->locked); - return gpg_error (GPG_ERR_INTERNAL); - } - ctrl->scd_local->locked++; - - if (ctrl->scd_local->ctx) - return 0; /* Okay, the context is fine. We used to test for an - alive context here and do an disconnect. Now that we - have a ticker function to check for it, it is easier - not to check here but to let the connection run on an - error instead. */ - - - /* We need to protect the following code. */ - rc = npth_mutex_lock (&start_scd_lock); - if (rc) - { - log_error ("failed to acquire the start_scd lock: %s\n", - strerror (rc)); - return gpg_error (GPG_ERR_INTERNAL); - } - - /* Check whether the pipe server has already been started and in - this case either reuse a lingering pipe connection or establish a - new socket based one. */ - if (primary_scd_ctx && primary_scd_ctx_reusable) - { - ctx = primary_scd_ctx; - primary_scd_ctx_reusable = 0; - if (opt.verbose) - log_info ("new connection to SCdaemon established (reusing)\n"); - goto leave; - } - - rc = assuan_new (&ctx); - if (rc) - { - log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc)); - err = rc; - goto leave; - } - - if (socket_name) - { - rc = assuan_socket_connect (ctx, socket_name, 0, 0); - if (rc) - { - log_error ("can't connect to socket '%s': %s\n", - socket_name, gpg_strerror (rc)); - err = gpg_error (GPG_ERR_NO_SCDAEMON); - goto leave; - } - - if (opt.verbose) - log_info ("new connection to SCdaemon established\n"); - goto leave; - } - - if (primary_scd_ctx) - { - log_info ("SCdaemon is running but won't accept further connections\n"); - err = gpg_error (GPG_ERR_NO_SCDAEMON); - goto leave; - } - - /* Nope, it has not been started. Fire it up now. */ - if (opt.verbose) - log_info ("no running SCdaemon - starting it\n"); - - if (fflush (NULL)) - { -#ifndef HAVE_W32_SYSTEM - err = gpg_error_from_syserror (); -#endif - log_error ("error flushing pending output: %s\n", strerror (errno)); - /* At least Windows XP fails here with EBADF. According to docs - and Wine an fflush(NULL) is the same as _flushall. However - the Wime implementation does not flush stdin,stdout and stderr - - see above. Lets try to ignore the error. */ -#ifndef HAVE_W32_SYSTEM - goto leave; -#endif - } - - if (!opt.scdaemon_program || !*opt.scdaemon_program) - opt.scdaemon_program = gnupg_module_name (GNUPG_MODULE_NAME_SCDAEMON); - if ( !(pgmname = strrchr (opt.scdaemon_program, '/'))) - pgmname = opt.scdaemon_program; - else - pgmname++; - - argv[0] = pgmname; - argv[1] = "--multi-server"; - if (gnupg_default_homedir_p ()) - argv[2] = NULL; - else - { - abs_homedir = make_absfilename_try (gnupg_homedir (), NULL); - if (!abs_homedir) - { - log_error ("error building filename: %s\n", - gpg_strerror (gpg_error_from_syserror ())); - goto leave; - } - - argv[2] = "--homedir"; - argv[3] = abs_homedir; - argv[4] = NULL; - } - - i=0; - if (!opt.running_detached) - { - if (log_get_fd () != -1) - no_close_list[i++] = assuan_fd_from_posix_fd (log_get_fd ()); - no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr)); - } - no_close_list[i] = ASSUAN_INVALID_FD; - - /* Connect to the scdaemon and perform initial handshaking. Use - detached flag so that under Windows SCDAEMON does not show up a - new window. */ - rc = assuan_pipe_connect (ctx, opt.scdaemon_program, argv, - no_close_list, atfork_cb, NULL, - ASSUAN_PIPE_CONNECT_DETACHED); - if (rc) - { - log_error ("can't connect to the SCdaemon: %s\n", - gpg_strerror (rc)); - err = gpg_error (GPG_ERR_NO_SCDAEMON); - goto leave; - } - - if (opt.verbose) - log_debug ("first connection to SCdaemon established\n"); - - - /* Get the name of the additional socket opened by scdaemon. */ - { - membuf_t data; - unsigned char *databuf; - size_t datalen; - - xfree (socket_name); - socket_name = NULL; - init_membuf (&data, 256); - assuan_transact (ctx, "GETINFO socket_name", - put_membuf_cb, &data, NULL, NULL, NULL, NULL); - - databuf = get_membuf (&data, &datalen); - if (databuf && datalen) - { - socket_name = xtrymalloc (datalen + 1); - if (!socket_name) - log_error ("warning: can't store socket name: %s\n", - strerror (errno)); - else - { - memcpy (socket_name, databuf, datalen); - socket_name[datalen] = 0; - if (DBG_IPC) - log_debug ("additional connections at '%s'\n", socket_name); - } - } - xfree (databuf); - } - - /* Tell the scdaemon we want him to send us an event signal. We - don't support this for W32CE. */ -#ifndef HAVE_W32CE_SYSTEM - if (opt.sigusr2_enabled) - { - char buf[100]; - -#ifdef HAVE_W32_SYSTEM - snprintf (buf, sizeof buf, "OPTION event-signal=%p", - get_agent_scd_notify_event ()); -#else - snprintf (buf, sizeof buf, "OPTION event-signal=%d", SIGUSR2); -#endif - assuan_transact (ctx, buf, NULL, NULL, NULL, NULL, NULL, NULL); - } -#endif /*HAVE_W32CE_SYSTEM*/ - - primary_scd_ctx = ctx; - primary_scd_ctx_reusable = 0; - - leave: - xfree (abs_homedir); - if (err) - { - unlock_scd (ctrl, err); - if (ctx) - assuan_release (ctx); - } - else - { - ctrl->scd_local->ctx = ctx; - } - rc = npth_mutex_unlock (&start_scd_lock); - if (rc) - log_error ("failed to release the start_scd lock: %s\n", strerror (rc)); - return err; -} - - -/* Check whether the SCdaemon is active. This is a fast check without - any locking and might give a wrong result if another thread is about - to start the daemon or the daemon is about to be stopped.. */ -int -agent_scd_check_running (void) -{ - return !!primary_scd_ctx; + return daemon_start (DAEMON_SCD, ctrl); } - -/* Check whether the Scdaemon is still alive and clean it up if not. */ -void -agent_scd_check_aliveness (void) +static int +unlock_scd (ctrl_t ctrl, gpg_error_t err) { - pid_t pid; -#ifdef HAVE_W32_SYSTEM - DWORD rc; -#else - int rc; -#endif - struct timespec abstime; - int err; - - if (!primary_scd_ctx) - return; /* No scdaemon running. */ - - /* This is not a critical function so we use a short timeout while - acquiring the lock. */ - npth_clock_gettime (&abstime); - abstime.tv_sec += 1; - err = npth_mutex_timedlock (&start_scd_lock, &abstime); - if (err) - { - if (err == ETIMEDOUT) - { - if (opt.verbose > 1) - log_info ("failed to acquire the start_scd lock while" - " doing an aliveness check: %s\n", strerror (err)); - } - else - log_error ("failed to acquire the start_scd lock while" - " doing an aliveness check: %s\n", strerror (err)); - return; - } - - if (primary_scd_ctx) - { - pid = assuan_get_pid (primary_scd_ctx); -#ifdef HAVE_W32_SYSTEM - /* If we have a PID we disconnect if either GetExitProcessCode - fails or if ir returns the exit code of the scdaemon. 259 is - the error code for STILL_ALIVE. */ - if (pid != (pid_t)(void*)(-1) && pid - && (!GetExitCodeProcess ((HANDLE)pid, &rc) || rc != 259)) -#else - if (pid != (pid_t)(-1) && pid - && ((rc=waitpid (pid, NULL, WNOHANG))==-1 || (rc == pid)) ) -#endif - { - /* Okay, scdaemon died. Disconnect the primary connection - now but take care that it won't do another wait. Also - cleanup all other connections and release their - resources. The next use will start a new daemon then. - Due to the use of the START_SCD_LOCAL we are sure that - none of these context are actually in use. */ - struct scd_local_s *sl; - - assuan_set_flag (primary_scd_ctx, ASSUAN_NO_WAITPID, 1); - assuan_release (primary_scd_ctx); - - for (sl=scd_local_list; sl; sl = sl->next_local) - { - if (sl->ctx) - { - if (sl->ctx != primary_scd_ctx) - assuan_release (sl->ctx); - sl->ctx = NULL; - } - } - - primary_scd_ctx = NULL; - primary_scd_ctx_reusable = 0; - - xfree (socket_name); - socket_name = NULL; - } - } - - err = npth_mutex_unlock (&start_scd_lock); - if (err) - log_error ("failed to release the start_scd lock while" - " doing the aliveness check: %s\n", strerror (err)); + return daemon_unlock (DAEMON_SCD, ctrl, err); } - - -/* Reset the SCD if it has been used. Actually it is not a reset but - a cleanup of resources used by the current connection. */ -int -agent_reset_scd (ctrl_t ctrl) +static assuan_context_t +daemon_ctx (ctrl_t ctrl) { - if (ctrl->scd_local) - { - if (ctrl->scd_local->ctx) - { - /* We can't disconnect the primary context because libassuan - does a waitpid on it and thus the system would hang. - Instead we send a reset and keep that connection for - reuse. */ - if (ctrl->scd_local->ctx == primary_scd_ctx) - { - /* Send a RESTART to the SCD. This is required for the - primary connection as a kind of virtual EOF; we don't - have another way to tell it that the next command - should be viewed as if a new connection has been - made. For the non-primary connections this is not - needed as we simply close the socket. We don't check - for an error here because the RESTART may fail for - example if the scdaemon has already been terminated. - Anyway, we need to set the reusable flag to make sure - that the aliveness check can clean it up. */ - assuan_transact (primary_scd_ctx, "RESTART", - NULL, NULL, NULL, NULL, NULL, NULL); - primary_scd_ctx_reusable = 1; - } - else - assuan_release (ctrl->scd_local->ctx); - ctrl->scd_local->ctx = NULL; - } - - /* Remove the local context from our list and release it. */ - if (!scd_local_list) - BUG (); - else if (scd_local_list == ctrl->scd_local) - scd_local_list = ctrl->scd_local->next_local; - else - { - struct scd_local_s *sl; - - for (sl=scd_local_list; sl->next_local; sl = sl->next_local) - if (sl->next_local == ctrl->scd_local) - break; - if (!sl->next_local) - BUG (); - sl->next_local = ctrl->scd_local->next_local; - } - xfree (ctrl->scd_local); - ctrl->scd_local = NULL; - } - - return 0; + return daemon_type_ctx (DAEMON_SCD, ctrl); } @@ -641,7 +150,7 @@ agent_card_learn (ctrl_t ctrl, parm.certinfo_cb_arg = certinfo_cb_arg; parm.sinfo_cb = sinfo_cb; parm.sinfo_cb_arg = sinfo_cb_arg; - rc = assuan_transact (ctrl->scd_local->ctx, "LEARN --force", + rc = assuan_transact (daemon_ctx(ctrl), "LEARN --force", NULL, NULL, NULL, NULL, learn_status_cb, &parm); if (rc) @@ -701,7 +210,7 @@ agent_card_serialno (ctrl_t ctrl, char **r_serialno, const char *demand) else snprintf (line, DIM(line), "SERIALNO --demand=%s", demand); - rc = assuan_transact (ctrl->scd_local->ctx, line, + rc = assuan_transact (daemon_ctx (ctrl), line, NULL, NULL, NULL, NULL, get_serialno_cb, &serialno); if (rc) @@ -838,13 +347,13 @@ agent_card_pksign (ctrl_t ctrl, bin2hex (indata, indatalen, stpcpy (line, "SETDATA ")); - rc = assuan_transact (ctrl->scd_local->ctx, line, + rc = assuan_transact (daemon_ctx (ctrl), line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_scd (ctrl, rc); init_membuf (&data, 1024); - inqparm.ctx = ctrl->scd_local->ctx; + inqparm.ctx = daemon_ctx (ctrl); inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; inqparm.getpin_cb_desc = desc_text; @@ -857,7 +366,7 @@ agent_card_pksign (ctrl_t ctrl, else snprintf (line, sizeof line, "PKSIGN %s %s", hash_algo_option (mdalgo), keyid); - rc = assuan_transact (ctrl->scd_local->ctx, line, + rc = assuan_transact (daemon_ctx (ctrl), line, put_membuf_cb, &data, inq_needpin, &inqparm, NULL, NULL); @@ -932,14 +441,14 @@ agent_card_pkdecrypt (ctrl_t ctrl, sprintf (p, "%02X", indata[len]); p += 2; } - rc = assuan_transact (ctrl->scd_local->ctx, line, + rc = assuan_transact (daemon_ctx (ctrl), line, NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_scd (ctrl, rc); } init_membuf (&data, 1024); - inqparm.ctx = ctrl->scd_local->ctx; + inqparm.ctx = daemon_ctx (ctrl); inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; inqparm.getpin_cb_desc = desc_text; @@ -947,7 +456,7 @@ agent_card_pkdecrypt (ctrl_t ctrl, inqparm.keydata = NULL; inqparm.keydatalen = 0; snprintf (line, DIM(line), "PKDECRYPT %s", keyid); - rc = assuan_transact (ctrl->scd_local->ctx, line, + rc = assuan_transact (daemon_ctx (ctrl), line, put_membuf_cb, &data, inq_needpin, &inqparm, padding_info_cb, r_padding); @@ -983,7 +492,7 @@ agent_card_readcert (ctrl_t ctrl, init_membuf (&data, 1024); snprintf (line, DIM(line), "READCERT %s", id); - rc = assuan_transact (ctrl->scd_local->ctx, line, + rc = assuan_transact (daemon_ctx (ctrl), line, put_membuf_cb, &data, NULL, NULL, NULL, NULL); @@ -1018,7 +527,7 @@ agent_card_readkey (ctrl_t ctrl, const char *id, unsigned char **r_buf) init_membuf (&data, 1024); snprintf (line, DIM(line), "READKEY %s", id); - rc = assuan_transact (ctrl->scd_local->ctx, line, + rc = assuan_transact (daemon_ctx (ctrl), line, put_membuf_cb, &data, NULL, NULL, NULL, NULL); @@ -1072,7 +581,7 @@ agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, return rc; snprintf (line, DIM(line), "WRITEKEY %s%s", force ? "--force " : "", id); - parms.ctx = ctrl->scd_local->ctx; + parms.ctx = daemon_ctx (ctrl); parms.getpin_cb = getpin_cb; parms.getpin_cb_arg = getpin_cb_arg; parms.getpin_cb_desc= NULL; @@ -1080,7 +589,7 @@ agent_card_writekey (ctrl_t ctrl, int force, const char *serialno, parms.keydata = keydata; parms.keydatalen = keydatalen; - rc = assuan_transact (ctrl->scd_local->ctx, line, NULL, NULL, + rc = assuan_transact (daemon_ctx (ctrl), line, NULL, NULL, inq_writekey_parms, &parms, NULL, NULL); return unlock_scd (ctrl, rc); } @@ -1152,7 +661,7 @@ agent_card_getattr (ctrl_t ctrl, const char *name, char **result) if (err) return err; - err = assuan_transact (ctrl->scd_local->ctx, line, + err = assuan_transact (daemon_ctx (ctrl), line, NULL, NULL, NULL, NULL, card_getattr_cb, &parm); if (!err && parm.error) @@ -1225,7 +734,7 @@ agent_card_cardlist (ctrl_t ctrl, strlist_t *result) if (err) return err; - err = assuan_transact (ctrl->scd_local->ctx, line, + err = assuan_transact (daemon_ctx (ctrl), line, NULL, NULL, NULL, NULL, card_cardlist_cb, &parm); if (!err && parm.error) @@ -1301,7 +810,7 @@ agent_card_scd (ctrl_t ctrl, const char *cmdline, if (rc) return rc; - inqparm.ctx = ctrl->scd_local->ctx; + inqparm.ctx = daemon_ctx (ctrl); inqparm.getpin_cb = getpin_cb; inqparm.getpin_cb_arg = getpin_cb_arg; inqparm.getpin_cb_desc = NULL; @@ -1309,14 +818,14 @@ agent_card_scd (ctrl_t ctrl, const char *cmdline, inqparm.keydata = NULL; inqparm.keydatalen = 0; - saveflag = assuan_get_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS); - assuan_set_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS, 1); - rc = assuan_transact (ctrl->scd_local->ctx, cmdline, + saveflag = assuan_get_flag (daemon_ctx (ctrl), ASSUAN_CONVEY_COMMENTS); + assuan_set_flag (daemon_ctx (ctrl), ASSUAN_CONVEY_COMMENTS, 1); + rc = assuan_transact (daemon_ctx (ctrl), cmdline, pass_data_thru, assuan_context, inq_needpin, &inqparm, pass_status_thru, assuan_context); - assuan_set_flag (ctrl->scd_local->ctx, ASSUAN_CONVEY_COMMENTS, saveflag); + assuan_set_flag (daemon_ctx (ctrl), ASSUAN_CONVEY_COMMENTS, saveflag); if (rc) { return unlock_scd (ctrl, rc); @@ -1324,12 +833,3 @@ agent_card_scd (ctrl_t ctrl, const char *cmdline, return unlock_scd (ctrl, 0); } - -void -agent_card_killscd (void) -{ - if (primary_scd_ctx == NULL) - return; - assuan_transact (primary_scd_ctx, "KILLSCD", - NULL, NULL, NULL, NULL, NULL, NULL); -} diff --git a/agent/command-ssh.c b/agent/command-ssh.c index d6729b70a..bac9a42c9 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -2575,7 +2575,7 @@ ssh_handler_request_identities (ctrl_t ctrl, reader - this should be allowed even without being listed in sshcontrol. */ - if (!opt.disable_scdaemon) + if (!opt.disable_daemon[DAEMON_SCD]) { char *serialno; strlist_t card_list, sl; @@ -3691,8 +3691,8 @@ start_command_handler_ssh (ctrl_t ctrl, gnupg_fd_t sock_client) es_ungetc (c, stream_sock); } - /* Reset the SCD in case it has been used. */ - agent_reset_scd (ctrl); + /* Reset the daemon in case it has been used. */ + agent_reset_daemon (ctrl); out: @@ -3839,8 +3839,8 @@ serve_mmapped_ssh_request (ctrl_t ctrl, valid_response = 1; } - /* Reset the SCD in case it has been used. */ - agent_reset_scd (ctrl); + /* Reset the daemon in case it has been used. */ + agent_reset_daemon (ctrl); return valid_response? 0 : -1; } diff --git a/agent/command.c b/agent/command.c index dad0abbc2..0cba8ea93 100644 --- a/agent/command.c +++ b/agent/command.c @@ -2946,7 +2946,7 @@ cmd_getinfo (assuan_context_t ctx, char *line) } else if (!strcmp (line, "scd_running")) { - rc = agent_scd_check_running ()? 0 : gpg_error (GPG_ERR_FALSE); + rc = agent_daemon_check_running (DAEMON_SCD)? 0 : gpg_error (GPG_ERR_FALSE); } else if (!strcmp (line, "std_env_names")) { @@ -3412,7 +3412,7 @@ start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd) clear_nonce_cache (ctrl); /* Reset the SCD if needed. */ - agent_reset_scd (ctrl); + agent_reset_daemon (ctrl); /* Reset the pinentry (in case of popup messages). */ agent_reset_query (ctrl); diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 3dcbbf802..30d1d86cd 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -815,7 +815,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) xfree (opt.pinentry_invisible_char); opt.pinentry_invisible_char = NULL; opt.pinentry_timeout = 0; - opt.scdaemon_program = NULL; + memset (opt.daemon_program, 0, sizeof opt.daemon_program); opt.def_cache_ttl = DEFAULT_CACHE_TTL; opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH; opt.max_cache_ttl = MAX_CACHE_TTL; @@ -832,7 +832,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) opt.allow_external_cache = 1; opt.allow_loopback_pinentry = 1; opt.allow_emacs_pinentry = 0; - opt.disable_scdaemon = 0; + memset (opt.disable_daemon, 0, sizeof opt.disable_daemon); disable_check_own_socket = 0; /* Note: When changing the next line, change also gpgconf_list. */ opt.ssh_fingerprint_digest = GCRY_MD_MD5; @@ -875,8 +875,8 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break; break; case oPinentryTimeout: opt.pinentry_timeout = pargs->r.ret_ulong; break; - case oScdaemonProgram: opt.scdaemon_program = pargs->r.ret_str; break; - case oDisableScdaemon: opt.disable_scdaemon = 1; break; + case oScdaemonProgram: opt.daemon_program[DAEMON_SCD] = pargs->r.ret_str; break; + case oDisableScdaemon: opt.disable_daemon[DAEMON_SCD] = 1; break; case oDisableCheckOwnSocket: disable_check_own_socket = 1; break; case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break; @@ -984,7 +984,7 @@ initialize_modules (void) assuan_set_system_hooks (ASSUAN_SYSTEM_NPTH); initialize_module_cache (); initialize_module_call_pinentry (); - initialize_module_call_scd (); + initialize_module_daemon (); initialize_module_trustlist (); } @@ -2094,7 +2094,7 @@ get_agent_active_connection_count (void) event. */ #if defined(HAVE_W32_SYSTEM) && !defined(HAVE_W32CE_SYSTEM) void * -get_agent_scd_notify_event (void) +get_agent_daemon_notify_event (void) { static HANDLE the_event = INVALID_HANDLE_VALUE; @@ -2379,7 +2379,7 @@ handle_tick (void) last_minute = time (NULL); /* Check whether the scdaemon has died and cleanup in this case. */ - agent_scd_check_aliveness (); + agent_daemon_check_aliveness (); /* If we are running as a child of another process, check whether the parent is still alive and shutdown if not. */ @@ -2436,8 +2436,8 @@ agent_sighup_action (void) "pinentry-basic" fallback was in use. */ gnupg_module_name_flush_some (); - if (opt.disable_scdaemon) - agent_card_killscd (); + if (opt.disable_daemon[DAEMON_SCD]) + agent_kill_daemon (DAEMON_SCD); } @@ -2471,7 +2471,7 @@ handle_signal (int signo) logging system. */ /* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */ agent_query_dump_state (); - agent_scd_dump_state (); + agent_daemon_dump_state (); break; case SIGUSR2: @@ -2874,7 +2874,7 @@ handle_connections (gnupg_fd_t listen_fd, sigs = 0; ev = pth_event (PTH_EVENT_SIGS, &sigs, &signo); # else - events[0] = get_agent_scd_notify_event (); + events[0] = get_agent_daemon_notify_event (); events[1] = INVALID_HANDLE_VALUE; # endif #endif -- 2.26.2 From James.Bottomley at HansenPartnership.com Sun Jun 14 19:26:48 2020 From: James.Bottomley at HansenPartnership.com (James Bottomley) Date: Sun, 14 Jun 2020 10:26:48 -0700 Subject: [PATCH 4/5] agent: Add new shadow key type and functions to call tpm2daemon In-Reply-To: <20200614172649.23749-1-James.Bottomley@HansenPartnership.com> References: <20200614172649.23749-1-James.Bottomley@HansenPartnership.com> Message-ID: <20200614172649.23749-5-James.Bottomley@HansenPartnership.com> A new shadow key type: "tpm2-v1" is introduced signalling that the shadowed key is handled by the tpm2daemon. A function to identify this type is introduced and diversions to the tpm2daemon functions are conditioned on this function for pkign and pkdecrypt where the same diversions to scd are currently done. The (info) field of the shadowed key stores the actual TPM key. The TPM key is encrypted so only the physical TPM it was created on can read it (so no special protection is required for the info filed), but if the (info) field becomes corrupt or damaged, the key will be lost (unlike the token case, where the key is actually moved inside the token). Note, this commit adds handling for existing TPM format shadow keys, but there is still no way to create them. Signed-off-by: James Bottomley --- agent/Makefile.am | 5 + agent/agent.h | 50 +++++++++ agent/call-daemon.c | 1 + agent/call-tpm2d.c | 248 ++++++++++++++++++++++++++++++++++++++++++++ agent/command.c | 5 + agent/divert-tpm2.c | 143 +++++++++++++++++++++++++ agent/gpg-agent.c | 4 + agent/keyformat.txt | 12 ++- agent/pkdecrypt.c | 8 +- agent/pksign.c | 14 ++- 10 files changed, 482 insertions(+), 8 deletions(-) create mode 100644 agent/call-tpm2d.c create mode 100644 agent/divert-tpm2.c diff --git a/agent/Makefile.am b/agent/Makefile.am index 46770291a..e0dc83039 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -56,6 +56,11 @@ gpg_agent_SOURCES = \ call-daemon.c \ learncard.c +if HAVE_LIBTSS +gpg_agent_SOURCES += divert-tpm2.c \ + call-tpm2d.c +endif + common_libs = $(libcommon) commonpth_libs = $(libcommonpth) if HAVE_W32CE_SYSTEM diff --git a/agent/agent.h b/agent/agent.h index e35ee1c91..54f8dc90f 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -58,6 +58,7 @@ both the daemon_type and the daemon_modules array */ enum daemon_type { DAEMON_SCD, + DAEMON_TPM2D, DAEMON_MAX_TYPE, }; @@ -433,6 +434,7 @@ gpg_error_t agent_public_key_from_file (ctrl_t ctrl, gcry_sexp_t *result); int agent_is_dsa_key (gcry_sexp_t s_key); int agent_is_eddsa_key (gcry_sexp_t s_key); +int agent_is_tpm2_key(gcry_sexp_t s_key); int agent_key_available (const unsigned char *grip); gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, int *r_keytype, @@ -550,6 +552,44 @@ gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag); void agent_reload_trustlist (void); +/*-- divert-tpm2.c --*/ +#ifdef HAVE_LIBTSS +int divert_tpm2_pksign (ctrl_t ctrl, const char *desc_text, + const unsigned char *digest, size_t digestlen, int algo, + const unsigned char *shadow_info, unsigned char **r_sig, + size_t *r_siglen); +int divert_tpm2_pkdecrypt (ctrl_t ctrl, const char *desc_text, + const unsigned char *cipher, + const unsigned char *shadow_info, + char **r_buf, size_t *r_len, int *r_padding); +int divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip, + gcry_sexp_t s_skey); +#else +static inline int divert_tpm2_pksign (ctrl_t ctrl, const char *desc_text, + const unsigned char *digest, + size_t digestlen, int algo, + const unsigned char *shadow_info, + unsigned char **r_sig, + size_t *r_siglen) +{ + return -EINVAL; +} +static inline int divert_tpm2_pkdecrypt (ctrl_t ctrl, const char *desc_text, + const unsigned char *cipher, + const unsigned char *shadow_info, + char **r_buf, size_t *r_len, + int *r_padding) +{ + return -EINVAL; +} +static inline int divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip, + gcry_sexp_t s_skey) +{ + return -EINVAL; +} +#endif + + /*-- divert-scd.c --*/ int divert_pksign (ctrl_t ctrl, const char *desc_text, @@ -576,6 +616,16 @@ void agent_daemon_check_aliveness (void); void agent_reset_daemon (ctrl_t ctrl); void agent_kill_daemon (enum daemon_type type); +/*-- call-tpm2d.c --*/ +int agent_tpm2d_writekey (ctrl_t ctrl, unsigned char **shadow_info, + gcry_sexp_t s_skey); +int agent_tpm2d_pksign (ctrl_t ctrl, const unsigned char *digest, + size_t digestlen, const unsigned char *shadow_info, + unsigned char **r_sig, size_t *r_siglen); +int agent_tpm2d_pkdecrypt (ctrl_t ctrl, const unsigned char *cipher, + size_t cipherlen, const unsigned char *shadow_info, + char **r_buf, size_t *r_len); + /*-- call-scd.c --*/ int agent_card_learn (ctrl_t ctrl, void (*kpinfo_cb)(void*, const char *), diff --git a/agent/call-daemon.c b/agent/call-daemon.c index 977228194..72fde51e5 100644 --- a/agent/call-daemon.c +++ b/agent/call-daemon.c @@ -23,6 +23,7 @@ /* daemon type to module mapping */ static const int daemon_modules[DAEMON_MAX_TYPE] = { [DAEMON_SCD] = GNUPG_MODULE_NAME_SCDAEMON, + [DAEMON_TPM2D] = GNUPG_MODULE_NAME_TPM2DAEMON, }; /* Definition of module local data of the CTRL structure. */ diff --git a/agent/call-tpm2d.c b/agent/call-tpm2d.c new file mode 100644 index 000000000..66401b2bc --- /dev/null +++ b/agent/call-tpm2d.c @@ -0,0 +1,248 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "agent.h" +#include +#include "../common/strlist.h" +#include "../common/sexp-parse.h" +#include "../common/i18n.h" + +static int +start_tpm2d (ctrl_t ctrl) +{ + return daemon_start (DAEMON_TPM2D, ctrl); +} + +static int +unlock_tpm2d (ctrl_t ctrl, gpg_error_t err) +{ + return daemon_unlock (DAEMON_TPM2D, ctrl, err); +} + +static assuan_context_t +daemon_ctx (ctrl_t ctrl) +{ + return daemon_type_ctx (DAEMON_TPM2D, ctrl); +} + +struct inq_parm_s { + assuan_context_t ctx; + gpg_error_t (*getpin_cb)(ctrl_t, const char *, char **); + ctrl_t ctrl; + /* The next fields are used by inq_keydata. */ + const unsigned char *keydata; + size_t keydatalen; + /* following only used by inq_extra */ + const unsigned char *extra; + size_t extralen; + char *pin; +}; + +static gpg_error_t +inq_needpin (void *opaque, const char *line) +{ + struct inq_parm_s *parm = opaque; + char *pin = NULL; + gpg_error_t rc; + const char *s; + + if ((s = has_leading_keyword (line, "NEEDPIN"))) + { + rc = parm->getpin_cb (parm->ctrl, s, &pin); + if (!rc) + rc = assuan_send_data (parm->ctx, pin, strlen(pin)); + parm->pin = pin; + } + else + { + log_error ("unsupported inquiry '%s'\n", line); + rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); + } + + return rc; +} + +static gpg_error_t +inq_keydata (void *opaque, const char *line) +{ + struct inq_parm_s *parm = opaque; + + if (has_leading_keyword (line, "KEYDATA")) + return assuan_send_data (parm->ctx, parm->keydata, parm->keydatalen); + else + return inq_needpin (opaque, line); +} + +static gpg_error_t +inq_extra (void *opaque, const char *line) +{ + struct inq_parm_s *parm = opaque; + + if (has_leading_keyword (line, "EXTRA")) + return assuan_send_data (parm->ctx, parm->extra, parm->extralen); + else + return inq_keydata (opaque, line); +} + +int +agent_tpm2d_writekey (ctrl_t ctrl, unsigned char **shadow_info, + gcry_sexp_t s_skey) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + size_t n; + unsigned char *kbuf; + membuf_t data; + struct inq_parm_s inqparm; + size_t len; + + rc = start_tpm2d (ctrl); + if (rc) + return rc; + + /* note: returned data is TPM protected so no need for a sensitive context */ + init_membuf(&data, 4096); + + inqparm.ctx = daemon_ctx (ctrl); + inqparm.getpin_cb = agent_ask_new_passphrase; + inqparm.ctrl = ctrl; + inqparm.pin = NULL; + + n = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); + kbuf = xtrymalloc (n); + gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, kbuf, n); + inqparm.keydata = kbuf; + inqparm.keydatalen = n; + snprintf(line, sizeof(line), "IMPORT"); + + rc = assuan_transact (daemon_ctx (ctrl), line, + put_membuf_cb, &data, + inq_keydata, &inqparm, + NULL, NULL); + xfree (kbuf); + xfree (inqparm.pin); + if (rc) + { + xfree (get_membuf (&data, &len)); + return unlock_tpm2d (ctrl, rc); + } + + *shadow_info = get_membuf (&data, &len); + + return unlock_tpm2d (ctrl, 0); +} + +static gpg_error_t +pin_cb (ctrl_t ctrl, const char *prompt, char **passphrase) +{ + *passphrase = agent_get_cache (ctrl, ctrl->keygrip, CACHE_MODE_USER); + if (*passphrase) + return 0; + return agent_get_passphrase(ctrl, passphrase, + _("Please enter your passphrase, so that the " + "secret key can be unlocked for this session"), + prompt, NULL, 0, + ctrl->keygrip, CACHE_MODE_USER); +} + +int +agent_tpm2d_pksign (ctrl_t ctrl, const unsigned char *digest, + size_t digestlen, const unsigned char *shadow_info, + unsigned char **r_sig, size_t *r_siglen) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + struct inq_parm_s inqparm; + + rc = start_tpm2d (ctrl); + if (rc) + return rc; + + init_membuf (&data, 1024); + + inqparm.ctx = daemon_ctx (ctrl); + inqparm.getpin_cb = pin_cb; + inqparm.ctrl = ctrl; + inqparm.keydata = shadow_info; + inqparm.keydatalen = gcry_sexp_canon_len (shadow_info, 0, NULL, NULL); + inqparm.extra = digest; + inqparm.extralen = digestlen; + inqparm.pin = NULL; + + snprintf(line, sizeof(line), "PKSIGN"); + + rc = assuan_transact (daemon_ctx (ctrl), line, + put_membuf_cb, &data, + inq_extra, &inqparm, + NULL, NULL); + if (!rc) + agent_put_cache (ctrl, ctrl->keygrip, CACHE_MODE_USER, inqparm.pin, 0); + + xfree (inqparm.pin); + + if (rc) + { + size_t len; + xfree (get_membuf (&data, &len)); + return unlock_tpm2d (ctrl, rc); + } + + *r_sig = get_membuf (&data, r_siglen); + + return unlock_tpm2d (ctrl, 0); +} + +int +agent_tpm2d_pkdecrypt (ctrl_t ctrl, const unsigned char *cipher, + size_t cipherlen, const unsigned char *shadow_info, + char **r_buf, size_t *r_len) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + struct inq_parm_s inqparm; + + rc = start_tpm2d (ctrl); + if (rc) + return rc; + + init_membuf (&data, 1024); + + inqparm.ctx = daemon_ctx (ctrl); + inqparm.getpin_cb = pin_cb; + inqparm.ctrl = ctrl; + inqparm.keydata = shadow_info; + inqparm.keydatalen = gcry_sexp_canon_len (shadow_info, 0, NULL, NULL); + inqparm.extra = cipher; + inqparm.extralen = cipherlen; + inqparm.pin = NULL; + + snprintf(line, sizeof(line), "PKDECRYPT"); + + rc = assuan_transact (daemon_ctx (ctrl), line, + put_membuf_cb, &data, + inq_extra, &inqparm, + NULL, NULL); + if (!rc) + agent_put_cache (ctrl, ctrl->keygrip, CACHE_MODE_USER, inqparm.pin, 0); + + xfree (inqparm.pin); + + if (rc) + { + size_t len; + xfree (get_membuf (&data, &len)); + return unlock_tpm2d (ctrl, rc); + } + + *r_buf = get_membuf (&data, r_len); + + return unlock_tpm2d (ctrl, 0); +} diff --git a/agent/command.c b/agent/command.c index 0cba8ea93..bea113c1b 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1194,6 +1194,11 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx, if (err) goto leave; } + else if (strcmp (shadow_info_type, "tpm2-v1") == 0) + { + serialno = xstrdup("TPM-Protected"); + idstr = NULL; + } else { log_error ("Unrecognised shadow key type %s\n", shadow_info_type); diff --git a/agent/divert-tpm2.c b/agent/divert-tpm2.c new file mode 100644 index 000000000..c133c8aa0 --- /dev/null +++ b/agent/divert-tpm2.c @@ -0,0 +1,143 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "agent.h" +#include "../common/i18n.h" +#include "../common/sexp-parse.h" + +int +divert_tpm2_pksign (ctrl_t ctrl, const char *desc_text, + const unsigned char *digest, size_t digestlen, int algo, + const unsigned char *shadow_info, unsigned char **r_sig, + size_t *r_siglen) +{ + return agent_tpm2d_pksign(ctrl, digest, digestlen, + shadow_info, r_sig, r_siglen); +} + + +static gpg_error_t +agent_write_tpm2_shadow_key (ctrl_t ctrl, const unsigned char *grip, + unsigned char *shadow_info) +{ + gpg_error_t err; + unsigned char *shdkey; + unsigned char *pkbuf; + size_t len; + gcry_sexp_t s_pkey; + + err = agent_public_key_from_file (ctrl, grip, &s_pkey); + len = gcry_sexp_sprint(s_pkey, GCRYSEXP_FMT_CANON, NULL, 0); + pkbuf = xtrymalloc (len); + gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, pkbuf, len); + gcry_sexp_release (s_pkey); + + err = agent_shadow_key_type (pkbuf, shadow_info, "tpm2-v1", &shdkey); + xfree (pkbuf); + if (err) + { + log_error ("shadowing the key failed: %s\n", gpg_strerror (err)); + return err; + } + + len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL); + err = agent_write_private_key (grip, shdkey, len, 1 /*force*/); + xfree (shdkey); + if (err) + log_error ("error writing key: %s\n", gpg_strerror (err)); + + return err; +} + +int +divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip, + gcry_sexp_t s_skey) +{ + int ret; + /* shadow_info is always shielded so no special handling required */ + unsigned char *shadow_info; + + ret = agent_tpm2d_writekey(ctrl, &shadow_info, s_skey); + if (!ret) + ret = agent_write_tpm2_shadow_key (ctrl, grip, shadow_info); + xfree (shadow_info); + return ret; +} + +int +divert_tpm2_pkdecrypt (ctrl_t ctrl, const char *desc_text, + const unsigned char *cipher, + const unsigned char *shadow_info, + char **r_buf, size_t *r_len, int *r_padding) +{ + const unsigned char *s; + size_t n; + + *r_padding = -1; + + (void)desc_text; + + s = cipher; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "enc-val")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (smatch (&s, n, "rsa")) + { + *r_padding = 0; + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "a")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + n = snext (&s); + } + else if (smatch (&s, n, "ecdh")) + { + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (smatch (&s, n, "s")) + { + n = snext (&s); + s += n; + if (*s++ != ')') + return gpg_error (GPG_ERR_INV_SEXP); + if (*s++ != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + } + if (!smatch (&s, n, "e")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + n = snext (&s); + } + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + + return agent_tpm2d_pkdecrypt (ctrl, s, n, shadow_info, r_buf, r_len); +} diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index 30d1d86cd..218bd3ea8 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -103,6 +103,7 @@ enum cmd_and_opt_values oLCmessages, oXauthority, oScdaemonProgram, + oTpm2daemonProgram, oDefCacheTTL, oDefCacheTTLSSH, oMaxCacheTTL, @@ -188,6 +189,8 @@ static ARGPARSE_OPTS opts[] = { ARGPARSE_s_u (oPinentryTimeout, "pinentry-timeout", "@"), ARGPARSE_s_s (oScdaemonProgram, "scdaemon-program", /* */ N_("|PGM|use PGM as the SCdaemon program") ), + ARGPARSE_s_s (oTpm2daemonProgram, "tpm2daemon-program", + /* */ N_("|PGM|use PGM as the tpm2daemon program") ), ARGPARSE_s_n (oDisableScdaemon, "disable-scdaemon", /* */ N_("do not use the SCdaemon") ), ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"), @@ -875,6 +878,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break; break; case oPinentryTimeout: opt.pinentry_timeout = pargs->r.ret_ulong; break; + case oTpm2daemonProgram: opt.daemon_program[DAEMON_TPM2D] = pargs->r.ret_str; break; case oScdaemonProgram: opt.daemon_program[DAEMON_SCD] = pargs->r.ret_str; break; case oDisableScdaemon: opt.disable_daemon[DAEMON_SCD] = 1; break; case oDisableCheckOwnSocket: disable_check_own_socket = 1; break; diff --git a/agent/keyformat.txt b/agent/keyformat.txt index 2e48b346e..30a95c0a3 100644 --- a/agent/keyformat.txt +++ b/agent/keyformat.txt @@ -271,8 +271,9 @@ to keys stored on a token: (comment whatever) ) -The currently used protocol is "t1-v1" (token info version 1). The -second list with the information has this layout: +The currently used protocols are "t1-v1" (token info version 1) and +"tpm2-v1" (TPM format key information). The second list with the +information has this layout for "t1-v1": (card_serial_number id_string_of_key fixed_pin_length) @@ -281,6 +282,13 @@ the PIN; a value of 0 indicates that this information is not available. The rationale for this field is that some pinpad equipped readers don't allow passing a variable length PIN. +This is the (info) layout for "tpm2-v1": + +(parent tpm_private_string tpm_public_string) + +Although this precise format is encapsulated inside the tpm2daemon +itself and nothing in gpg ever uses this. + More items may be added to the list. ** OpenPGP Private Key Transfer Format diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index 06a8e0b6f..6f766ca63 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -86,8 +86,12 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, goto leave; } - rc = divert_pkdecrypt (ctrl, desc_text, ciphertext, shadow_info, - &buf, &len, r_padding); + if (agent_is_tpm2_key (s_skey)) + rc = divert_tpm2_pkdecrypt (ctrl, desc_text, ciphertext, shadow_info, + &buf, &len, r_padding); + else + rc = divert_pkdecrypt (ctrl, desc_text, ciphertext, shadow_info, + &buf, &len, r_padding); if (rc) { log_error ("smartcard decryption failed: %s\n", gpg_strerror (rc)); diff --git a/agent/pksign.c b/agent/pksign.c index f54af0817..dae2638f4 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -353,10 +353,16 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, if (desc_text) agent_modify_description (desc_text, NULL, s_skey, &desc2); - err = divert_pksign (ctrl, desc2? desc2 : desc_text, - data, datalen, - ctrl->digest.algo, - shadow_info, &buf, &len); + if (agent_is_tpm2_key (s_skey)) + err = divert_tpm2_pksign (ctrl, desc2? desc2 : desc_text, + data, datalen, + ctrl->digest.algo, + shadow_info, &buf, &len); + else + err = divert_pksign (ctrl, desc2? desc2 : desc_text, + data, datalen, + ctrl->digest.algo, + shadow_info, &buf, &len); xfree (desc2); } if (err) -- 2.26.2 From James.Bottomley at HansenPartnership.com Sun Jun 14 19:26:44 2020 From: James.Bottomley at HansenPartnership.com (James Bottomley) Date: Sun, 14 Jun 2020 10:26:44 -0700 Subject: [PATCH 0/5] Add TPM2 support to gnupg Message-ID: <20200614172649.23749-1-James.Bottomley@HansenPartnership.com> I based these patches on the stable-2-2 branch so people can take them for a spin easily. They seem to upport to master quite well, if that would be the preferred destination? They also completely supersede the old tpm-work branch because the code is fully restructured to segregate all the TPM handling into its own daemon. The first two patches are code rearrangement precursors. They expose the shadow key handling functions to the broad code (we need this because TPM keys become a new type of shadow key and now we need to change handling based on which type of key we have) and they restructure the daemon infrastructure. Today, there's only really one kind of key handling subsidiary: scdaemon, but I need to introduce a new one (tpm2daemon) and it's much easier to do this if there's a reusable infrastructure. The final 3 patches add the actual TPM code itself. To use this you need to have a copy of the ibmtss library installed. Currently the configure.ac can use the older tss2 package in sid: https://packages.debian.org/sid/admin/tss2 But hopefully the new ibmtss package will replace it shortly. openSUSE and fedora already have the ibmtss version of the package. To use this you need your PC provisioned with a NV storage key. Apparently this is falling out of favour and most of the TSS code now derives an elliptic curve primary if it can't find the NV storage key. I'll update the code to do this on the next go around. You can see if you have the NV storage key provisioned by doing tssreadpublic -ho 0x81000001 If that succeeds, you're fine. If it doesn't you can create one by doing: key=`tsscreateprimary -hi o -st -rsa|sed 's/Handle //'` tssevictcontrol -hi o -ho ${key} -hp 81000001 tssflushcontext -ha ${key} Once created it will persist across reboots, but the complexity of this is why we've all agreed to move to the rederivation method. James --- James Bottomley (5): agent: expose shadow key type agent: separate out daemon handling infrastructure for reuse tpm2d: Add tpm2daemon code agent: Add new shadow key type and functions to call tpm2daemon g10: add new command keytotpm to convert a private key to TPM format Makefile.am | 7 +- agent/Makefile.am | 6 + agent/agent.h | 97 ++- agent/call-daemon.c | 583 +++++++++++++++++ agent/call-scd.c | 552 +--------------- agent/call-tpm2d.c | 248 ++++++++ agent/command-ssh.c | 10 +- agent/command.c | 82 ++- agent/divert-tpm2.c | 143 +++++ agent/findkey.c | 5 +- agent/gpg-agent.c | 26 +- agent/keyformat.txt | 12 +- agent/pkdecrypt.c | 8 +- agent/pksign.c | 14 +- agent/protect.c | 72 ++- am/cmacros.am | 3 + common/homedir.c | 7 + common/util.h | 1 + configure.ac | 37 ++ g10/call-agent.c | 22 + g10/call-agent.h | 3 + g10/keyedit.c | 45 +- tools/gpgconf-comp.c | 15 + tpm2d/Makefile.am | 18 + tpm2d/command.c | 570 +++++++++++++++++ tpm2d/tpm2.c | 969 ++++++++++++++++++++++++++++ tpm2d/tpm2.h | 39 ++ tpm2d/tpm2daemon.c | 1434 ++++++++++++++++++++++++++++++++++++++++++ tpm2d/tpm2daemon.h | 130 ++++ 29 files changed, 4573 insertions(+), 585 deletions(-) create mode 100644 agent/call-daemon.c create mode 100644 agent/call-tpm2d.c create mode 100644 agent/divert-tpm2.c create mode 100644 tpm2d/Makefile.am create mode 100644 tpm2d/command.c create mode 100644 tpm2d/tpm2.c create mode 100644 tpm2d/tpm2.h create mode 100644 tpm2d/tpm2daemon.c create mode 100644 tpm2d/tpm2daemon.h -- 2.26.2 From James.Bottomley at HansenPartnership.com Sun Jun 14 19:26:49 2020 From: James.Bottomley at HansenPartnership.com (James Bottomley) Date: Sun, 14 Jun 2020 10:26:49 -0700 Subject: [PATCH 5/5] g10: add new command keytotpm to convert a private key to TPM format In-Reply-To: <20200614172649.23749-1-James.Bottomley@HansenPartnership.com> References: <20200614172649.23749-1-James.Bottomley@HansenPartnership.com> Message-ID: <20200614172649.23749-6-James.Bottomley@HansenPartnership.com> The plumbing is done in two parts: the agent is modified to understand a KEYTOTPM assuan command taking the key grip as an argument. This simply obtains the key s expression and calls the existing writeky diversion to the tpm2daemon. The daemon reponds with the TPM conversion of the key and that key is then stored in the keyfile as a shadowed-private-key with "tpm2-v1" type. To effect the conversion, all the user does from gpg --edit-key is select which private key they wish to move (or move the primary if no key is selected) and type keytotpm. The conversion to TPM form is instantaneous and once converted, the actual key cannot be recovered, meaning that if you want your gpg key to move to a new laptop you must keep an unconverted backup copy in a safe location. When you do a list command, all TPM keys show up as card-no: TPM-Protected The key is stored encrypted to the TPM2 storage seed and since each TPM has a unique seed, only the single TPM contained in your laptop can now read the key. This means you cannot simply copy the shadowed key file over to a new laptop, you must copy over the backup copy and then convert it to TPM form on the new laptop. To decomission your laptop, execute a tssclear command which regenerates the storage seed and effectively shreds all keys. Note when you have done this *every* TPM2 shadowed private key becomes unreadable by any TPM and all are effectively destroyed. Signed-off-by: James Bottomley --- agent/command.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ g10/call-agent.c | 22 ++++++++++++++++++++ g10/call-agent.h | 3 +++ g10/keyedit.c | 45 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 121 insertions(+), 1 deletion(-) diff --git a/agent/command.c b/agent/command.c index bea113c1b..04a236f23 100644 --- a/agent/command.c +++ b/agent/command.c @@ -2601,6 +2601,57 @@ cmd_keytocard (assuan_context_t ctx, char *line) } + +static const char hlp_keytotpm[] = + "KEYTOTPM \n" + "\n"; +static gpg_error_t +cmd_keytotpm (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err = 0; + unsigned char grip[20]; + gcry_sexp_t s_skey; + unsigned char *shadow_info = NULL; + + if (ctrl->restricted) + return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); + + err = parse_keygrip (ctx, line, grip); + if (err) + goto leave; + + if (agent_key_available (grip)) + { + err =gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + + err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip, + &shadow_info, CACHE_MODE_IGNORE, NULL, + &s_skey, NULL); + if (err) + { + xfree (shadow_info); + goto leave; + } + if (shadow_info) + { + /* Key is on a TPM or smartcard already. */ + xfree (shadow_info); + gcry_sexp_release (s_skey); + err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); + goto leave; + } + + err = divert_tpm2_writekey (ctrl, grip, s_skey); + gcry_sexp_release (s_skey); + + leave: + return leave_cmd (ctx, err); +} + + static const char hlp_getval[] = "GETVAL \n" @@ -3300,6 +3351,7 @@ register_commands (assuan_context_t ctx) { "RELOADAGENT", cmd_reloadagent,hlp_reloadagent }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "KEYTOCARD", cmd_keytocard, hlp_keytocard }, + { "KEYTOTPM", cmd_keytotpm, hlp_keytotpm }, { NULL } }; int i, rc; diff --git a/g10/call-agent.c b/g10/call-agent.c index 6ebb8383e..78cef8835 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -923,6 +923,28 @@ agent_scd_apdu (const char *hexapdu, unsigned int *r_sw) return err; } +int +agent_keytotpm (ctrl_t ctrl, const char *hexgrip) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s parm; + + snprintf(line, DIM(line), "KEYTOTPM %s\n", hexgrip); + + rc = start_agent (ctrl, 0); + if (rc) + return rc; + parm.ctx = agent_ctx; + parm.ctrl = ctrl; + + rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, + NULL, NULL); + if (rc) + log_log (GPGRT_LOGLVL_ERROR, _("error from TPM: %s\n"), gpg_strerror (rc)); + return rc; +} + /* Used by: * card_store_subkey diff --git a/g10/call-agent.h b/g10/call-agent.h index bc8c3927a..670d3aadb 100644 --- a/g10/call-agent.h +++ b/g10/call-agent.h @@ -102,6 +102,9 @@ gpg_error_t agent_scd_getattr_one (const char *name, char **r_value); /* Update INFO with the attribute NAME. */ int agent_scd_getattr (const char *name, struct agent_card_info_s *info); +/* send the KEYTOTPM command */ +int agent_keytotpm (ctrl_t ctrl, const char *hexgrip); + /* Send the KEYTOCARD command. */ int agent_keytocard (const char *hexgrip, int keyno, int force, const char *serialno, const char *timestamp); diff --git a/g10/keyedit.c b/g10/keyedit.c index a7932ce95..825aeb48b 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -1244,7 +1244,7 @@ enum cmdids #endif /*!NO_TRUST_MODELS*/ cmdSHOWPREF, cmdSETPREF, cmdPREFKS, cmdNOTATION, cmdINVCMD, cmdSHOWPHOTO, cmdUPDTRUST, - cmdCHKTRUST, cmdADDCARDKEY, cmdKEYTOCARD, cmdBKUPTOCARD, + cmdCHKTRUST, cmdADDCARDKEY, cmdKEYTOCARD, cmdKEYTOTPM, cmdBKUPTOCARD, cmdCLEAN, cmdMINIMIZE, cmdGRIP, cmdNOP }; @@ -1295,6 +1295,8 @@ static struct N_("add a key to a smartcard")}, { "keytocard", cmdKEYTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, N_("move a key to a smartcard")}, + { "keytotpm", cmdKEYTOTPM, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, + N_("convert a key to TPM form using the local TPM")}, { "bkuptocard", cmdBKUPTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, N_("move a backup key to a smartcard")}, #endif /*ENABLE_CARD_SUPPORT */ @@ -1793,6 +1795,47 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, } break; + case cmdKEYTOTPM: + /* FIXME need to store the key and not commit until later */ + { + KBNODE node = NULL; + switch (count_selected_keys (keyblock)) + { + case 0: + if (cpr_get_answer_is_yes + ("keyedit.keytocard.use_primary", + /* TRANSLATORS: Please take care: This is about + moving the key and not about removing it. */ + _("Really move the primary key? (y/N) "))) + node = keyblock; + break; + case 1: + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + && node->flag & NODFLG_SELKEY) + break; + } + break; + default: + tty_printf (_("You must select exactly one key.\n")); + break; + } + if (node) + { + PKT_public_key *xxpk = node->pkt->pkt.public_key; + char *hexgrip; + + hexkeygrip_from_pk (xxpk, &hexgrip); + if (!agent_keytotpm (ctrl, hexgrip)) + { + redisplay = 1; + } + xfree (hexgrip); + } + } + break; + case cmdKEYTOCARD: { KBNODE node = NULL; -- 2.26.2 From James.Bottomley at HansenPartnership.com Sun Jun 14 19:26:45 2020 From: James.Bottomley at HansenPartnership.com (James Bottomley) Date: Sun, 14 Jun 2020 10:26:45 -0700 Subject: [PATCH 1/5] agent: expose shadow key type In-Reply-To: <20200614172649.23749-1-James.Bottomley@HansenPartnership.com> References: <20200614172649.23749-1-James.Bottomley@HansenPartnership.com> Message-ID: <20200614172649.23749-2-James.Bottomley@HansenPartnership.com> For TPM support it is necessary to indroduce another type of shadow key, so allow other agent functions to extract the type so they can make the right decisions based on it. Signed-off-by: James Bottomley --- agent/agent.h | 10 ++++++- agent/command.c | 21 +++++++++++---- agent/findkey.c | 5 ++-- agent/protect.c | 72 ++++++++++++++++++++++++++++++++++++++++++------- 4 files changed, 90 insertions(+), 18 deletions(-) diff --git a/agent/agent.h b/agent/agent.h index e10e02b8c..c19c3efdc 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -429,7 +429,8 @@ int agent_is_eddsa_key (gcry_sexp_t s_key); int agent_key_available (const unsigned char *grip); gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, int *r_keytype, - unsigned char **r_shadow_info); + unsigned char **r_shadow_info, + unsigned char **r_shadow_info_type); gpg_error_t agent_delete_key (ctrl_t ctrl, const char *desc_text, const unsigned char *grip, int force, int only_stubs); @@ -513,8 +514,15 @@ unsigned char *make_shadow_info (const char *serialno, const char *idstring); int agent_shadow_key (const unsigned char *pubkey, const unsigned char *shadow_info, unsigned char **result); +int agent_shadow_key_type (const unsigned char *pubkey, + const unsigned char *shadow_info, + const unsigned char *type, + unsigned char **result); gpg_error_t agent_get_shadow_info (const unsigned char *shadowkey, unsigned char const **shadow_info); +gpg_error_t agent_get_shadow_info_type (const unsigned char *shadowkey, + unsigned char const **shadow_info, + unsigned char **shadow_type); gpg_error_t parse_shadow_info (const unsigned char *shadow_info, char **r_hexsn, char **r_idstr, int *r_pinlen); gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo, diff --git a/agent/command.c b/agent/command.c index c24fc80fd..dad0abbc2 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1106,7 +1106,7 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx, char hexgrip[40+1]; char *fpr = NULL; int keytype; - unsigned char *shadow_info = NULL; + unsigned char *shadow_info = NULL, *shadow_info_type = NULL; char *serialno = NULL; char *idstr = NULL; const char *keytypestr; @@ -1117,7 +1117,8 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx, char ttlbuf[20]; char flagsbuf[5]; - err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info); + err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info, + &shadow_info_type); if (err) { if (in_ssh && gpg_err_code (err) == GPG_ERR_NOT_FOUND) @@ -1187,9 +1188,18 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx, if (shadow_info) { - err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL); - if (err) - goto leave; + if (strcmp (shadow_info_type, "t1-v1") == 0) + { + err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL); + if (err) + goto leave; + } + else + { + log_error ("Unrecognised shadow key type %s\n", shadow_info_type); + err = GPG_ERR_BAD_KEY; + goto leave; + } } if (!data) @@ -1224,6 +1234,7 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx, leave: xfree (fpr); + xfree (shadow_info_type); xfree (shadow_info); xfree (serialno); xfree (idstr); diff --git a/agent/findkey.c b/agent/findkey.c index bdb6ab4f5..e01787383 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -1372,7 +1372,8 @@ agent_key_available (const unsigned char *grip) S-expression. */ gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, - int *r_keytype, unsigned char **r_shadow_info) + int *r_keytype, unsigned char **r_shadow_info, + unsigned char **r_shadow_info_type) { gpg_error_t err; unsigned char *buf; @@ -1419,7 +1420,7 @@ agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, const unsigned char *s; size_t n; - err = agent_get_shadow_info (buf, &s); + err = agent_get_shadow_info_type (buf, &s, r_shadow_info_type); if (!err) { n = gcry_sexp_canon_len (s, 0, NULL, NULL); diff --git a/agent/protect.c b/agent/protect.c index 134fbf2e6..cc6dd7509 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -1518,9 +1518,10 @@ make_shadow_info (const char *serialno, const char *idstring) to. The input parameters are expected to be valid canonicalized S-expressions */ int -agent_shadow_key (const unsigned char *pubkey, - const unsigned char *shadow_info, - unsigned char **result) +agent_shadow_key_type (const unsigned char *pubkey, + const unsigned char *shadow_info, + const unsigned char *type, + unsigned char **result) { const unsigned char *s; const unsigned char *point; @@ -1576,7 +1577,7 @@ agent_shadow_key (const unsigned char *pubkey, assert (depth == 1); /* Calculate required length by taking in account: the "shadowed-" - prefix, the "shadowed", "t1-v1" as well as some parenthesis */ + prefix, the "shadowed", shadow type as well as some parenthesis */ n = 12 + pubkey_len + 1 + 3+8 + 2+5 + shadow_info_len + 1; *result = xtrymalloc (n); p = (char*)*result; @@ -1586,7 +1587,7 @@ agent_shadow_key (const unsigned char *pubkey, /* (10:public-key ...)*/ memcpy (p, pubkey+14, point - (pubkey+14)); p += point - (pubkey+14); - p = stpcpy (p, "(8:shadowed5:t1-v1"); + p += sprintf (p, "(8:shadowed%d:%s", (int)strlen(type), type); memcpy (p, shadow_info, shadow_info_len); p += shadow_info_len; *p++ = ')'; @@ -1596,11 +1597,20 @@ agent_shadow_key (const unsigned char *pubkey, return 0; } +int +agent_shadow_key (const unsigned char *pubkey, + const unsigned char *shadow_info, + unsigned char **result) +{ + return agent_shadow_key_type (pubkey, shadow_info, "t1-v1", result); +} + /* Parse a canonical encoded shadowed key and return a pointer to the - inner list with the shadow_info */ + inner list with the shadow_info and the shadow type */ gpg_error_t -agent_get_shadow_info (const unsigned char *shadowkey, - unsigned char const **shadow_info) +agent_get_shadow_info_type (const unsigned char *shadowkey, + unsigned char const **shadow_info, + unsigned char **shadow_type) { const unsigned char *s; size_t n; @@ -1652,17 +1662,59 @@ agent_get_shadow_info (const unsigned char *shadowkey, n = snext (&s); if (!n) return gpg_error (GPG_ERR_INV_SEXP); - if (smatch (&s, n, "t1-v1")) + if (shadow_type) { + char *buf = xtrymalloc(n+1); + memcpy(buf, s, n); + buf[n] = '\0'; + *shadow_type = buf; + } + + if (smatch (&s, n, "t1-v1") || smatch(&s, n, "tpm2-v1")) { if (*s != '(') return gpg_error (GPG_ERR_INV_SEXP); - *shadow_info = s; + if (shadow_info) + *shadow_info = s; } else return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL); return 0; } +gpg_error_t +agent_get_shadow_info(const unsigned char *shadowkey, + unsigned char const **shadow_info) +{ + return agent_get_shadow_info_type(shadowkey, shadow_info, NULL); +} + +int +agent_is_tpm2_key(gcry_sexp_t s_skey) +{ + unsigned char *buf; + unsigned char *type; + size_t len; + gpg_error_t err; + + err = make_canon_sexp(s_skey, &buf, &len); + if (err) + return 0; + + err = agent_get_shadow_info_type(buf, NULL, &type); + if (err) + return 0; + + err = strcmp(type, "tpm2-v1") == 0; + xfree(type); + return err; +} + +gpg_error_t +agent_get_shadow_type(const unsigned char *shadowkey, + unsigned char **shadow_type) +{ + return agent_get_shadow_info_type(shadowkey, NULL, shadow_type); +} /* Parse the canonical encoded SHADOW_INFO S-expression. On success the hex encoded serial number is returned as a malloced strings at -- 2.26.2 From James.Bottomley at HansenPartnership.com Sun Jun 14 19:26:47 2020 From: James.Bottomley at HansenPartnership.com (James Bottomley) Date: Sun, 14 Jun 2020 10:26:47 -0700 Subject: [PATCH 3/5] tpm2d: Add tpm2daemon code In-Reply-To: <20200614172649.23749-1-James.Bottomley@HansenPartnership.com> References: <20200614172649.23749-1-James.Bottomley@HansenPartnership.com> Message-ID: <20200614172649.23749-4-James.Bottomley@HansenPartnership.com> 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 --- Makefile.am | 7 +- am/cmacros.am | 3 + common/homedir.c | 7 + common/util.h | 1 + configure.ac | 37 ++ tools/gpgconf-comp.c | 15 + tpm2d/Makefile.am | 18 + tpm2d/command.c | 570 +++++++++++++++++ tpm2d/tpm2.c | 969 ++++++++++++++++++++++++++++ tpm2d/tpm2.h | 39 ++ tpm2d/tpm2daemon.c | 1434 ++++++++++++++++++++++++++++++++++++++++++ tpm2d/tpm2daemon.h | 130 ++++ 12 files changed, 3229 insertions(+), 1 deletion(-) create mode 100644 tpm2d/Makefile.am create mode 100644 tpm2d/command.c create mode 100644 tpm2d/tpm2.c create mode 100644 tpm2d/tpm2.h create mode 100644 tpm2d/tpm2daemon.c create mode 100644 tpm2d/tpm2daemon.h diff --git a/Makefile.am b/Makefile.am index 88b7bdf8b..1da466b6c 100644 --- a/Makefile.am +++ b/Makefile.am @@ -100,10 +100,15 @@ doc = doc else doc = endif +if HAVE_LIBTSS +tpm2d = tpm2d +else +tpm2d = +endif SUBDIRS = m4 common kbx \ ${gpg} ${sm} ${agent} ${scd} ${g13} ${dirmngr} \ - tools po ${doc} tests + tools po ${doc} tests ${tpm2d} dist_doc_DATA = README diff --git a/am/cmacros.am b/am/cmacros.am index 9610e4efe..e71bc4e9d 100644 --- a/am/cmacros.am +++ b/am/cmacros.am @@ -44,6 +44,9 @@ endif if GNUPG_SCDAEMON_PGM AM_CPPFLAGS += -DGNUPG_DEFAULT_SCDAEMON="\"@GNUPG_SCDAEMON_PGM@\"" endif +if GNUPG_TPM2DAEMON_PGM +AM_CPPFLAGS += -DGNUPG_DEFAULT_TPM2DAEMON="\"@GNUPG_TPM2DAEMON_PGM@\"" +endif if GNUPG_DIRMNGR_PGM AM_CPPFLAGS += -DGNUPG_DEFAULT_DIRMNGR="\"@GNUPG_DIRMNGR_PGM@\"" endif diff --git a/common/homedir.c b/common/homedir.c index 4b6e46e88..71d631f5f 100644 --- a/common/homedir.c +++ b/common/homedir.c @@ -1100,6 +1100,13 @@ gnupg_module_name (int which) X(libexecdir, "scd", "scdaemon"); #endif + case GNUPG_MODULE_NAME_TPM2DAEMON: +#ifdef GNUPG_DEFAULT_TPM2DAEMON + return GNUPG_DEFAULT_TPM2DAEMON; +#else + X(libexecdir, "tpm2d", TPM2DAEMON_NAME); +#endif + case GNUPG_MODULE_NAME_DIRMNGR: #ifdef GNUPG_DEFAULT_DIRMNGR return GNUPG_DEFAULT_DIRMNGR; diff --git a/common/util.h b/common/util.h index 5002039d6..c1ae410eb 100644 --- a/common/util.h +++ b/common/util.h @@ -290,6 +290,7 @@ char *_gnupg_socketdir_internal (int skip_checks, unsigned *r_info); #define GNUPG_MODULE_NAME_GPGCONF 10 #define GNUPG_MODULE_NAME_DIRMNGR_LDAP 11 #define GNUPG_MODULE_NAME_GPGV 12 +#define GNUPG_MODULE_NAME_TPM2DAEMON 13 const char *gnupg_module_name (int which); void gnupg_module_name_flush_some (void); void gnupg_set_builddir (const char *newdir); diff --git a/configure.ac b/configure.ac index 1d05d3991..ca2ba27ff 100644 --- a/configure.ac +++ b/configure.ac @@ -101,6 +101,7 @@ have_gnutls=no have_sqlite=no have_npth=no have_libusb=no +have_libtss=no have_system_resolver=no gnupg_have_ldap="n/a" @@ -183,6 +184,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="" ) @@ -1606,6 +1616,24 @@ fi AC_SUBST(NETLIBS) AC_SUBST(W32SOCKLIBS) +# +# TPM libtss library .. don't compile TPM support if we don't have it +# +LIBS="" +AC_SEARCH_LIBS([TSS_Create], [tss ibmtss],have_libtss=yes,) +if test "$have_libtss" = yes; then + CFLAGS="$CFLAGS -DTPM_POSIX" + 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_ERROR([No TSS2 include directory found]))) + LIBTSS_LIBS=$LIBS + AC_DEFINE(HAVE_LIBTSS, 1, [Defined if we have TPM2 support library]) + AC_SUBST(TSS_INCLUDE) +fi +AC_SUBST(LIBTSS_LIBS) +AM_CONDITIONAL(HAVE_LIBTSS, test "$have_libtss" = yes) +AC_SUBST(HAVE_LIBTSS) + # # Setup gcc specific options # @@ -1841,6 +1869,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]) @@ -1870,6 +1902,8 @@ AC_DEFINE_UNQUOTED(DIRMNGR_INFO_NAME, "DIRMNGR_INFO", [The name of the dirmngr info envvar]) AC_DEFINE_UNQUOTED(SCDAEMON_SOCK_NAME, "S.scdaemon", [The name of the SCdaemon 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, @@ -2029,6 +2063,7 @@ g10/Makefile sm/Makefile agent/Makefile scd/Makefile +tpm2d/Makefile g13/Makefile dirmngr/Makefile tools/gpg-zip @@ -2068,6 +2103,7 @@ echo " Default agent: $show_gnupg_agent_pgm Default pinentry: $show_gnupg_pinentry_pgm Default scdaemon: $show_gnupg_scdaemon_pgm + Default tpm2daemon: $show_gnupg_tpm2daemon_pgm Default dirmngr: $show_gnupg_dirmngr_pgm Dirmngr auto start: $dirmngr_auto_start @@ -2076,6 +2112,7 @@ echo " TLS support: $use_tls_library TOFU support: $use_tofu Tor support: $show_tor_support + TPM support: $have_libtss " if test x"$use_regex" != xyes ; then echo " diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index 135d8004d..5b472361e 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -134,6 +134,9 @@ typedef enum /* The GnuPG SCDaemon. */ GC_BACKEND_SCDAEMON, + /* The TPM2 daemon */ + GC_BACKEND_TPM2DAEMON, + /* The GnuPG directory manager. */ GC_BACKEND_DIRMNGR, @@ -190,6 +193,8 @@ static const struct gpg_agent_runtime_change, GPGCONF_NAME"-" GPG_AGENT_NAME ".conf" }, { SCDAEMON_DISP_NAME, SCDAEMON_NAME, GNUPG_MODULE_NAME_SCDAEMON, scdaemon_runtime_change, GPGCONF_NAME"-" SCDAEMON_NAME ".conf" }, + { TPM2DAEMON_DISP_NAME, TPM2DAEMON_NAME, GNUPG_MODULE_NAME_TPM2DAEMON, + NULL, GPGCONF_NAME"-" TPM2DAEMON_NAME ".conf" }, { DIRMNGR_DISP_NAME, DIRMNGR_NAME, GNUPG_MODULE_NAME_DIRMNGR, dirmngr_runtime_change, GPGCONF_NAME "-" DIRMNGR_NAME ".conf" }, { DIRMNGR_DISP_NAME " LDAP Server List", NULL, 0, @@ -600,6 +605,15 @@ static gc_option_t gc_options_gpg_agent[] = }; #endif /*BUILD_WITH_AGENT*/ +static gc_option_t gc_options_tpm2daemon[] = + { + /* The configuration file to which we write the changes. */ + { GPGCONF_NAME"-"TPM2DAEMON_NAME".conf", + GC_OPT_FLAG_NONE, GC_LEVEL_INTERNAL, + NULL, NULL, GC_ARG_TYPE_FILENAME, GC_BACKEND_TPM2DAEMON }, + + GC_OPTION_NULL, + }; #ifndef BUILD_WITH_SCDAEMON #define gc_options_scdaemon NULL @@ -1094,6 +1108,7 @@ static const struct { "gpg", "gnupg", N_("OpenPGP"), gc_options_gpg }, { "gpg-agent","gnupg", N_("Private Keys"), gc_options_gpg_agent }, { "scdaemon", "gnupg", N_("Smartcards"), gc_options_scdaemon }, + { "tpm2daemon", "gnupg", N_("TPM2"), gc_options_tpm2daemon }, { "gpgsm", "gnupg", N_("S/MIME"), gc_options_gpgsm }, { "dirmngr", "gnupg", N_("Network"), gc_options_dirmngr }, { "pinentry", "gnupg", N_("Passphrase Entry"), gc_options_pinentry } diff --git a/tpm2d/Makefile.am b/tpm2d/Makefile.am new file mode 100644 index 000000000..85e9f4267 --- /dev/null +++ b/tpm2d/Makefile.am @@ -0,0 +1,18 @@ +AM_CPPFLAGS = + +include $(top_srcdir)/am/cmacros.am + +if HAVE_LIBTSS +libexec_PROGRAMS = tpm2daemon + +AM_CFLAGS = $(LIBGCRYPT_CFLAGS) \ + $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) + +tpm2daemon_SOURCES = command.c \ + tpm2daemon.c \ + tpm2.c tpm2.h + +tpm2daemon_LDADD = $(libcommonpth) \ + $(LIBGCRYPT_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \ + $(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV) $(LIBTSS_LIBS) +endif diff --git a/tpm2d/command.c b/tpm2d/command.c new file mode 100644 index 000000000..7506125ae --- /dev/null +++ b/tpm2d/command.c @@ -0,0 +1,570 @@ +/* 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 . + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef USE_NPTH +# include +#endif + +#include "tpm2daemon.h" +#include "tpm2.h" +#include +#include +//#include "app-common.h" +//#include "iso7816.h" +//#include "apdu.h" /* Required for apdu_*_reader (). */ +//#include "atr.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.\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); + 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" + "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.\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" + "\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.\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; +} + + +/* Send a line with status information via assuan and escape all given + buffers. The variable elements are pairs of (char *, size_t), + terminated with a (NULL, 0). */ +void +send_status_info (ctrl_t ctrl, const char *keyword, ...) +{ + va_list arg_ptr; + const unsigned char *value; + size_t valuelen; + char buf[950], *p; + size_t n; + assuan_context_t ctx = ctrl->server_local->assuan_ctx; + + va_start (arg_ptr, keyword); + + p = buf; + n = 0; + while ( (value = va_arg (arg_ptr, const unsigned char *)) + && n < DIM (buf)-2 ) + { + valuelen = va_arg (arg_ptr, size_t); + if (!valuelen) + continue; /* empty buffer */ + if (n) + { + *p++ = ' '; + n++; + } + for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++) + { + if (*value == '+' || *value == '\"' || *value == '%' + || *value < ' ') + { + sprintf (p, "%%%02X", *value); + p += 3; + n += 2; + } + else if (*value == ' ') + *p++ = '+'; + else + *p++ = *value; + } + } + *p = 0; + assuan_write_status (ctx, keyword, buf); + + va_end (arg_ptr); +} + + +/* Send a ready formatted status line via assuan. */ +void +send_status_direct (ctrl_t ctrl, const char *keyword, const char *args) +{ + assuan_context_t ctx = ctrl->server_local->assuan_ctx; + + if (strchr (args, '\n')) + log_error ("error: LF detected in status line - not sending\n"); + else + assuan_write_status (ctx, keyword, args); +} diff --git a/tpm2d/tpm2.c b/tpm2d/tpm2.c new file mode 100644 index 000000000..9a0e9de3c --- /dev/null +++ b/tpm2d/tpm2.c @@ -0,0 +1,969 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tpm2.h" + +#include "../common/i18n.h" +#include "../common/sexp-parse.h" + +#include TSSINCLUDE(tssutils.h) +#include TSSINCLUDE(tssresponsecode.h) +#include TSSINCLUDE(tssmarshal.h) +#include TSSINCLUDE(Unmarshal_fp.h) +#include TSSINCLUDE(tsscryptoh.h) + +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 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 void +tpm2_error(TPM_RC rc, 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); +} + +#define _TSS_CHECK(f) \ + rc = f; \ + if (rc != TPM_RC_SUCCESS) \ + { \ + tpm2_error(rc, #f); \ + return GPG_ERR_CARD; \ + } + +int +tpm2_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"); + + _TSS_CHECK(TSS_Create(tssc)); + _TSS_CHECK(TSS_SetProperty(*tssc, TPM_DATA_DIR, tpm2_dir)); + return 0; +} + +void +tpm2_end(TSS_CONTEXT *tssc) +{ + TSS_Delete(tssc); +} + +void +tpm2_flush_handle(TSS_CONTEXT *tssc, TPM_HANDLE h) +{ + FlushContext_In in; + + if (!h) + return; + + in.flushHandle = h; + TSS_Execute(tssc, NULL, + (COMMAND_PARAMETERS *)&in, + NULL, + TPM_CC_FlushContext, + TPM_RH_NULL, NULL, 0); +} + +static int +tpm2_get_hmac_handle(TSS_CONTEXT *tssc, TPM_HANDLE *handle, + TPM_HANDLE salt_key) +{ + TPM_RC rc; + StartAuthSession_In in; + StartAuthSession_Out out; + StartAuthSession_Extra extra; + + memset(&in, 0, sizeof(in)); + memset(&extra, 0 , sizeof(extra)); + in.bind = TPM_RH_NULL; + in.sessionType = TPM_SE_HMAC; + in.authHash = TPM_ALG_SHA256; + in.tpmKey = TPM_RH_NULL; + in.symmetric.algorithm = TPM_ALG_AES; + in.symmetric.keyBits.aes = 128; + in.symmetric.mode.aes = TPM_ALG_CFB; + if (salt_key) { + ReadPublic_In rin; + ReadPublic_Out rout; + + rin.objectHandle = salt_key; + rc = TSS_Execute (tssc, + (RESPONSE_PARAMETERS *)&rout, + (COMMAND_PARAMETERS *)&rin, + NULL, + TPM_CC_ReadPublic, + TPM_RH_NULL, NULL, 0); + if (rc) { + tpm2_error(rc, "TPM2_ReadPublic"); + return GPG_ERR_CARD; + } + + /* 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 */ + in.tpmKey = salt_key; + } + rc = TSS_Execute(tssc, + (RESPONSE_PARAMETERS *)&out, + (COMMAND_PARAMETERS *)&in, + (EXTRA_PARAMETERS *)&extra, + TPM_CC_StartAuthSession, + TPM_RH_NULL, NULL, 0); + if (rc) { + tpm2_error(rc, "TPM2_StartAuthSession"); + return GPG_ERR_CARD; + } + + *handle = out.sessionHandle; + + return 0; +} + +static int +tpm2_exec_with_auth(ctrl_t ctrl, TSS_CONTEXT *tssc, + gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info, + char **retstr), + int cmd, char *cmd_str, + void *out, void *in) +{ + TPM_HANDLE ah; + char *auth; + TPM_RC rc; + + rc = pin_cb(ctrl, _("TPM Key Passphrase"), &auth); + if (rc) + return rc; + + rc = tpm2_get_hmac_handle(tssc, &ah, 0); + if (rc) + return rc; + + rc = TSS_Execute(tssc, out, in, NULL, + cmd, + ah, auth, 0, + TPM_RH_NULL, NULL, 0); + gcry_free (auth); + 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; + Load_In in; + Load_Out out; + 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; + + in.parentHandle = parent; + + buf = (BYTE *)priv; + size = priv_len; + TPM2B_PRIVATE_Unmarshal(&in.inPrivate, &buf, &size); + + buf = (BYTE *)pub; + size = pub_len; + TPM2B_PUBLIC_Unmarshal(&in.inPublic, &buf, &size, FALSE); + + *type = in.inPublic.publicArea.type; + + rc = TSS_Execute(tssc, + (RESPONSE_PARAMETERS *)&out, + (COMMAND_PARAMETERS *)&in, + NULL, + TPM_CC_Load, + TPM_RS_PW, NULL, 0, + TPM_RH_NULL, NULL, 0); + if (rc != TPM_RC_SUCCESS) { + tpm2_error(rc, "TPM2_Load"); + return GPG_ERR_CARD; + } + + *key = out.objectHandle; + + 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) +{ + Sign_In in; + Sign_Out out; + int ret; + + /* The TPM insists on knowing the digest type, so + * calculate that from the size */ + switch (digestlen) { + case 20: + in.inScheme.details.rsassa.hashAlg = TPM_ALG_SHA1; + break; + case 32: + in.inScheme.details.rsassa.hashAlg = TPM_ALG_SHA256; + break; + case 48: + in.inScheme.details.rsassa.hashAlg = TPM_ALG_SHA384; + break; +#ifdef TPM_ALG_SHA512 + case 64: + in.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; + } + in.digest.t.size = digestlen; + memcpy(in.digest.t.buffer, digest, digestlen); + in.keyHandle = key; + in.validation.tag = TPM_ST_HASHCHECK; + in.validation.hierarchy = TPM_RH_NULL; + in.validation.digest.t.size = 0; + + if (type == TPM_ALG_RSA) + in.inScheme.scheme = TPM_ALG_RSASSA; + else if (type == TPM_ALG_ECC) + in.inScheme.scheme = TPM_ALG_ECDSA; + else + return GPG_ERR_PUBKEY_ALGO; + + + ret = tpm2_exec_with_auth(ctrl, tssc, pin_cb, + TPM_CC_Sign, "TPM2_Sign", &out, &in); + if (ret) + return ret; + + if (type == TPM_ALG_RSA) + *r_siglen = out.signature.signature.rsassa.sig.t.size; + else if (type == TPM_ALG_ECC) + *r_siglen = out.signature.signature.ecdsa.signatureR.t.size + + out.signature.signature.ecdsa.signatureS.t.size; + + *r_sig = xtrymalloc(*r_siglen); + if (!r_sig) + return GPG_ERR_ENOMEM; + + if (type == TPM_ALG_RSA) + { + memcpy(*r_sig, out.signature.signature.rsassa.sig.t.buffer, *r_siglen); + } + else if (type == TPM_ALG_ECC) + { + memcpy(*r_sig, out.signature.signature.ecdsa.signatureR.t.buffer, + out.signature.signature.ecdsa.signatureR.t.size); + memcpy(*r_sig + out.signature.signature.ecdsa.signatureR.t.size, + out.signature.signature.ecdsa.signatureS.t.buffer, + out.signature.signature.ecdsa.signatureS.t.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; + s->seedValue.b.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(s->sensitive.ecc.t.buffer); + rc = gcry_mpi_print (GCRYMPI_FMT_USG, s->sensitive.ecc.t.buffer, len, &len, d); + s->sensitive.ecc.t.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; + p->objectAttributes.val = TPMA_OBJECT_NODA | + TPMA_OBJECT_SIGN | + TPMA_OBJECT_DECRYPT | + TPMA_OBJECT_USERWITHAUTH; + p->authPolicy.t.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(p->unique.ecc.x.t.buffer, q, len); + p->unique.ecc.x.t.size = len; + memcpy(p->unique.ecc.y.t.buffer, q + len, len); + p->unique.ecc.y.t.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; + s->seedValue.b.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(s->sensitive.rsa.t.buffer); + rc = gcry_mpi_print (GCRYMPI_FMT_USG, s->sensitive.rsa.t.buffer, len, &len, p); + s->sensitive.rsa.t.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; + /* note: all our keys are decrypt only. This is because + * we use the TPM2_RSA_Decrypt operation for both signing + * and decryption (see e_tpm2.c for details) */ + p->objectAttributes.val = TPMA_OBJECT_NODA | + TPMA_OBJECT_DECRYPT | + TPMA_OBJECT_SIGN | + TPMA_OBJECT_USERWITHAUTH; + p->authPolicy.t.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(p->unique.rsa.t.buffer); + p->parameters.rsaDetail.keyBits = gcry_mpi_get_nbits (n); + rc = gcry_mpi_print (GCRYMPI_FMT_USG, p->unique.rsa.t.buffer, len, &len, n); + p->unique.rsa.t.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(TPM2B_NAME *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) { + /* copy the digest */ + memcpy(name->t.name + sizeof(TPMI_ALG_HASH), (uint8_t *)&digest.digest, sizeInBytes); + /* copy the hash algorithm */ + TPMI_ALG_HASH nameAlgNbo = htons(tpmtPublic->nameAlg); + memcpy(name->t.name, (uint8_t *)&nameAlgNbo, sizeof(TPMI_ALG_HASH)); + /* set the size */ + name->t.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, + TPM2B_NAME *name, + TPM_ALG_ID nalg, + TPMT_SYM_DEF_OBJECT *symdef, + TPM2B_DATA *innerkey, + TPM2B_PRIVATE *p) +{ + BYTE *buf = p->t.buffer; + + p->t.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->t.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->t.size += bsize; + + /* compute hash of unencrypted marshalled sensitive and + * write to the digest buffer */ + hash.hashAlg = nalg; + TSS_Hash_Generate(&hash, bsize, s2b, + name->t.size, name->t.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->b.buffer, innerkey->b.size); + /* encrypt the hash and sensitive in-place */ + gcry_cipher_encrypt(hd, p->t.buffer, p->t.size, NULL, 0); + gcry_cipher_close(hd); + + } else if (symdef->algorithm == TPM_ALG_NULL) { + TPM2B *s2b = (TPM2B *)buf; + int32_t size = sizeof(*s); + UINT16 bsize = 0, written = 0; + + 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->b.size += bsize + sizeof(s2b->size); + } else { + log_error ("Unknown symmetric algorithm\n"); + return TPM_RC_SYMMETRIC; + } + + return TPM_RC_SUCCESS; +} + +static void +tpm2_encrypt_duplicate(Import_In *iin, TPMT_SENSITIVE *s) +{ + TPM2B_NAME name; + TPMT_PUBLIC *p = &iin->objectPublic.publicArea; + const int aes_key_bits = 128; + const int aes_key_bytes = aes_key_bits/8; + + tpm2_ObjectPublic_GetName(&name, p); + gcry_randomize(iin->encryptionKey.t.buffer, + aes_key_bytes, GCRY_STRONG_RANDOM); + iin->encryptionKey.t.size = aes_key_bytes; + + /* set random iin.symSeed */ + iin->inSymSeed.t.size = 0; + iin->symmetricAlg.algorithm = TPM_ALG_AES; + iin->symmetricAlg.keyBits.aes = aes_key_bits; + iin->symmetricAlg.mode.aes = TPM_ALG_CFB; + + tpm2_SensitiveToDuplicate(s, &name, p->nameAlg, &iin->symmetricAlg, + &iin->encryptionKey, &iin->duplicate); +} + +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) +{ + Import_In iin; + Import_Out iout; + 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; + + iin.parentHandle = TPM2_PARENT; + ret = sexp_to_tpm2(&iin.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; + s.authValue.b.size = strlen(passphrase); + memcpy(s.authValue.b.buffer, passphrase, s.authValue.b.size); + + /* 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 */ + tpm2_encrypt_duplicate(&iin, &s); + + /* 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, iin.parentHandle); + if (rc) + return GPG_ERR_CARD; + + rc = TSS_Execute(tssc, + (RESPONSE_PARAMETERS *)&iout, + (COMMAND_PARAMETERS *)&iin, + NULL, + TPM_CC_Import, + ah, NULL, TPMA_SESSION_DECRYPT, + TPM_RH_NULL, NULL, 0); + if (rc) { + tpm2_error(rc, "TPM2_Import"); + /* failure means auth handle is not flushed */ + tpm2_flush_handle(tssc, ah); + return GPG_ERR_CARD; + } + + size = sizeof(pub); + buffer = pub; + len = 0; + TSS_TPM2B_PUBLIC_Marshal(&iin.objectPublic, + &len, &buffer, &size); + pub_len = len; + + size = sizeof(priv); + buffer = priv; + len = 0; + TSS_TPM2B_PRIVATE_Marshal(&iout.outPrivate, + &len, &buffer, &size); + priv_len = len; + + *shadow_info = make_tpm2_shadow_info(iin.parentHandle, 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) +{ + ECDH_ZGen_In in; + ECDH_ZGen_Out out; + 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; + + in.keyHandle = key; + memcpy(in.inPoint.point.x.t.buffer, ciphertext + 1, len); + in.inPoint.point.x.t.size = len; + memcpy(in.inPoint.point.y.t.buffer, ciphertext + 1 + len, len); + in.inPoint.point.y.t.size = len; + + ret = tpm2_exec_with_auth(ctrl, tssc, pin_cb, TPM_CC_ECDH_ZGen, + "TPM2_ECDH_ZGen", &out, &in); + if (ret) + return ret; + + *decrypt_len = out.outPoint.point.x.t.size + out.outPoint.point.y.t.size + 1; + *decrypt = xtrymalloc(*decrypt_len); + (*decrypt)[0] = 0x04; + memcpy(*decrypt + 1, out.outPoint.point.x.t.buffer, + out.outPoint.point.x.t.size); + memcpy(*decrypt + 1 + out.outPoint.point.x.t.size, + out.outPoint.point.y.t.buffer, + out.outPoint.point.y.t.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) +{ + RSA_Decrypt_In in; + RSA_Decrypt_Out out; + int ret; + + in.keyHandle = key; + in.inScheme.scheme = TPM_ALG_RSAES; + in.cipherText.t.size = ciphertext_len; + memcpy (in.cipherText.t.buffer, ciphertext, ciphertext_len); + in.label.t.size = 0; + + ret = tpm2_exec_with_auth(ctrl, tssc, pin_cb, TPM_CC_RSA_Decrypt, + "TPM2_RSA_Decrypt", &out, &in); + if (ret) + return ret; + + *decrypt_len = out.message.t.size; + *decrypt = xtrymalloc(out.message.t.size); + memcpy (*decrypt, out.message.t.buffer, out.message.t.size); + + return 0; +} diff --git a/tpm2d/tpm2.h b/tpm2d/tpm2.h new file mode 100644 index 000000000..38ee8a81c --- /dev/null +++ b/tpm2d/tpm2.h @@ -0,0 +1,39 @@ +#ifndef _TPM2_H +#define _TPM2_H + +#include "../common/util.h" + +#define TSSINCLUDE(x) < TSS_INCLUDE/x > +#include TSSINCLUDE(tss.h) + +#define TSS2_LIB "libtss.so.0" +#define TPM2_PARENT 0x81000001 + +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); +int tpm2_rsa_decrypt(ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key, + gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info, + char **retstr), + const char *ciphertext, int ciphertext_len, + char **decrypt, size_t *decrypt_len); +int tpm2_ecc_decrypt(ctrl_t ctrl, TSS_CONTEXT *tssc, TPM_HANDLE key, + gpg_error_t (*pin_cb)(ctrl_t ctrl, const char *info, + char **retstr), + const char *ciphertext, int ciphertext_len, + char **decrypt, size_t *decrypt_len); + +#endif diff --git a/tpm2d/tpm2daemon.c b/tpm2d/tpm2daemon.c new file mode 100644 index 000000000..6761aa9ef --- /dev/null +++ b/tpm2d/tpm2daemon.c @@ -0,0 +1,1434 @@ +/* tpm2daemon.c - The GnuPG tpm2 Daemon + * Copyright (C) 2001-2002, 2004-2005, 2007-2009 Free Software Foundation, Inc. + * Copyright (C) 2001-2002, 2004-2005, 2007-2014 Werner Koch + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifndef HAVE_W32_SYSTEM +#include +#include +#endif /*HAVE_W32_SYSTEM*/ +#include +#include +#include + +#define GNUPG_COMMON_NEED_AFLOCAL +#include "tpm2daemon.h" +#include + +#include /* malloc hooks */ + +#include "../common/i18n.h" +#include "../common/sysutils.h" +#include "../common/gc-opt-flags.h" +#include "../common/asshelp.h" +#include "../common/exechelp.h" +#include "../common/init.h" + +#ifndef ENAMETOOLONG +# define ENAMETOOLONG EINVAL +#endif + +enum cmd_and_opt_values +{ aNull = 0, + oCsh = 'c', + oQuiet = 'q', + oSh = 's', + oVerbose = 'v', + + oNoVerbose = 500, + aGPGConfList, + aGPGConfTest, + oOptions, + oDebug, + oDebugAll, + oDebugLevel, + oDebugWait, + oDebugAllowCoreDump, + oDebugCCIDDriver, + oDebugLogTid, + oDebugAssuanLogCats, + oNoGreeting, + oNoOptions, + oHomedir, + oNoDetach, + oNoGrab, + oLogFile, + oServer, + oMultiServer, + oDaemon, + oBatch, + oReaderPort, + oCardTimeout, + octapiDriver, + opcscDriver, + oDisableCCID, + oDisableOpenSC, + oDisablePinpad, + oAllowAdmin, + oDenyAdmin, + oDisableApplication, + oEnablePinpadVarlen, + oListenBacklog +}; + + + +static ARGPARSE_OPTS 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 (oDebugCCIDDriver, "debug-ccid-driver", "@"), + 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 (oReaderPort, "reader-port", + N_("|N|connect to reader at port N")), + ARGPARSE_s_s (octapiDriver, "ctapi-driver", + N_("|NAME|use NAME as ct-API driver")), + ARGPARSE_s_s (opcscDriver, "pcsc-driver", + N_("|NAME|use NAME as PC/SC driver")), + ARGPARSE_s_n (oDisableCCID, "disable-ccid", +#ifdef HAVE_LIBUSB + N_("do not use the internal CCID driver") +#else + "@" +#endif + /* end --disable-ccid */), + ARGPARSE_s_u (oCardTimeout, "card-timeout", + N_("|N|disconnect the card after N seconds of inactivity")), + + ARGPARSE_s_n (oDisablePinpad, "disable-pinpad", + N_("do not use a reader's pinpad")), + ARGPARSE_ignore (300, "disable-keypad"), + + ARGPARSE_s_n (oAllowAdmin, "allow-admin", "@"), + ARGPARSE_s_n (oDenyAdmin, "deny-admin", + N_("deny the use of admin card commands")), + ARGPARSE_s_s (oDisableApplication, "disable-application", "@"), + ARGPARSE_s_n (oEnablePinpadVarlen, "enable-pinpad-varlen", + N_("use variable length input for pinpad")), + ARGPARSE_s_s (oHomedir, "homedir", "@"), + ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"), + + ARGPARSE_end () +}; + + +/* The list of supported debug flags. */ +static struct debug_flags_s debug_flags [] = + { + { DBG_MPI_VALUE , "mpi" }, + { DBG_CRYPTO_VALUE , "crypto" }, + { DBG_MEMORY_VALUE , "memory" }, + { DBG_CACHE_VALUE , "cache" }, + { DBG_MEMSTAT_VALUE, "memstat" }, + { DBG_HASHING_VALUE, "hashing" }, + { DBG_IPC_VALUE , "ipc" }, + { DBG_CARD_IO_VALUE, "cardio" }, + { DBG_READER_VALUE , "reader" }, + { 0, NULL } + }; + + +/* The card driver we use by default for PC/SC. */ +#if defined(HAVE_W32_SYSTEM) || defined(__CYGWIN__) +#define DEFAULT_PCSC_DRIVER "winscard.dll" +#elif defined(__APPLE__) +#define DEFAULT_PCSC_DRIVER "/System/Library/Frameworks/PCSC.framework/PCSC" +#elif defined(__GLIBC__) +#define DEFAULT_PCSC_DRIVER "libpcsclite.so.1" +#else +#define DEFAULT_PCSC_DRIVER "libpcsclite.so" +#endif + +/* 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|DBG_CACHE_VALUE|DBG_CARD_IO_VALUE); + else if (!strcmp (level, "guru") || numok) + { + opt.debug = ~0; + /* Unless the "guru" string has been used we don't want to allow + hashing debugging. The rationale is that people tend to + select the highest debug value and would then clutter their + disk with debug files which may reveal confidential data. */ + if (numok) + opt.debug &= ~(DBG_HASHING_VALUE); + } + 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 ) +{ + ARGPARSE_ARGS pargs; + int orig_argc; + char **orig_argv; + FILE *configfp = NULL; + char *configname = NULL; + const char *shell; + unsigned int configlineno; + int parse_debug = 0; + const char *debug_level = NULL; + int default_config =1; + 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; + const char *config_filename = NULL; + int allow_coredump = 0; + struct assuan_malloc_hooks malloc_hooks; + int res; + npth_t pipecon_handler; + + early_system_init (); + 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.allow_admin = 1; + opt.pcsc_driver = DEFAULT_PCSC_DRIVER; + + 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= 1|(1<<6); /* do not remove the args, ignore version */ + while (arg_parse( &pargs, opts)) + { + if (pargs.r_opt == oDebug || pargs.r_opt == oDebugAll) + parse_debug++; + else if (pargs.r_opt == oOptions) + { /* yes there is one, so we do not try the default one, but + read the option file when it is encountered at the + commandline */ + default_config = 0; + } + else if (pargs.r_opt == oNoOptions) + default_config = 0; /* --no-options */ + else if (pargs.r_opt == oHomedir) + gnupg_set_homedir (pargs.r.ret_str); + } + + /* initialize the secure memory. */ + gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0); + maybe_setuid = 0; + + /* + Now we are working under our real uid + */ + + + if (default_config) + configname = make_filename (gnupg_homedir (), TPM2DAEMON_NAME EXTSEP_S "conf", + NULL ); + + + argc = orig_argc; + argv = orig_argv; + pargs.argc = &argc; + pargs.argv = &argv; + pargs.flags= 1; /* do not remove the args */ + next_pass: + if (configname) + { + configlineno = 0; + configfp = fopen (configname, "r"); + if (!configfp) + { + if (default_config) + { + if( parse_debug ) + log_info (_("Note: no default option file '%s'\n"), + configname ); + } + else + { + log_error (_("option file '%s': %s\n"), + configname, strerror(errno) ); + exit(2); + } + xfree (configname); + configname = NULL; + } + if (parse_debug && configname ) + log_info (_("reading options from '%s'\n"), configname ); + default_config = 0; + } + + while (optfile_parse( configfp, configname, &configlineno, &pargs, opts) ) + { + switch (pargs.r_opt) + { + case aGPGConfList: gpgconf_list = 1; break; + case aGPGConfTest: gpgconf_list = 2; break; + case oQuiet: opt.quiet = 1; break; + case oVerbose: opt.verbose++; break; + case oBatch: opt.batch=1; 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 oDebugCCIDDriver: + break; + case oDebugLogTid: + log_set_pid_suffix_cb (tid_log_callback); + break; + case oDebugAssuanLogCats: + set_libassuan_log_cats (pargs.r.ret_ulong); + break; + + case oOptions: + /* config files may not be nested (silently ignore them) */ + if (!configfp) + { + xfree(configname); + configname = xstrdup(pargs.r.ret_str); + goto next_pass; + } + 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 oReaderPort: opt.reader_port = pargs.r.ret_str; break; + case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break; + case opcscDriver: opt.pcsc_driver = pargs.r.ret_str; break; + case oDisableCCID: opt.disable_ccid = 1; break; + case oDisableOpenSC: break; + + case oDisablePinpad: opt.disable_pinpad = 1; break; + + case oAllowAdmin: /* Dummy because allow is now the default. */ + break; + case oDenyAdmin: opt.allow_admin = 0; break; + + case oCardTimeout: opt.card_timeout = pargs.r.ret_ulong; break; + + case oDisableApplication: + add_to_strlist (&opt.disabled_applications, pargs.r.ret_str); + break; + + case oEnablePinpadVarlen: opt.enable_pinpad_varlen = 1; break; + + case oListenBacklog: + listen_backlog = pargs.r.ret_int; + break; + + default: + pargs.err = configfp? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR; + break; + } + } + if (configfp) + { + fclose( configfp ); + configfp = NULL; + /* Keep a copy of the config name for use by --gpgconf-list. */ + config_filename = configname; + configname = NULL; + goto next_pass; + } + xfree (configname); + configname = NULL; + if (log_get_errorcount(0)) + exit(2); + if (nogreeting ) + greeting = 0; + + if (greeting) + { + es_fprintf (es_stderr, "%s %s; %s\n", + strusage(11), strusage(13), strusage(14) ); + es_fprintf (es_stderr, "%s\n", 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 0 + if (initialize_module_command ()) + { + log_error ("initialization failed\n"); + cleanup (); + exit (1); + } +#endif + + if (gpgconf_list == 2) + tpm2d_exit (0); + if (gpgconf_list) + { + /* List options and default values in the GPG Conf format. */ + char *filename = NULL; + char *filename_esc; + + if (config_filename) + filename = xstrdup (config_filename); + else + filename = make_filename (gnupg_homedir (), + TPM2DAEMON_NAME EXTSEP_S "conf", NULL); + filename_esc = percent_escape (filename, NULL); + + es_printf ("%s-%s.conf:%lu:\"%s\n", + GPGCONF_NAME, TPM2DAEMON_NAME, + GC_OPT_FLAG_DEFAULT, filename_esc); + xfree (filename_esc); + xfree (filename); + + 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 ); + + es_printf ("reader-port:%lu:\n", GC_OPT_FLAG_NONE ); + es_printf ("ctapi-driver:%lu:\n", GC_OPT_FLAG_NONE ); + es_printf ("pcsc-driver:%lu:\"%s:\n", + GC_OPT_FLAG_DEFAULT, DEFAULT_PCSC_DRIVER ); +#ifdef HAVE_LIBUSB + es_printf ("disable-ccid:%lu:\n", GC_OPT_FLAG_NONE ); +#endif + es_printf ("deny-admin:%lu:\n", GC_OPT_FLAG_NONE ); + es_printf ("disable-pinpad:%lu:\n", GC_OPT_FLAG_NONE ); + es_printf ("card-timeout:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, 0); + es_printf ("enable-pinpad-varlen:%lu:\n", GC_OPT_FLAG_NONE ); + + tpm2d_exit (0); + } + + /* Now start with logging to a file if this is desired. */ + if (logfile) + { + log_set_file (logfile); + log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID); + } + + if (debug_wait && pipe_server) + { + log_debug ("waiting for debugger - my pid is %u .....\n", + (unsigned int)getpid()); + gnupg_sleep (debug_wait); + log_debug ("... okay\n"); + } + + if (pipe_server) + { + /* This is the simple pipe based server */ + ctrl_t ctrl; + npth_attr_t tattr; + int fd = -1; + +#ifndef HAVE_W32_SYSTEM + { + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, NULL); + } +#endif + + npth_init (); + gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); + + /* If --debug-allow-core-dump has been given we also need to + switch the working directory to a place where we can actually + write. */ + if (allow_coredump) + { + if (chdir("/tmp")) + log_debug ("chdir to '/tmp' failed: %s\n", strerror (errno)); + else + log_debug ("changed working directory to '/tmp'\n"); + } + + /* In multi server mode we need to listen on an additional + socket. Create that socket now before starting the handler + for the pipe connection. This allows that handler to send + back the name of that socket. */ + if (multi_server) + { + socket_name = create_socket_name (TPM2DAEMON_SOCK_NAME); + fd = FD2INT(create_server_socket (socket_name, + &redir_socket_name, &socket_nonce)); + } + + res = npth_attr_init (&tattr); + if (res) + { + log_error ("error allocating thread attributes: %s\n", + strerror (res)); + tpm2d_exit (2); + } + npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED); + + ctrl = xtrycalloc (1, sizeof *ctrl); + if ( !ctrl ) + { + log_error ("error allocating connection control data: %s\n", + strerror (errno) ); + tpm2d_exit (2); + } + ctrl->thread_startup.fd = GNUPG_INVALID_FD; + res = npth_create (&pipecon_handler, &tattr, start_connection_thread, ctrl); + if (res) + { + log_error ("error spawning pipe connection handler: %s\n", + strerror (res) ); + xfree (ctrl); + tpm2d_exit (2); + } + npth_setname_np (pipecon_handler, "pipe-connection"); + npth_attr_destroy (&tattr); + + /* We run handle_connection to wait for the shutdown signal and + to run the ticker stuff. */ + handle_connections (fd); + if (fd != -1) + close (fd); + } + else if (!is_daemon) + { + log_info (_("please use the option '--daemon'" + " to run the program in the background\n")); + } + else + { /* Regular server mode */ + int fd; +#ifndef HAVE_W32_SYSTEM + pid_t pid; + int i; +#endif + + /* Create the socket. */ + socket_name = create_socket_name (TPM2DAEMON_SOCK_NAME); + fd = FD2INT (create_server_socket (socket_name, + &redir_socket_name, &socket_nonce)); + + + fflush (NULL); +#ifdef HAVE_W32_SYSTEM + (void)csh_style; + (void)nodetach; +#else + pid = fork (); + if (pid == (pid_t)-1) + { + log_fatal ("fork failed: %s\n", strerror (errno) ); + exit (1); + } + else if (pid) + { /* we are the parent */ + char *infostr; + + close (fd); + + /* create the info string: :: */ + if (gpgrt_asprintf (&infostr, "TPM2DAEMON_INFO=%s:%lu:1", + socket_name, (ulong) pid) < 0) + { + log_error ("out of core\n"); + kill (pid, SIGTERM); + exit (1); + } + *socket_name = 0; /* don't let cleanup() remove the socket - + the child should do this from now on */ + if (argc) + { /* run the program given on the commandline */ + if (putenv (infostr)) + { + log_error ("failed to set environment: %s\n", + strerror (errno) ); + kill (pid, SIGTERM ); + exit (1); + } + execvp (argv[0], argv); + log_error ("failed to run the command: %s\n", strerror (errno)); + kill (pid, SIGTERM); + exit (1); + } + else + { + /* Print the environment string, so that the caller can use + shell's eval to set it */ + if (csh_style) + { + *strchr (infostr, '=') = ' '; + es_printf ( "setenv %s;\n", infostr); + } + else + { + es_printf ( "%s; export TPM2DAEMON_INFO;\n", infostr); + } + xfree (infostr); + exit (0); + } + /* NOTREACHED */ + } /* end parent */ + + /* This is the child. */ + + npth_init (); + gpgrt_set_syscall_clamp (npth_unprotect, npth_protect); + + /* Detach from tty and put process into a new session. */ + if (!nodetach ) + { + /* Close stdin, stdout and stderr unless it is the log stream. */ + for (i=0; i <= 2; i++) + { + if (!log_test_fd (i) && i != fd ) + { + if ( !close (i) + && open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1) + { + log_error ("failed to open '%s': %s\n", + "/dev/null", strerror (errno)); + cleanup (); + exit (1); + } + } + } + + if (setsid() == -1) + { + log_error ("setsid() failed: %s\n", strerror(errno) ); + cleanup (); + exit (1); + } + } + + { + struct sigaction sa; + + sa.sa_handler = SIG_IGN; + sigemptyset (&sa.sa_mask); + sa.sa_flags = 0; + sigaction (SIGPIPE, &sa, NULL); + } + +#endif /*!HAVE_W32_SYSTEM*/ + + if (gnupg_chdir (gnupg_daemon_rootdir ())) + { + log_error ("chdir to '%s' failed: %s\n", + gnupg_daemon_rootdir (), strerror (errno)); + exit (1); + } + + handle_connections (fd); + + close (fd); + } + + return 0; +} + +void +tpm2d_exit (int rc) +{ +#if 0 +#warning no update_random_seed_file + update_random_seed_file(); +#endif +#if 0 + /* at this time a bit annoying */ + if (opt.debug & DBG_MEMSTAT_VALUE) + { + gcry_control( GCRYCTL_DUMP_MEMORY_STATS ); + gcry_control( GCRYCTL_DUMP_RANDOM_STATS ); + } + if (opt.debug) + gcry_control (GCRYCTL_DUMP_SECMEM_STATS ); +#endif + 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 () +{ + 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 and resetting cards\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", strusage(11), strusage(13) ); + cleanup (); + tpm2d_exit (0); + } + break; + + case SIGINT: + log_info ("SIGINT received - immediate shutdown\n"); + log_info( "%s %s stopped\n", strusage(11), 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"), strusage(11), strusage(13)); + npth_attr_destroy (&tattr); +} + +/* Return the number of active connections. */ +int +get_active_connection_count (void) +{ + return active_connections; +} diff --git a/tpm2d/tpm2daemon.h b/tpm2d/tpm2daemon.h new file mode 100644 index 000000000..5e07d9d93 --- /dev/null +++ b/tpm2d/tpm2daemon.h @@ -0,0 +1,130 @@ +/* scdaemon.h - Global definitions for the SCdaemon + * Copyright (C) 2001, 2002, 2003 Free Software Foundation, Inc. + * + * This file is part of GnuPG. + * + * GnuPG is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * GnuPG is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + */ + +#ifndef TPM2DAEMON_H +#define TPM2DAEMON_H + +#ifdef GPG_ERR_SOURCE_DEFAULT +#error GPG_ERR_SOURCE_DEFAULT already defined +#endif +/* FIXME: assuan doesn't have a define for TPM2D */ +#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_UNKNOWN +#include + +#include +#include +#include "../common/util.h" +#include "../common/sysutils.h" + +/* To convey some special hash algorithms we use algorithm numbers + reserved for application use. */ +#ifndef GCRY_MODULE_ID_USER +#define GCRY_MODULE_ID_USER 1024 +#endif +#define MD_USER_TLS_MD5SHA1 (GCRY_MODULE_ID_USER+1) + +/* Maximum length of a digest. */ +#define MAX_DIGEST_LEN 64 + + + +/* A large struct name "opt" to keep global flags. */ +struct +{ + unsigned int debug; /* Debug flags (DBG_foo_VALUE). */ + int verbose; /* Verbosity level. */ + int quiet; /* Be as quiet as possible. */ + int dry_run; /* Don't change any persistent data. */ + int batch; /* Batch mode. */ + const char *ctapi_driver; /* Library to access the ctAPI. */ + const char *pcsc_driver; /* Library to access the PC/SC system. */ + const char *reader_port; /* NULL or reder port to use. */ + int disable_ccid; /* Disable the use of the internal CCID driver. */ + int disable_pinpad; /* Do not use a pinpad. */ + int enable_pinpad_varlen; /* Use variable length input for pinpad. */ + int allow_admin; /* Allow the use of admin commands for certain + cards. */ + strlist_t disabled_applications; /* Card applications we do not + want to use. */ + unsigned long card_timeout; /* Disconnect after N seconds of inactivity. */ +} opt; + + +#define DBG_MPI_VALUE 2 /* debug mpi details */ +#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */ +#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */ +#define DBG_CACHE_VALUE 64 /* debug the caching */ +#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */ +#define DBG_HASHING_VALUE 512 /* debug hashing operations */ +#define DBG_IPC_VALUE 1024 +#define DBG_CARD_IO_VALUE 2048 +#define DBG_READER_VALUE 4096 /* Trace reader related functions. */ + +#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE) +#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE) +#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE) +#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE) +#define DBG_IPC (opt.debug & DBG_IPC_VALUE) +#define DBG_CARD_IO (opt.debug & DBG_CARD_IO_VALUE) +#define DBG_READER (opt.debug & DBG_READER_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; + +/*-- scdaemon.c --*/ +void tpm2d_exit (int rc); +const char *scd_get_socket_name (void); + +/*-- command.c --*/ +gpg_error_t initialize_module_command (void); +int tpm2d_command_handler (ctrl_t, int); +void send_status_info (ctrl_t ctrl, const char *keyword, ...) + GPGRT_ATTR_SENTINEL(1); +void send_status_direct (ctrl_t ctrl, const char *keyword, const char *args); +void send_client_notifications (app_t app, int removal); +void tpm2d_kick_the_loop (void); +int get_active_connection_count (void); + +#endif /*TPM2DAEMON_H*/ -- 2.26.2 From wk at gnupg.org Wed Jun 24 12:53:42 2020 From: wk at gnupg.org (Werner Koch) Date: Wed, 24 Jun 2020 12:53:42 +0200 Subject: [PATCH 0/5] Add TPM2 support to gnupg In-Reply-To: <20200614172649.23749-1-James.Bottomley@HansenPartnership.com> (James Bottomley via Gnupg-devel's message of "Sun, 14 Jun 2020 10:26:44 -0700") References: <20200614172649.23749-1-James.Bottomley@HansenPartnership.com> Message-ID: <87pn9ozqdl.fsf@wheatstone.g10code.de> Hi! thanks for the patches. They are quite large and thus I don not want to have them in our 2.2 LTS branch. Instead I started to port them to master (2.3). There are a couple of changes how scdaemon is managed in master and thus part 2 of your packages requires quiet some changes. I really liked your changes to separate things as far as possible. I have pushed part 1 and part 2 as a start and will look into adding the remaining patches as time permits. To avoid build problems and also because we do not want to include the TPM thing into our regular tests, the TPM support will need to be hidden" behind a configure option; I hope that is okay for you. Salam-Shalom, Werner -- Die Gedanken sind frei. Ausnahmen regelt ein Bundesgesetz. -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 227 bytes Desc: not available URL: From James.Bottomley at HansenPartnership.com Wed Jun 24 18:42:33 2020 From: James.Bottomley at HansenPartnership.com (James Bottomley) Date: Wed, 24 Jun 2020 09:42:33 -0700 Subject: [PATCH 0/5] Add TPM2 support to gnupg In-Reply-To: <87pn9ozqdl.fsf@wheatstone.g10code.de> References: <20200614172649.23749-1-James.Bottomley@HansenPartnership.com> <87pn9ozqdl.fsf@wheatstone.g10code.de> Message-ID: <1593016953.3375.5.camel@HansenPartnership.com> On Wed, 2020-06-24 at 12:53 +0200, Werner Koch wrote: > Hi! > > thanks for the patches. They are quite large and thus I don not want > to have them in our 2.2 LTS branch. Instead I started to port them > to master (2.3). Thanks. I've actually got a v2 coming that strips out more of the scdaemon pieces that were copied over. It also adds support for systems which don't have the persistent storage key provisioned, which seems like it's going to be all of them. > There are a couple of changes how scdaemon is managed in master and > thus part 2 of your packages requires quiet some changes. > > I really liked your changes to separate things as far as possible. > > I have pushed part 1 and part 2 as a start and will look into adding > the remaining patches as time permits. OK, I can base off that. I have a minor update ... some pieces of patches 1 and 2 managed to stray into the later series which I noticed after I'd sent it, but I can do an explicit interdiff. > To avoid build problems and also because we do not want to include > the TPM thing into our regular tests, the TPM support will need to be > hidden" behind a configure option; I hope that is okay for you. I'm fairly ambivalent. I like systems that just build according to the capabilities, but it is nice to have autoconf tell you why the --with- tpm you selected can't work without X packages. However, for testing there's no reason why TPM support should impact the regular tests at all. As long as you don't have a TPM format key and never exercise keytotpm, the TPM side will never get used. I can also give you a test harness based on the software TPM emulator that can run TPM based tests. You can see how I use it here: https://git.kernel.org/pub/scm/linux/kernel/git/jejb/openssl_tpm2_engine.git/tree/tests Although I bet I should have used a test runner to start and stop the software TPM. James -------------- next part -------------- A non-text attachment was scrubbed... Name: signature.asc Type: application/pgp-signature Size: 228 bytes Desc: This is a digitally signed message part URL: From James.Bottomley at HansenPartnership.com Thu Jun 25 20:46:31 2020 From: James.Bottomley at HansenPartnership.com (James Bottomley) Date: Thu, 25 Jun 2020 11:46:31 -0700 Subject: [PATCH v2 0/3] Add TPM2 support to gnupg 2.3 Message-ID: <20200625184634.16985-1-James.Bottomley@HansenPartnership.com> This patch series is based on the master branch with the two patches Werner upported applied. I'm not as familiar with this branch, so the patch set is lightly tested, but I have got it to do key conversion, signing and encryption successfully. I may have missed some subtleties of the new daemon structure, but the tpm2daemon is very simple, so hopefully it doesn't matter. This branch also contains the code to derive an EC primary each time, so it will work out of the box on any TPM2 system without the need to create a persistent RSA storage key at 81000001. For those who want to try it out, I've created a git tree with these patches in at https://git.kernel.org/pub/scm/linux/kernel/git/jejb/gnupg.git The master-tpm-daemon branch is based on 2.3 and the stable-2-2-tpm-daemon branch is currently based on 2.2.20 James --- James Bottomley (3): tpm2d: Add tpm2daemon code agent: Add new shadow key type and functions to call tpm2daemon g10: add new command keytotpm to convert a private key to TPM format Makefile.am | 7 +- agent/Makefile.am | 5 + agent/agent.h | 50 ++ agent/call-daemon.c | 3 +- agent/call-tpm2d.c | 248 ++++++++ agent/command.c | 57 ++ agent/divert-tpm2.c | 144 +++++ agent/gpg-agent.c | 4 + agent/keyformat.txt | 12 +- agent/pkdecrypt.c | 8 +- agent/pksign.c | 16 +- am/cmacros.am | 3 + common/homedir.c | 7 + common/mapstrings.c | 1 + common/util.h | 1 + configure.ac | 39 ++ g10/call-agent.c | 22 + g10/call-agent.h | 3 + g10/keyedit.c | 45 +- tools/gpgconf-comp.c | 62 +- tools/gpgconf.h | 3 + tpm2d/Makefile.am | 18 + tpm2d/command.c | 508 +++++++++++++++++ tpm2d/tpm2.c | 1048 ++++++++++++++++++++++++++++++++++ tpm2d/tpm2.h | 36 ++ tpm2d/tpm2daemon.c | 1289 ++++++++++++++++++++++++++++++++++++++++++ tpm2d/tpm2daemon.h | 105 ++++ 27 files changed, 3731 insertions(+), 13 deletions(-) create mode 100644 agent/call-tpm2d.c create mode 100644 agent/divert-tpm2.c create mode 100644 tpm2d/Makefile.am create mode 100644 tpm2d/command.c create mode 100644 tpm2d/tpm2.c create mode 100644 tpm2d/tpm2.h create mode 100644 tpm2d/tpm2daemon.c create mode 100644 tpm2d/tpm2daemon.h -- 2.26.2 From James.Bottomley at HansenPartnership.com Thu Jun 25 20:46:33 2020 From: James.Bottomley at HansenPartnership.com (James Bottomley) Date: Thu, 25 Jun 2020 11:46:33 -0700 Subject: [PATCH v2 2/3] agent: Add new shadow key type and functions to call tpm2daemon In-Reply-To: <20200625184634.16985-1-James.Bottomley@HansenPartnership.com> References: <20200625184634.16985-1-James.Bottomley@HansenPartnership.com> Message-ID: <20200625184634.16985-3-James.Bottomley@HansenPartnership.com> A new shadow key type: "tpm2-v1" is introduced signalling that the shadowed key is handled by the tpm2daemon. A function to identify this type is introduced and diversions to the tpm2daemon functions are conditioned on this function for pkign and pkdecrypt where the same diversions to scd are currently done. The (info) field of the shadowed key stores the actual TPM key. The TPM key is encrypted so only the physical TPM it was created on can read it (so no special protection is required for the info filed), but if the (info) field becomes corrupt or damaged, the key will be lost (unlike the token case, where the key is actually moved inside the token). Note, this commit adds handling for existing TPM format shadow keys, but there is still no way to create them. Signed-off-by: James Bottomley --- agent/Makefile.am | 5 + agent/agent.h | 50 +++++++++ agent/call-daemon.c | 3 +- agent/call-tpm2d.c | 248 ++++++++++++++++++++++++++++++++++++++++++++ agent/command.c | 5 + agent/divert-tpm2.c | 144 +++++++++++++++++++++++++ agent/gpg-agent.c | 4 + agent/keyformat.txt | 12 ++- agent/pkdecrypt.c | 8 +- agent/pksign.c | 16 ++- 10 files changed, 485 insertions(+), 10 deletions(-) create mode 100644 agent/call-tpm2d.c create mode 100644 agent/divert-tpm2.c diff --git a/agent/Makefile.am b/agent/Makefile.am index 64d308838..d72d989d3 100644 --- a/agent/Makefile.am +++ b/agent/Makefile.am @@ -57,6 +57,11 @@ gpg_agent_SOURCES = \ call-daemon.c \ learncard.c +if HAVE_LIBTSS +gpg_agent_SOURCES += divert-tpm2.c \ + call-tpm2d.c +endif + common_libs = $(libcommon) commonpth_libs = $(libcommonpth) if HAVE_W32CE_SYSTEM diff --git a/agent/agent.h b/agent/agent.h index 9c84f6a17..6b8c5fa14 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -59,6 +59,7 @@ enum daemon_type { DAEMON_SCD, + DAEMON_TPM2D, DAEMON_MAX_TYPE }; @@ -456,6 +457,7 @@ gpg_error_t agent_public_key_from_file (ctrl_t ctrl, const unsigned char *grip, gcry_sexp_t *result); int agent_pk_get_algo (gcry_sexp_t s_key); +int agent_is_tpm2_key(gcry_sexp_t s_key); int agent_key_available (const unsigned char *grip); gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip, int *r_keytype, @@ -573,6 +575,44 @@ gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag); void agent_reload_trustlist (void); +/*-- divert-tpm2.c --*/ +#ifdef HAVE_LIBTSS +int divert_tpm2_pksign (ctrl_t ctrl, const char *desc_text, + const unsigned char *digest, size_t digestlen, int algo, + const unsigned char *shadow_info, unsigned char **r_sig, + size_t *r_siglen); +int divert_tpm2_pkdecrypt (ctrl_t ctrl, const char *desc_text, + const unsigned char *cipher, + const unsigned char *shadow_info, + char **r_buf, size_t *r_len, int *r_padding); +int divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip, + gcry_sexp_t s_skey); +#else +static inline int divert_tpm2_pksign (ctrl_t ctrl, const char *desc_text, + const unsigned char *digest, + size_t digestlen, int algo, + const unsigned char *shadow_info, + unsigned char **r_sig, + size_t *r_siglen) +{ + return -EINVAL; +} +static inline int divert_tpm2_pkdecrypt (ctrl_t ctrl, const char *desc_text, + const unsigned char *cipher, + const unsigned char *shadow_info, + char **r_buf, size_t *r_len, + int *r_padding) +{ + return -EINVAL; +} +static inline int divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip, + gcry_sexp_t s_skey) +{ + return -EINVAL; +} +#endif + + /*-- divert-scd.c --*/ int divert_pksign (ctrl_t ctrl, const char *desc_text, @@ -602,6 +642,16 @@ void agent_daemon_check_aliveness (void); void agent_reset_daemon (ctrl_t ctrl); void agent_kill_daemon (enum daemon_type type); +/*-- call-tpm2d.c --*/ +int agent_tpm2d_writekey (ctrl_t ctrl, unsigned char **shadow_info, + gcry_sexp_t s_skey); +int agent_tpm2d_pksign (ctrl_t ctrl, const unsigned char *digest, + size_t digestlen, const unsigned char *shadow_info, + unsigned char **r_sig, size_t *r_siglen); +int agent_tpm2d_pkdecrypt (ctrl_t ctrl, const unsigned char *cipher, + size_t cipherlen, const unsigned char *shadow_info, + char **r_buf, size_t *r_len); + /*-- call-scd.c --*/ int agent_card_learn (ctrl_t ctrl, void (*kpinfo_cb)(void*, const char *), diff --git a/agent/call-daemon.c b/agent/call-daemon.c index 7a2bcbe27..2518a3c0e 100644 --- a/agent/call-daemon.c +++ b/agent/call-daemon.c @@ -45,7 +45,8 @@ * same order as given by the daemon_type enum. */ static const int daemon_modules[DAEMON_MAX_TYPE] = { - GNUPG_MODULE_NAME_SCDAEMON + GNUPG_MODULE_NAME_SCDAEMON, + GNUPG_MODULE_NAME_TPM2DAEMON, }; /* Definition of module local data of the CTRL structure. */ diff --git a/agent/call-tpm2d.c b/agent/call-tpm2d.c new file mode 100644 index 000000000..66401b2bc --- /dev/null +++ b/agent/call-tpm2d.c @@ -0,0 +1,248 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "agent.h" +#include +#include "../common/strlist.h" +#include "../common/sexp-parse.h" +#include "../common/i18n.h" + +static int +start_tpm2d (ctrl_t ctrl) +{ + return daemon_start (DAEMON_TPM2D, ctrl); +} + +static int +unlock_tpm2d (ctrl_t ctrl, gpg_error_t err) +{ + return daemon_unlock (DAEMON_TPM2D, ctrl, err); +} + +static assuan_context_t +daemon_ctx (ctrl_t ctrl) +{ + return daemon_type_ctx (DAEMON_TPM2D, ctrl); +} + +struct inq_parm_s { + assuan_context_t ctx; + gpg_error_t (*getpin_cb)(ctrl_t, const char *, char **); + ctrl_t ctrl; + /* The next fields are used by inq_keydata. */ + const unsigned char *keydata; + size_t keydatalen; + /* following only used by inq_extra */ + const unsigned char *extra; + size_t extralen; + char *pin; +}; + +static gpg_error_t +inq_needpin (void *opaque, const char *line) +{ + struct inq_parm_s *parm = opaque; + char *pin = NULL; + gpg_error_t rc; + const char *s; + + if ((s = has_leading_keyword (line, "NEEDPIN"))) + { + rc = parm->getpin_cb (parm->ctrl, s, &pin); + if (!rc) + rc = assuan_send_data (parm->ctx, pin, strlen(pin)); + parm->pin = pin; + } + else + { + log_error ("unsupported inquiry '%s'\n", line); + rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE); + } + + return rc; +} + +static gpg_error_t +inq_keydata (void *opaque, const char *line) +{ + struct inq_parm_s *parm = opaque; + + if (has_leading_keyword (line, "KEYDATA")) + return assuan_send_data (parm->ctx, parm->keydata, parm->keydatalen); + else + return inq_needpin (opaque, line); +} + +static gpg_error_t +inq_extra (void *opaque, const char *line) +{ + struct inq_parm_s *parm = opaque; + + if (has_leading_keyword (line, "EXTRA")) + return assuan_send_data (parm->ctx, parm->extra, parm->extralen); + else + return inq_keydata (opaque, line); +} + +int +agent_tpm2d_writekey (ctrl_t ctrl, unsigned char **shadow_info, + gcry_sexp_t s_skey) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + size_t n; + unsigned char *kbuf; + membuf_t data; + struct inq_parm_s inqparm; + size_t len; + + rc = start_tpm2d (ctrl); + if (rc) + return rc; + + /* note: returned data is TPM protected so no need for a sensitive context */ + init_membuf(&data, 4096); + + inqparm.ctx = daemon_ctx (ctrl); + inqparm.getpin_cb = agent_ask_new_passphrase; + inqparm.ctrl = ctrl; + inqparm.pin = NULL; + + n = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0); + kbuf = xtrymalloc (n); + gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, kbuf, n); + inqparm.keydata = kbuf; + inqparm.keydatalen = n; + snprintf(line, sizeof(line), "IMPORT"); + + rc = assuan_transact (daemon_ctx (ctrl), line, + put_membuf_cb, &data, + inq_keydata, &inqparm, + NULL, NULL); + xfree (kbuf); + xfree (inqparm.pin); + if (rc) + { + xfree (get_membuf (&data, &len)); + return unlock_tpm2d (ctrl, rc); + } + + *shadow_info = get_membuf (&data, &len); + + return unlock_tpm2d (ctrl, 0); +} + +static gpg_error_t +pin_cb (ctrl_t ctrl, const char *prompt, char **passphrase) +{ + *passphrase = agent_get_cache (ctrl, ctrl->keygrip, CACHE_MODE_USER); + if (*passphrase) + return 0; + return agent_get_passphrase(ctrl, passphrase, + _("Please enter your passphrase, so that the " + "secret key can be unlocked for this session"), + prompt, NULL, 0, + ctrl->keygrip, CACHE_MODE_USER); +} + +int +agent_tpm2d_pksign (ctrl_t ctrl, const unsigned char *digest, + size_t digestlen, const unsigned char *shadow_info, + unsigned char **r_sig, size_t *r_siglen) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + struct inq_parm_s inqparm; + + rc = start_tpm2d (ctrl); + if (rc) + return rc; + + init_membuf (&data, 1024); + + inqparm.ctx = daemon_ctx (ctrl); + inqparm.getpin_cb = pin_cb; + inqparm.ctrl = ctrl; + inqparm.keydata = shadow_info; + inqparm.keydatalen = gcry_sexp_canon_len (shadow_info, 0, NULL, NULL); + inqparm.extra = digest; + inqparm.extralen = digestlen; + inqparm.pin = NULL; + + snprintf(line, sizeof(line), "PKSIGN"); + + rc = assuan_transact (daemon_ctx (ctrl), line, + put_membuf_cb, &data, + inq_extra, &inqparm, + NULL, NULL); + if (!rc) + agent_put_cache (ctrl, ctrl->keygrip, CACHE_MODE_USER, inqparm.pin, 0); + + xfree (inqparm.pin); + + if (rc) + { + size_t len; + xfree (get_membuf (&data, &len)); + return unlock_tpm2d (ctrl, rc); + } + + *r_sig = get_membuf (&data, r_siglen); + + return unlock_tpm2d (ctrl, 0); +} + +int +agent_tpm2d_pkdecrypt (ctrl_t ctrl, const unsigned char *cipher, + size_t cipherlen, const unsigned char *shadow_info, + char **r_buf, size_t *r_len) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + membuf_t data; + struct inq_parm_s inqparm; + + rc = start_tpm2d (ctrl); + if (rc) + return rc; + + init_membuf (&data, 1024); + + inqparm.ctx = daemon_ctx (ctrl); + inqparm.getpin_cb = pin_cb; + inqparm.ctrl = ctrl; + inqparm.keydata = shadow_info; + inqparm.keydatalen = gcry_sexp_canon_len (shadow_info, 0, NULL, NULL); + inqparm.extra = cipher; + inqparm.extralen = cipherlen; + inqparm.pin = NULL; + + snprintf(line, sizeof(line), "PKDECRYPT"); + + rc = assuan_transact (daemon_ctx (ctrl), line, + put_membuf_cb, &data, + inq_extra, &inqparm, + NULL, NULL); + if (!rc) + agent_put_cache (ctrl, ctrl->keygrip, CACHE_MODE_USER, inqparm.pin, 0); + + xfree (inqparm.pin); + + if (rc) + { + size_t len; + xfree (get_membuf (&data, &len)); + return unlock_tpm2d (ctrl, rc); + } + + *r_buf = get_membuf (&data, r_len); + + return unlock_tpm2d (ctrl, 0); +} diff --git a/agent/command.c b/agent/command.c index 689507bbc..3388664e4 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1288,6 +1288,11 @@ do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx, if (err) goto leave; } + else if (strcmp (shadow_info_type, "tpm2-v1") == 0) + { + serialno = xstrdup("TPM-Protected"); + idstr = NULL; + } else { log_error ("unrecognised shadow key type %s\n", shadow_info_type); diff --git a/agent/divert-tpm2.c b/agent/divert-tpm2.c new file mode 100644 index 000000000..22fbfd808 --- /dev/null +++ b/agent/divert-tpm2.c @@ -0,0 +1,144 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "agent.h" +#include "../common/i18n.h" +#include "../common/sexp-parse.h" + +int +divert_tpm2_pksign (ctrl_t ctrl, const char *desc_text, + const unsigned char *digest, size_t digestlen, int algo, + const unsigned char *shadow_info, unsigned char **r_sig, + size_t *r_siglen) +{ + return agent_tpm2d_pksign(ctrl, digest, digestlen, + shadow_info, r_sig, r_siglen); +} + + +static gpg_error_t +agent_write_tpm2_shadow_key (ctrl_t ctrl, const unsigned char *grip, + unsigned char *shadow_info) +{ + gpg_error_t err; + unsigned char *shdkey; + unsigned char *pkbuf; + size_t len; + gcry_sexp_t s_pkey; + + err = agent_public_key_from_file (ctrl, grip, &s_pkey); + len = gcry_sexp_sprint(s_pkey, GCRYSEXP_FMT_CANON, NULL, 0); + pkbuf = xtrymalloc (len); + gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, pkbuf, len); + gcry_sexp_release (s_pkey); + + err = agent_shadow_key_type (pkbuf, shadow_info, "tpm2-v1", &shdkey); + xfree (pkbuf); + if (err) + { + log_error ("shadowing the key failed: %s\n", gpg_strerror (err)); + return err; + } + + len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL); + err = agent_write_private_key (grip, shdkey, len, 1 /*force*/, NULL, NULL); + xfree (shdkey); + if (err) + log_error ("error writing key: %s\n", gpg_strerror (err)); + + return err; +} + +int +divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip, + gcry_sexp_t s_skey) +{ + int ret; + /* shadow_info is always shielded so no special handling required */ + unsigned char *shadow_info; + + ret = agent_tpm2d_writekey(ctrl, &shadow_info, s_skey); + if (!ret) { + ret = agent_write_tpm2_shadow_key (ctrl, grip, shadow_info); + xfree (shadow_info); + } + return ret; +} + +int +divert_tpm2_pkdecrypt (ctrl_t ctrl, const char *desc_text, + const unsigned char *cipher, + const unsigned char *shadow_info, + char **r_buf, size_t *r_len, int *r_padding) +{ + const unsigned char *s; + size_t n; + + *r_padding = -1; + + (void)desc_text; + + s = cipher; + if (*s != '(') + return gpg_error (GPG_ERR_INV_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "enc-val")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (smatch (&s, n, "rsa")) + { + *r_padding = 0; + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (!smatch (&s, n, "a")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + n = snext (&s); + } + else if (smatch (&s, n, "ecdh")) + { + if (*s != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + s++; + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + if (smatch (&s, n, "s")) + { + n = snext (&s); + s += n; + if (*s++ != ')') + return gpg_error (GPG_ERR_INV_SEXP); + if (*s++ != '(') + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + n = snext (&s); + if (!n) + return gpg_error (GPG_ERR_INV_SEXP); + } + if (!smatch (&s, n, "e")) + return gpg_error (GPG_ERR_UNKNOWN_SEXP); + n = snext (&s); + } + else + return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM); + + return agent_tpm2d_pkdecrypt (ctrl, s, n, shadow_info, r_buf, r_len); +} diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index ae7da0634..568fb0464 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -102,6 +102,7 @@ enum cmd_and_opt_values oLCmessages, oXauthority, oScdaemonProgram, + oTpm2daemonProgram, oDefCacheTTL, oDefCacheTTLSSH, oMaxCacheTTL, @@ -199,6 +200,8 @@ static gpgrt_opt_t opts[] = { /* */ N_("do not use the SCdaemon") ), ARGPARSE_s_s (oScdaemonProgram, "scdaemon-program", /* */ N_("|PGM|use PGM as the SCdaemon program") ), + ARGPARSE_s_s (oTpm2daemonProgram, "tpm2daemon-program", + /* */ N_("|PGM|use PGM as the tpm2daemon program") ), ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"), ARGPARSE_s_s (oExtraSocket, "extra-socket", @@ -905,6 +908,7 @@ parse_rereadable_options (gpgrt_argparse_t *pargs, int reread) opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break; break; case oPinentryTimeout: opt.pinentry_timeout = pargs->r.ret_ulong; break; + case oTpm2daemonProgram: opt.daemon_program[DAEMON_TPM2D] = pargs->r.ret_str; break; case oScdaemonProgram: opt.daemon_program[DAEMON_SCD] = pargs->r.ret_str; break; case oDisableScdaemon: opt.disable_daemon[DAEMON_SCD] = 1; break; case oDisableCheckOwnSocket: disable_check_own_socket = 1; break; diff --git a/agent/keyformat.txt b/agent/keyformat.txt index e2ca05c84..e130e28b1 100644 --- a/agent/keyformat.txt +++ b/agent/keyformat.txt @@ -307,8 +307,9 @@ to keys stored on a token: (comment whatever) ) -The currently used protocol is "t1-v1" (token info version 1). The -second list with the information has this layout: +The currently used protocols are "t1-v1" (token info version 1) and +"tpm2-v1" (TPM format key information). The second list with the +information has this layout for "t1-v1": (card_serial_number id_string_of_key fixed_pin_length) @@ -317,6 +318,13 @@ the PIN; a value of 0 indicates that this information is not available. The rationale for this field is that some pinpad equipped readers don't allow passing a variable length PIN. +This is the (info) layout for "tpm2-v1": + +(parent tpm_private_string tpm_public_string) + +Although this precise format is encapsulated inside the tpm2daemon +itself and nothing in gpg ever uses this. + More items may be added to the list. ** OpenPGP Private Key Transfer Format diff --git a/agent/pkdecrypt.c b/agent/pkdecrypt.c index ec23daf83..03e8ab713 100644 --- a/agent/pkdecrypt.c +++ b/agent/pkdecrypt.c @@ -85,8 +85,12 @@ agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, goto leave; } - rc = divert_pkdecrypt (ctrl, desc_text, ctrl->keygrip, ciphertext, - shadow_info, &buf, &len, r_padding); + if (agent_is_tpm2_key (s_skey)) + rc = divert_tpm2_pkdecrypt (ctrl, desc_text, ciphertext, shadow_info, + &buf, &len, r_padding); + else + rc = divert_pkdecrypt (ctrl, desc_text, ctrl->keygrip, ciphertext, + shadow_info, &buf, &len, r_padding); if (rc) { log_error ("smartcard decryption failed: %s\n", gpg_strerror (rc)); diff --git a/agent/pksign.c b/agent/pksign.c index 0640b04ef..55aefa0fe 100644 --- a/agent/pksign.c +++ b/agent/pksign.c @@ -395,11 +395,17 @@ agent_pksign_do (ctrl_t ctrl, const char *cache_nonce, if (desc_text) agent_modify_description (desc_text, NULL, s_pkey, &desc2); - err = divert_pksign (ctrl, desc2? desc2 : desc_text, - ctrl->keygrip, - data, datalen, - ctrl->digest.algo, - shadow_info, &buf, &len); + if (agent_is_tpm2_key (s_skey)) + err = divert_tpm2_pksign (ctrl, desc2? desc2 : desc_text, + data, datalen, + ctrl->digest.algo, + shadow_info, &buf, &len); + else + err = divert_pksign (ctrl, desc2? desc2 : desc_text, + ctrl->keygrip, + data, datalen, + ctrl->digest.algo, + shadow_info, &buf, &len); xfree (desc2); } if (err) -- 2.26.2 From James.Bottomley at HansenPartnership.com Thu Jun 25 20:46:34 2020 From: James.Bottomley at HansenPartnership.com (James Bottomley) Date: Thu, 25 Jun 2020 11:46:34 -0700 Subject: [PATCH v2 3/3] g10: add new command keytotpm to convert a private key to TPM format In-Reply-To: <20200625184634.16985-1-James.Bottomley@HansenPartnership.com> References: <20200625184634.16985-1-James.Bottomley@HansenPartnership.com> Message-ID: <20200625184634.16985-4-James.Bottomley@HansenPartnership.com> The plumbing is done in two parts: the agent is modified to understand a KEYTOTPM assuan command taking the key grip as an argument. This simply obtains the key s expression and calls the existing writeky diversion to the tpm2daemon. The daemon reponds with the TPM conversion of the key and that key is then stored in the keyfile as a shadowed-private-key with "tpm2-v1" type. To effect the conversion, all the user does from gpg --edit-key is select which private key they wish to move (or move the primary if no key is selected) and type keytotpm. The conversion to TPM form is instantaneous and once converted, the actual key cannot be recovered, meaning that if you want your gpg key to move to a new laptop you must keep an unconverted backup copy in a safe location. When you do a list command, all TPM keys show up as card-no: TPM-Protected The key is stored encrypted to the TPM2 storage seed and since each TPM has a unique seed, only the single TPM contained in your laptop can now read the key. This means you cannot simply copy the shadowed key file over to a new laptop, you must copy over the backup copy and then convert it to TPM form on the new laptop. To decomission your laptop, execute a tssclear command which regenerates the storage seed and effectively shreds all keys. Note when you have done this *every* TPM2 shadowed private key becomes unreadable by any TPM and all are effectively destroyed. Signed-off-by: James Bottomley --- agent/command.c | 52 ++++++++++++++++++++++++++++++++++++++++++++++++ g10/call-agent.c | 22 ++++++++++++++++++++ g10/call-agent.h | 3 +++ g10/keyedit.c | 45 ++++++++++++++++++++++++++++++++++++++++- 4 files changed, 121 insertions(+), 1 deletion(-) diff --git a/agent/command.c b/agent/command.c index 3388664e4..cb49793fb 100644 --- a/agent/command.c +++ b/agent/command.c @@ -2912,6 +2912,57 @@ cmd_put_secret (assuan_context_t ctx, char *line) } + +static const char hlp_keytotpm[] = + "KEYTOTPM \n" + "\n"; +static gpg_error_t +cmd_keytotpm (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err = 0; + unsigned char grip[20]; + gcry_sexp_t s_skey; + unsigned char *shadow_info = NULL; + + if (ctrl->restricted) + return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); + + err = parse_keygrip (ctx, line, grip); + if (err) + goto leave; + + if (agent_key_available (grip)) + { + err =gpg_error (GPG_ERR_NO_SECKEY); + goto leave; + } + + err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip, + &shadow_info, CACHE_MODE_IGNORE, NULL, + &s_skey, NULL); + if (err) + { + xfree (shadow_info); + goto leave; + } + if (shadow_info) + { + /* Key is on a TPM or smartcard already. */ + xfree (shadow_info); + gcry_sexp_release (s_skey); + err = gpg_error (GPG_ERR_UNUSABLE_SECKEY); + goto leave; + } + + err = divert_tpm2_writekey (ctrl, grip, s_skey); + gcry_sexp_release (s_skey); + + leave: + return leave_cmd (ctx, err); +} + + static const char hlp_getval[] = "GETVAL \n" @@ -3613,6 +3664,7 @@ register_commands (assuan_context_t ctx) { "RELOADAGENT", cmd_reloadagent,hlp_reloadagent }, { "GETINFO", cmd_getinfo, hlp_getinfo }, { "KEYTOCARD", cmd_keytocard, hlp_keytocard }, + { "KEYTOTPM", cmd_keytotpm, hlp_keytotpm }, { NULL } }; int i, rc; diff --git a/g10/call-agent.c b/g10/call-agent.c index 5c6a4a66d..55fb5b1e9 100644 --- a/g10/call-agent.c +++ b/g10/call-agent.c @@ -1040,6 +1040,28 @@ agent_scd_apdu (const char *hexapdu, unsigned int *r_sw) return err; } +int +agent_keytotpm (ctrl_t ctrl, const char *hexgrip) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + struct default_inq_parm_s parm; + + snprintf(line, DIM(line), "KEYTOTPM %s\n", hexgrip); + + rc = start_agent (ctrl, 0); + if (rc) + return rc; + parm.ctx = agent_ctx; + parm.ctrl = ctrl; + + rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm, + NULL, NULL); + if (rc) + log_log (GPGRT_LOGLVL_ERROR, _("error from TPM: %s\n"), gpg_strerror (rc)); + return rc; +} + /* Used by: * card_store_subkey diff --git a/g10/call-agent.h b/g10/call-agent.h index be5c777d4..7bf0b7823 100644 --- a/g10/call-agent.h +++ b/g10/call-agent.h @@ -125,6 +125,9 @@ gpg_error_t agent_scd_getattr_one (const char *name, char **r_value); /* Update INFO with the attribute NAME. */ int agent_scd_getattr (const char *name, struct agent_card_info_s *info); +/* send the KEYTOTPM command */ +int agent_keytotpm (ctrl_t ctrl, const char *hexgrip); + /* Send the KEYTOCARD command. */ int agent_keytocard (const char *hexgrip, int keyno, int force, const char *serialno, const char *timestamp); diff --git a/g10/keyedit.c b/g10/keyedit.c index ac9f4688c..7cfc9ef38 100644 --- a/g10/keyedit.c +++ b/g10/keyedit.c @@ -1245,7 +1245,7 @@ enum cmdids #endif /*!NO_TRUST_MODELS*/ cmdSHOWPREF, cmdSETPREF, cmdPREFKS, cmdNOTATION, cmdINVCMD, cmdSHOWPHOTO, cmdUPDTRUST, - cmdCHKTRUST, cmdADDCARDKEY, cmdKEYTOCARD, cmdBKUPTOCARD, + cmdCHKTRUST, cmdADDCARDKEY, cmdKEYTOCARD, cmdKEYTOTPM, cmdBKUPTOCARD, cmdCLEAN, cmdMINIMIZE, cmdGRIP, cmdNOP }; @@ -1296,6 +1296,8 @@ static struct N_("add a key to a smartcard")}, { "keytocard", cmdKEYTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, N_("move a key to a smartcard")}, + { "keytotpm", cmdKEYTOTPM, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, + N_("convert a key to TPM form using the local TPM")}, { "bkuptocard", cmdBKUPTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, N_("move a backup key to a smartcard")}, #endif /*ENABLE_CARD_SUPPORT */ @@ -1794,6 +1796,47 @@ keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr, } break; + case cmdKEYTOTPM: + /* FIXME need to store the key and not commit until later */ + { + KBNODE node = NULL; + switch (count_selected_keys (keyblock)) + { + case 0: + if (cpr_get_answer_is_yes + ("keyedit.keytocard.use_primary", + /* TRANSLATORS: Please take care: This is about + moving the key and not about removing it. */ + _("Really move the primary key? (y/N) "))) + node = keyblock; + break; + case 1: + for (node = keyblock; node; node = node->next) + { + if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY + && node->flag & NODFLG_SELKEY) + break; + } + break; + default: + tty_printf (_("You must select exactly one key.\n")); + break; + } + if (node) + { + PKT_public_key *xxpk = node->pkt->pkt.public_key; + char *hexgrip; + + hexkeygrip_from_pk (xxpk, &hexgrip); + if (!agent_keytotpm (ctrl, hexgrip)) + { + redisplay = 1; + } + xfree (hexgrip); + } + } + break; + case cmdKEYTOCARD: { KBNODE node = NULL; -- 2.26.2