summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorNIIBE Yutaka <gniibe@fsij.org>2022-09-02 07:52:17 +0200
committerNIIBE Yutaka <gniibe@fsij.org>2022-09-02 07:52:17 +0200
commitd49788ef9f82913df4031fc3ea9ee6d98132bf16 (patch)
treea7558b6be11081573fb298b5bf44202b5314119a
parentcommon: Make nvc_lookup more robust. (diff)
downloadgnupg2-d49788ef9f82913df4031fc3ea9ee6d98132bf16.tar.xz
gnupg2-d49788ef9f82913df4031fc3ea9ee6d98132bf16.zip
tools:gpg-auth: New tool for authentication.
* tools/Makefile.am (bin_PROGRAMS): Add gpg-auth. (gpg_auth_SOURCES, gpg_auth_LDADD): * tools/gpg-auth.c: New. -- GnuPG-bug-id: 5862 Signed-off-by: NIIBE Yutaka <gniibe@fsij.org>
-rw-r--r--tools/Makefile.am8
-rw-r--r--tools/gpg-auth.c917
2 files changed, 924 insertions, 1 deletions
diff --git a/tools/Makefile.am b/tools/Makefile.am
index a6979ebf1..61837a63f 100644
--- a/tools/Makefile.am
+++ b/tools/Makefile.am
@@ -59,7 +59,7 @@ endif
bin_PROGRAMS = gpgconf gpg-connect-agent gpg-card gpg-wks-client
if !HAVE_W32_SYSTEM
-bin_PROGRAMS += watchgnupg gpgparsemail ${gpg_wks_server} gpgsplit
+bin_PROGRAMS += watchgnupg gpgparsemail ${gpg_wks_server} gpgsplit gpg-auth
else
bin_PROGRAMS += gpgconf-w32
endif
@@ -189,6 +189,12 @@ gpg_pair_tool_LDADD = $(libcommon) \
$(LIBGCRYPT_LIBS) $(GPG_ERROR_LIBS) \
$(LIBINTL) $(LIBICONV) $(W32SOCKLIBS)
+gpg_auth_SOURCES = gpg-auth.c
+gpg_auth_LDADD = $(common_libs) \
+ $(LIBASSUAN_LIBS) $(LIBGCRYPT_LIBS) \
+ $(GPG_ERROR_LIBS) \
+ $(LIBINTL) $(NETLIBS) $(LIBICONV)
+
# Instead of a symlink we install a simple wrapper script for the new
# gpg-wks-client location. We assume bin is a sibling of libexec.
install-exec-local:
diff --git a/tools/gpg-auth.c b/tools/gpg-auth.c
new file mode 100644
index 000000000..dd4dffaae
--- /dev/null
+++ b/tools/gpg-auth.c
@@ -0,0 +1,917 @@
+/* gpg-auth.c - Authenticate using GnuPG
+ * Copyright (C) 2022 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * This file 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.
+ *
+ * This file 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 Lesser 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://gnu.org/licenses/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#define INCLUDED_BY_MAIN_MODULE 1
+
+#include "../common/util.h"
+#include "../common/status.h"
+#include "../common/i18n.h"
+#include "../common/init.h"
+#include "../common/sysutils.h"
+#include "../common/asshelp.h"
+#include "../common/session-env.h"
+#include "../common/membuf.h"
+#include "../common/exechelp.h"
+
+
+/* We keep all global options in the structure OPT. */
+struct
+{
+ int interactive;
+ int verbose;
+ unsigned int debug;
+ int quiet;
+ int with_colons;
+ const char *agent_program;
+ int autostart;
+
+ /* Options passed to the gpg-agent: */
+ char *lc_ctype;
+ char *lc_messages;
+} opt;
+
+/* Debug values and macros. */
+#define DBG_IPC_VALUE 1024 /* Debug assuan communication. */
+#define DBG_EXTPROG_VALUE 16384 /* Debug external program calls */
+
+#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
+#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE)
+
+
+/* Constants to identify the commands and options. */
+enum opt_values
+ {
+ aNull = 0,
+
+ oQuiet = 'q',
+ oVerbose = 'v',
+
+ oDebug = 500,
+
+ oGpgProgram,
+ oGpgsmProgram,
+ oAgentProgram,
+ oStatusFD,
+ oWithColons,
+ oNoAutostart,
+
+ oLCctype,
+ oLCmessages,
+
+ oChUid,
+
+ oDummy
+ };
+
+
+/* The list of commands and options. */
+static gpgrt_opt_t opts[] = {
+ ARGPARSE_group (301, ("@\nOptions:\n ")),
+
+ ARGPARSE_s_n (oVerbose, "verbose", ("verbose")),
+ ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")),
+ ARGPARSE_s_s (oDebug, "debug", "@"),
+ ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
+ ARGPARSE_s_n (oWithColons, "with-colons", "@"),
+ ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
+ ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
+ ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
+ ARGPARSE_s_s (oLCmessages, "lc-messages","@"),
+
+ ARGPARSE_end ()
+};
+
+/* The list of supported debug flags. */
+static struct debug_flags_s debug_flags [] =
+ {
+ { DBG_IPC_VALUE , "ipc" },
+ { DBG_EXTPROG_VALUE, "extprog" },
+ { 0, NULL }
+ };
+
+
+/* Print usage information and provide strings for help. */
+static const char *
+my_strusage( int level )
+{
+ const char *p;
+
+ switch (level)
+ {
+ case 9: p = "GPL-3.0-or-later"; break;
+ case 11: p = "gpg-auth"; break;
+ case 12: p = "@GNUPG@"; break;
+ case 13: p = VERSION; break;
+ case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
+ case 17: p = PRINTABLE_OS_NAME; break;
+ case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break;
+
+ case 1:
+ case 40:
+ p = ("Usage: gpg-auth"
+ " [options] USERID|FINGERPRINT (-h for help)");
+ break;
+ case 41:
+ p = ("Syntax: gpg-auth"
+ " [options] USERID|FINGERPRINT\n\n"
+ "Tool to authenticate a user using a smartcard.\n"
+ "Use command \"help\" to list all commands.");
+ break;
+
+ default: p = NULL; break;
+ }
+ return p;
+}
+
+/* Command line parsing. */
+static void
+parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts)
+{
+ while (gpgrt_argparse (NULL, pargs, popts))
+ {
+ switch (pargs->r_opt)
+ {
+ case oQuiet: opt.quiet = 1; break;
+ case oVerbose: opt.verbose++; break;
+ case oDebug:
+ if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags))
+ {
+ pargs->r_opt = ARGPARSE_INVALID_ARG;
+ pargs->err = ARGPARSE_PRINT_ERROR;
+ }
+ break;
+
+ case oAgentProgram: opt.agent_program = pargs->r.ret_str; break;
+
+ case oStatusFD:
+ gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1));
+ break;
+
+ case oWithColons: opt.with_colons = 1; break;
+ case oNoAutostart: opt.autostart = 0; break;
+
+ case oLCctype: opt.lc_ctype = pargs->r.ret_str; break;
+ case oLCmessages: opt.lc_messages = pargs->r.ret_str; break;
+
+ default: pargs->err = ARGPARSE_PRINT_ERROR; break;
+ }
+ }
+}
+
+
+
+struct ga_key_list {
+ struct ga_key_list *next;
+ char keygrip[41]; /* Keygrip to identify a key. */
+ size_t pubkey_len;
+ char *pubkey; /* Public key in SSH format. */
+};
+
+/* Local prototypes. */
+static gpg_error_t scd_passwd_reset (assuan_context_t ctx, const char *keygrip);
+static gpg_error_t ga_scd_connect (assuan_context_t *r_scd_ctx, int use_agent);
+static gpg_error_t ga_scd_get_auth_keys (assuan_context_t ctx,
+ struct ga_key_list **r_key_list);
+static gpg_error_t ga_filter_by_authorized_keys (struct ga_key_list **r_key_list);
+static void ga_release_auth_keys (struct ga_key_list *key_list);
+static gpg_error_t scd_pkauth (assuan_context_t ctx, const char *keygrip);
+static gpg_error_t authenticate (assuan_context_t ctx, struct ga_key_list *key_list);
+static int getpin (const char *info, char *buf, size_t maxbuf);
+
+/* gpg-auth main. */
+int
+main (int argc, char **argv)
+{
+ gpg_error_t err;
+ gpgrt_argparse_t pargs;
+ assuan_context_t scd_ctx = NULL;
+ struct ga_key_list *key_list = NULL;
+
+ gnupg_reopen_std ("gpg-auth");
+ gpgrt_set_strusage (my_strusage);
+ log_set_prefix ("gpg-auth", GPGRT_LOG_WITH_PREFIX);
+
+ /* Make sure that our subsystems are ready. */
+ i18n_init();
+ init_common_subsystems (&argc, &argv);
+
+ assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
+ setup_libassuan_logging (&opt.debug, NULL);
+
+ /* Setup default options. */
+ opt.autostart = 1;
+
+ /* Parse the command line. */
+ pargs.argc = &argc;
+ pargs.argv = &argv;
+ pargs.flags = ARGPARSE_FLAG_KEEP;
+ parse_arguments (&pargs, opts);
+ gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
+
+ if (log_get_errorcount (0))
+ exit (2);
+
+ if (argc != 1)
+ gpgrt_usage (1); /* Never returns. */
+
+ err = ga_scd_connect (&scd_ctx, 0);
+
+ if (!err)
+ err = ga_scd_get_auth_keys (scd_ctx, &key_list);
+
+ if (!err)
+ err = ga_filter_by_authorized_keys (&key_list);
+
+ if (!err)
+ err = authenticate (scd_ctx, key_list);
+
+ ga_release_auth_keys (key_list);
+
+ if (scd_ctx)
+ assuan_release (scd_ctx);
+
+ if (err)
+ exit (1);
+
+ return 0;
+}
+
+static gpg_error_t
+authenticate (assuan_context_t ctx, struct ga_key_list *key_list)
+{
+ gpg_error_t err;
+
+ while (key_list)
+ {
+ err = scd_passwd_reset (ctx, key_list->keygrip);
+ if (err)
+ return err;
+
+ err = scd_pkauth (ctx, key_list->keygrip);
+ if (!err)
+ /* Success! */
+ return 0;
+
+ key_list = key_list->next;
+ }
+
+ return 0;
+}
+
+static gpg_error_t
+get_serialno_cb (void *opaque, const char *line)
+{
+ char **serialno = opaque;
+ const char *keyword = line;
+ const char *s;
+ int keywordlen, n;
+
+ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+ ;
+ while (spacep (line))
+ line++;
+
+ if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
+ {
+ if (*serialno)
+ return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */
+ for (n=0,s=line; hexdigitp (s); s++, n++)
+ ;
+ if (!n || (n&1)|| !(spacep (s) || !*s) )
+ return gpg_error (GPG_ERR_ASS_PARAMETER);
+ *serialno = xtrymalloc (n+1);
+ if (!*serialno)
+ return gpg_error_from_syserror ();
+ memcpy (*serialno, line, n);
+ (*serialno)[n] = 0;
+ }
+
+ return 0;
+}
+
+/* Helper function, which is used by scd_connect.
+
+ Try to retrieve the SCDaemon's socket name from the gpg-agent
+ context CTX. On success, *SOCKET_NAME is filled with a copy of the
+ socket name. Return proper error code or zero on success. */
+static gpg_error_t
+agent_scd_getinfo_socket_name (assuan_context_t ctx, char **socket_name)
+{
+ membuf_t data;
+ gpg_error_t err = 0;
+ unsigned char *databuf;
+ size_t datalen;
+
+ init_membuf (&data, 256);
+ *socket_name = NULL;
+
+ err = assuan_transact (ctx, "SCD GETINFO socket_name", put_membuf_cb, &data,
+ NULL, NULL, NULL, NULL);
+ databuf = get_membuf (&data, &datalen);
+ if (!err)
+ {
+ if (databuf && datalen)
+ {
+ char *res = xtrymalloc (datalen + 1);
+ if (!res)
+ err = gpg_error_from_syserror ();
+ else
+ {
+ memcpy (res, databuf, datalen);
+ res[datalen] = 0;
+ *socket_name = res;
+ }
+ }
+ }
+
+ xfree (databuf);
+
+ return err;
+}
+
+/* Callback parameter for learn card */
+struct learn_parm_s
+{
+ void (*kpinfo_cb)(void*, const char *);
+ void *kpinfo_cb_arg;
+ void (*certinfo_cb)(void*, const char *);
+ void *certinfo_cb_arg;
+ void (*sinfo_cb)(void*, const char *, size_t, const char *);
+ void *sinfo_cb_arg;
+};
+
+/* Connect to the agent and send the standard options. */
+static gpg_error_t
+start_agent (assuan_context_t *ctx_p)
+{
+ gpg_error_t err;
+ session_env_t session_env;
+
+ session_env = session_env_new ();
+ if (!session_env)
+ log_fatal ("error allocating session environment block: %s\n",
+ strerror (errno));
+
+ err = start_new_gpg_agent (ctx_p,
+ GPG_ERR_SOURCE_DEFAULT,
+ opt.agent_program,
+ NULL, NULL,
+ session_env,
+ opt.autostart,
+ !opt.quiet, 0,
+ NULL, NULL);
+
+ session_env_release (session_env);
+ return err;
+}
+
+static gpg_error_t
+scd_serialno (assuan_context_t ctx)
+{
+ char *serialno = NULL;
+ gpg_error_t err;
+
+ err = assuan_transact (ctx, "SERIALNO", NULL, NULL, NULL, NULL,
+ get_serialno_cb, &serialno);
+ xfree (serialno);
+ return err;
+}
+
+
+static gpg_error_t
+scd_passwd_reset (assuan_context_t ctx, const char *keygrip)
+{
+ char line[ASSUAN_LINELENGTH];
+ gpg_error_t err;
+
+ snprintf (line, DIM(line), "PASSWD --clear OPENPGP.2 %s", keygrip);
+ err = assuan_transact (ctx, line, NULL, NULL, NULL, NULL,
+ NULL, NULL);
+ return err;
+}
+
+
+/* Connect to scdaemon by pipe or socket. Execute initial "SEREIALNO"
+ command to enable all connected token under scdaemon control. */
+static gpg_error_t
+ga_scd_connect (assuan_context_t *r_scd_ctx, int use_agent)
+{
+ assuan_context_t assuan_ctx;
+ gpg_error_t err;
+
+ err = assuan_new (&assuan_ctx);
+ if (err)
+ return err;
+
+ if (use_agent)
+ /* Use scdaemon under gpg-agent. */
+ {
+ char *scd_socket_name = NULL;
+ assuan_context_t ctx;
+
+ err = start_agent (&ctx);
+ if (err)
+ return err;
+
+ /* Note that if gpg-agent is there but no scdaemon yet,
+ * gpg-agent automatically invokes scdaemon by this query
+ * itself.
+ */
+ err = agent_scd_getinfo_socket_name (ctx, &scd_socket_name);
+ assuan_release (ctx);
+
+ if (!err)
+ err = assuan_socket_connect (assuan_ctx, scd_socket_name, 0, 0);
+
+ if (!err)
+ log_debug ("got scdaemon socket name from gpg-agent, "
+ "connected to socket '%s'", scd_socket_name);
+
+ xfree (scd_socket_name);
+ }
+ else
+ {
+ const char *scd_path;
+ const char *pgmname;
+ const char *argv[3];
+ int no_close_list[2];
+
+ scd_path = gnupg_module_name (GNUPG_MODULE_NAME_SCDAEMON);
+ if (!(pgmname = strrchr (scd_path, '/')))
+ pgmname = scd_path;
+ else
+ pgmname++;
+
+ /* Fill argument vector for scdaemon. */
+ argv[0] = pgmname;
+ argv[1] = "--server";
+ argv[2] = NULL;
+
+ no_close_list[0] = assuan_fd_from_posix_fd (fileno (stderr));
+ no_close_list[1] = ASSUAN_INVALID_FD;
+
+ /* Connect to the scdaemon */
+ err = assuan_pipe_connect (assuan_ctx, scd_path, argv, no_close_list,
+ NULL, NULL, 0);
+ if (err)
+ {
+ log_error ("could not spawn scdaemon: %s", gpg_strerror (err));
+ return err;
+ }
+
+ log_debug ("spawned a new scdaemon (path: '%s')", scd_path);
+ }
+
+ if (err)
+ assuan_release (assuan_ctx);
+ else
+ {
+ scd_serialno (assuan_ctx);
+ *r_scd_ctx = assuan_ctx;
+ }
+
+ return err;
+}
+
+
+/* Handle the NEEDPIN inquiry. */
+static gpg_error_t
+inq_needpin (void *opaque, const char *line)
+{
+ assuan_context_t ctx = opaque;
+ const char *s;
+ char *pin;
+ size_t pinlen;
+ int rc;
+
+ rc = 0;
+
+ if ((s = has_leading_keyword (line, "NEEDPIN")))
+ {
+ line = s;
+ pinlen = 90;
+ pin = gcry_malloc_secure (pinlen);
+ if (!pin)
+ return out_of_core ();
+
+ rc = getpin (line, pin, pinlen);
+ if (!rc)
+ {
+ assuan_begin_confidential (ctx);
+ rc = assuan_send_data (ctx, pin, pinlen);
+ assuan_end_confidential (ctx);
+ }
+ wipememory (pin, pinlen);
+ xfree (pin);
+ }
+ else if ((s = has_leading_keyword (line, "POPUPPINPADPROMPT")))
+ {
+ /* i 18 */;
+ /* Please use PINPAD! */;
+ }
+ else if ((s = has_leading_keyword (line, "DISMISSPINPADPROMPT")))
+ {
+ ;
+ }
+ else
+ {
+ log_error ("unsupported inquiry '%s'\n", line);
+ rc = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
+ }
+
+ return gpg_error (rc);
+}
+
+struct card_keyinfo_parm_s {
+ int error;
+ struct ga_key_list *list;
+};
+
+/* Callback function for scd_keyinfo_list. */
+static gpg_error_t
+card_keyinfo_cb (void *opaque, const char *line)
+{
+ gpg_error_t err = 0;
+ struct card_keyinfo_parm_s *parm = opaque;
+ const char *keyword = line;
+ int keywordlen;
+ struct ga_key_list *keyinfo = NULL;
+
+ for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+ ;
+ while (spacep (line))
+ line++;
+
+ if (keywordlen == 7 && !memcmp (keyword, "KEYINFO", keywordlen))
+ {
+ const char *s;
+ int n;
+ struct ga_key_list **l_p = &parm->list;
+
+ /* It's going to append the information at the end. */
+ while ((*l_p))
+ l_p = &(*l_p)->next;
+
+ keyinfo = xtrycalloc (1, sizeof *keyinfo);
+ if (!keyinfo)
+ goto alloc_error;
+
+ for (n=0,s=line; hexdigitp (s); s++, n++)
+ ;
+
+ if (n != 40)
+ goto parm_error;
+
+ memcpy (keyinfo->keygrip, line, 40);
+ keyinfo->keygrip[40] = 0;
+
+ line = s;
+
+ if (!*line)
+ goto parm_error;
+
+ while (spacep (line))
+ line++;
+
+ if (*line++ != 'T')
+ goto parm_error;
+
+ if (!*line)
+ goto parm_error;
+
+ while (spacep (line))
+ line++;
+
+ for (n=0,s=line; hexdigitp (s); s++, n++)
+ ;
+
+ if (!n)
+ goto skip;
+
+ skip:
+ *l_p = keyinfo;
+ }
+
+ return err;
+
+ alloc_error:
+ xfree (keyinfo);
+ if (!parm->error)
+ parm->error = gpg_error_from_syserror ();
+ return 0;
+
+ parm_error:
+ xfree (keyinfo);
+ if (!parm->error)
+ parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
+ return 0;
+}
+
+
+/* Call the scdaemon to retrieve list of available keys on cards. On
+ success, the allocated structure is stored at R_KEY_LIST. On
+ error, an error code is returned and NULL is stored at R_KEY_LIST. */
+static gpg_error_t
+scd_keyinfo_list (assuan_context_t ctx, struct ga_key_list **r_key_list)
+{
+ int err;
+ struct card_keyinfo_parm_s parm;
+
+ memset (&parm, 0, sizeof parm);
+
+ err = assuan_transact (ctx, "KEYINFO --list=auth", NULL, NULL, NULL, NULL,
+ card_keyinfo_cb, &parm);
+ if (!err && parm.error)
+ err = parm.error;
+
+ if (!err)
+ *r_key_list = parm.list;
+ else
+ ga_release_auth_keys (parm.list);
+
+ return err;
+}
+
+static gpg_error_t
+scd_get_pubkey (assuan_context_t ctx, struct ga_key_list *key)
+{
+ char line[ASSUAN_LINELENGTH];
+ membuf_t data;
+ unsigned char *databuf;
+ size_t datalen;
+ gpg_error_t err = 0;
+
+ init_membuf (&data, 256);
+
+ snprintf (line, DIM(line), "READKEY --format=ssh %s", key->keygrip);
+ err = assuan_transact (ctx, line, put_membuf_cb, &data,
+ NULL, NULL, NULL, NULL);
+ databuf = get_membuf (&data, &datalen);
+
+ if (!err)
+ {
+ key->pubkey_len = datalen;
+ key->pubkey = databuf;
+ }
+ else
+ xfree (databuf);
+
+ return err;
+}
+
+
+static gpg_error_t
+ga_scd_get_auth_keys (assuan_context_t ctx, struct ga_key_list **r_key_list)
+{
+ gpg_error_t err;
+ struct ga_key_list *kl, *key_list = NULL;
+
+ /* Get list of auth keys with their keygrips. */
+ err = scd_keyinfo_list (ctx, &key_list);
+
+ /* And retrieve public key for each key. */
+ kl = key_list;
+ while (kl)
+ {
+ err = scd_get_pubkey (ctx, kl);
+ if (err)
+ break;
+ kl = kl->next;
+ }
+
+ if (err)
+ ga_release_auth_keys (key_list);
+ else
+ *r_key_list = key_list;
+
+ return err;
+}
+
+struct ssh_key_list {
+ struct ssh_key_list *next;
+ size_t pubkey_len;
+ char *pubkey; /* Public key in SSH format. */
+};
+
+static void
+release_ssh_key_list (struct ssh_key_list *key_list)
+{
+ struct ssh_key_list *key;
+
+ while (key_list)
+ {
+ key = key_list;
+ key_list = key_list->next;
+ xfree (key->pubkey);
+ xfree (key);
+ }
+}
+
+static gpg_error_t
+ssh_authorized_keys (struct ssh_key_list **r_ssh_key_list)
+{
+ gpg_error_t err = 0;
+ char *fname = NULL;
+ estream_t fp = NULL;
+ char *line = NULL;
+ size_t length_of_line = 0;
+ size_t maxlen;
+ ssize_t len;
+ const char *fields[3];
+ struct ssh_key_list *ssh_key_list = NULL;
+ struct ssh_key_list *ssh_key_prev = NULL;
+ struct ssh_key_list *ssh_key = NULL;
+
+ fname = make_absfilename_try ("~", ".ssh", "authorized_keys", NULL);
+ if (fname == NULL)
+ return gpg_error (GPG_ERR_INV_NAME);
+
+ fp = es_fopen (fname, "r");
+ if (!fp)
+ {
+ err = gpg_error_from_syserror ();
+ xfree (fname);
+ return err;
+ }
+ xfree (fname);
+
+ maxlen = 2048; /* Set limit. */
+ while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0)
+ {
+ if (!maxlen)
+ {
+ err = gpg_error (GPG_ERR_LINE_TOO_LONG);
+ log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
+ goto leave;
+ }
+
+ /* Strip newline and carriage return, if present. */
+ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
+ line[--len] = '\0';
+
+ if (split_fields (line, fields, DIM (fields)) < 2)
+ continue; /* Skip empty lines or line with only a field. */
+ if (*fields[0] == '#')
+ continue; /* Skip comments. */
+
+ ssh_key = xtrycalloc (1, sizeof *ssh_key);
+ if (!ssh_key)
+ {
+ err = gpg_error_from_syserror ();
+ release_ssh_key_list (ssh_key_list);
+ goto leave;
+ }
+
+ ssh_key->pubkey = strdup (fields[1]);
+ ssh_key->pubkey_len = strlen (ssh_key->pubkey);
+ if (ssh_key_list)
+ ssh_key_prev->next = ssh_key;
+ else
+ ssh_key_list = ssh_key;
+
+ ssh_key_prev = ssh_key;
+ }
+
+ *r_ssh_key_list = ssh_key_list;
+
+ leave:
+ xfree (line);
+ es_fclose (fp);
+ return err;
+}
+
+static gpg_error_t
+ga_filter_by_authorized_keys (struct ga_key_list **r_key_list)
+{
+ gpg_error_t err;
+ struct ga_key_list *cur = *r_key_list;
+ struct ga_key_list *key_list = NULL;
+ struct ga_key_list *prev = NULL;
+ struct ssh_key_list *ssh_key_list = NULL;
+
+ err = ssh_authorized_keys (&ssh_key_list);
+ if (err)
+ return err;
+
+ if (ssh_key_list == NULL)
+ return gpg_error (GPG_ERR_NOT_FOUND);
+
+ while (cur)
+ {
+ struct ssh_key_list *skl = ssh_key_list;
+
+ while (skl)
+ if (!strncmp (cur->pubkey, skl->pubkey, cur->pubkey_len))
+ break;
+ else
+ skl = skl->next;
+
+ /* valid? */
+ if (skl)
+ {
+ if (key_list)
+ prev->next = cur;
+ else
+ key_list = cur;
+ prev = cur;
+ cur = cur->next;
+ }
+ else
+ {
+ struct ga_key_list *k = cur;
+
+ cur = cur->next;
+ xfree (k->pubkey);
+ xfree (k);
+ }
+ }
+
+ release_ssh_key_list (ssh_key_list);
+ *r_key_list = key_list;
+ return 0;
+}
+
+static void
+ga_release_auth_keys (struct ga_key_list *key_list)
+{
+ struct ga_key_list *key;
+
+ while (key_list)
+ {
+ key = key_list;
+ key_list = key_list->next;
+ xfree (key->pubkey);
+ xfree (key);
+ }
+}
+
+static int
+getpin (const char *info, char *buf, size_t maxbuf)
+{
+ int rc = 0;
+ char line[ASSUAN_LINELENGTH];
+ const char *fields[2];
+
+ (void)info;
+
+ fputs ("P 18\n", stdout);
+ fputs ("Please input PIN: \n", stdout);
+
+ fgets (line, ASSUAN_LINELENGTH, stdin);
+ if (split_fields (line, fields, DIM (fields)) < DIM (fields))
+ rc = GPG_ERR_PROTOCOL_VIOLATION;
+ else if (strcmp (fields[0], "p") != 0)
+ rc = GPG_ERR_CANCELED;
+ if (!fgets (line, ASSUAN_LINELENGTH, stdin))
+ rc = GPG_ERR_PROTOCOL_VIOLATION;
+ if (!rc)
+ {
+ size_t len = strlen (line);
+
+ /* Strip newline and carriage return, if present. */
+ while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
+ line[--len] = '\0';
+
+ if (len > maxbuf)
+ rc = GPG_ERR_BUFFER_TOO_SHORT;
+ else
+ memcpy (buf, line, len+1);
+ }
+
+ return rc;
+}
+
+
+static gpg_error_t
+scd_pkauth (assuan_context_t ctx, const char *keygrip)
+{
+ char line[ASSUAN_LINELENGTH];
+ gpg_error_t err;
+
+ snprintf (line, DIM(line), "PKAUTH --challenge-response %s", keygrip);
+ err = assuan_transact (ctx, line, NULL, NULL, inq_needpin, ctx,
+ NULL, NULL);
+ return err;
+}