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