diff options
author | NIIBE Yutaka <gniibe@fsij.org> | 2022-09-02 07:52:17 +0200 |
---|---|---|
committer | NIIBE Yutaka <gniibe@fsij.org> | 2022-09-02 07:52:17 +0200 |
commit | d49788ef9f82913df4031fc3ea9ee6d98132bf16 (patch) | |
tree | a7558b6be11081573fb298b5bf44202b5314119a | |
parent | common: Make nvc_lookup more robust. (diff) | |
download | gnupg2-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.am | 8 | ||||
-rw-r--r-- | tools/gpg-auth.c | 917 |
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; +} |