From bd85f9232ad639d4acba443272147c4fc01b1b65 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Thu, 16 Jan 2020 21:28:45 +0100 Subject: [PATCH] card: Allow switching of cards and applications. * tools/card-call-scd.c (struct card_cardlist_parm_s): Add field with_apps. (card_cardlist_cb): Handle the new with_apps flag. (scd_switchcard): New. (scd_switchapp): New. (scd_applist): New. (scd_serialno): Pass --all also in --demand mode. * tools/gpg-card.c (cmd_list): Simplify switching of cards. Add switching of alls. Print a list of apps per card. -- Note that the output format of "list --card" slightly changes: The current card is indicated with an asterisk. That should not harm any robust parsers which might already be in use. It is anyway a development version. Signed-off-by: Werner Koch --- tools/card-call-scd.c | 95 ++++++++++++++++++++++++++++++-- tools/gpg-card.c | 124 ++++++++++++++++++++++++++++++++++-------- tools/gpg-card.h | 6 ++ 3 files changed, 197 insertions(+), 28 deletions(-) diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index e4fa6abd3..80058efa9 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -1,5 +1,5 @@ /* card-call-scd.c - IPC calls to scdaemon. - * Copyright (C) 2019 g10 Code GmbH + * Copyright (C) 2019, 2020 g10 Code GmbH * Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc. * Copyright (C) 2013-2015 Werner Koch * @@ -88,6 +88,7 @@ struct genkey_parm_s struct card_cardlist_parm_s { gpg_error_t error; + int with_apps; strlist_t list; }; @@ -558,6 +559,45 @@ get_serialno_cb (void *opaque, const char *line) } +/* Make the card with SERIALNO the current one. */ +gpg_error_t +scd_switchcard (const char *serialno) +{ + int err; + char line[ASSUAN_LINELENGTH]; + + err = start_agent (START_AGENT_SUPPRESS_ERRORS); + if (err) + return err; + + snprintf (line, DIM(line), "SCD SWITCHCARD -- %s", serialno); + return assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, + NULL, NULL); +} + + +/* Make the app APPNAME the one on the card. */ +gpg_error_t +scd_switchapp (const char *appname) +{ + int err; + char line[ASSUAN_LINELENGTH]; + + if (appname && !*appname) + appname = NULL; + + err = start_agent (START_AGENT_SUPPRESS_ERRORS); + if (err) + return err; + + snprintf (line, DIM(line), "SCD SWITCHAPP --%s%s", + appname? " ":"", appname? appname:""); + return assuan_transact (agent_ctx, line, + NULL, NULL, NULL, NULL, + NULL, NULL); +} + /* For historical reasons OpenPGP cards simply use the numbers 1 to 3 * for the . Other cards and future versions of @@ -1286,7 +1326,7 @@ scd_serialno (char **r_serialno, const char *demand) if (!demand) strcpy (line, "SCD SERIALNO --all"); else - snprintf (line, DIM(line), "SCD SERIALNO --demand=%s", demand); + snprintf (line, DIM(line), "SCD SERIALNO --demand=%s --all", demand); err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, @@ -1407,10 +1447,24 @@ card_cardlist_cb (void *opaque, const char *line) for (n=0,s=line; hexdigitp (s); s++, n++) ; - if (!n || (n&1) || *s) + if (!n || (n&1)) parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); + if (parm->with_apps) + { + /* Format of the stored string is the S/N, a space, and a + * space separated list of appnames. */ + if (*s != ' ' || spacep (s+1) || !s[1]) + parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); + else /* We assume the rest of the line is well formatted. */ + add_to_strlist (&parm->list, line); + } else - add_to_strlist (&parm->list, line); + { + if (*s) + parm->error = gpg_error (GPG_ERR_ASS_PARAMETER); + else + add_to_strlist (&parm->list, line); + } } return 0; @@ -1446,6 +1500,39 @@ scd_cardlist (strlist_t *result) } +/* Return the serial numbers and appnames of the current card or, with + * ALL given has true, of all cards currently inserted. */ +gpg_error_t +scd_applist (strlist_t *result, int all) +{ + gpg_error_t err; + struct card_cardlist_parm_s parm; + + memset (&parm, 0, sizeof parm); + *result = NULL; + + err = start_agent (START_AGENT_SUPPRESS_ERRORS); + if (err) + return err; + + parm.with_apps = 1; + err = assuan_transact (agent_ctx, + all ? "SCD GETINFO all_active_apps" + /**/: "SCD GETINFO active_apps", + NULL, NULL, NULL, NULL, + card_cardlist_cb, &parm); + if (!err && parm.error) + err = parm.error; + + if (!err) + *result = parm.list; + else + free_strlist (parm.list); + + return err; +} + + /* Change the PIN of an OpenPGP card or reset the retry counter. * CHVNO 1: Change the PIN diff --git a/tools/gpg-card.c b/tools/gpg-card.c index e9dc8ebfd..52f37591f 100644 --- a/tools/gpg-card.c +++ b/tools/gpg-card.c @@ -1,5 +1,5 @@ /* gpg-card.c - An interactive tool to work with cards. - * Copyright (C) 2019 g10 Code GmbH + * Copyright (C) 2019, 2020 g10 Code GmbH * * This file is part of GnuPG. * @@ -1000,53 +1000,118 @@ list_card (card_info_t info) -/* The LIST command. This also updates INFO. */ +/* The LIST command. This also updates INFO if needed. */ static gpg_error_t cmd_list (card_info_t info, char *argstr) { gpg_error_t err; - int opt_cards; + int opt_cards, opt_apps; strlist_t cards = NULL; strlist_t sl; estream_t fp = opt.interactive? NULL : es_stdout; - int cardno, count; - + int cardno = -1; + char *appstr = NULL; + int count; + int need_learn = 0; + int star; + size_t snlen; + const char *s; if (!info) return print_help - ("LIST [--cards] [N]\n\n" - "Show the content of the current card or with N given the N-th card.\n" - "Option --cards lists available cards.", + ("LIST [--cards] [--apps] [N] [APP]\n\n" + "Show the content of the current card.\n" + "With N given select and list the n-th card;\n" + "with APP also given select that application.\n" + "To select an APP on the current card use '-' for N.\n" + "Option --cards lists available cards.\n" + "Option --apps lists additional card applications", 0); opt_cards = has_leading_option (argstr, "--cards"); + opt_apps = has_leading_option (argstr, "--apps"); argstr = skip_options (argstr); - if (digitp (argstr)) + if (digitp (argstr) || (*argstr == '-' && spacep (argstr+1))) { - cardno = atoi (argstr); - while (digitp (argstr)) - argstr++; + if (*argstr == '-' && (argstr[1] || spacep (argstr+1))) + argstr++; /* Keep current card. */ + else + { + cardno = atoi (argstr); + while (digitp (argstr)) + argstr++; + } while (spacep (argstr)) argstr++; + if (*argstr) + { + appstr = argstr; + while (*argstr && !spacep (argstr)) + argstr++; + while (spacep (argstr)) + argstr++; + if (*argstr) + { + /* Extra arguments found. */ + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + } } - else - cardno = -1; - - - if (opt_cards) + else if (*argstr) { - err = scd_cardlist (&cards); + /* First argument needs to be a digit. */ + err = gpg_error (GPG_ERR_INV_ARG); + goto leave; + } + + + if (!info->serialno) + { + /* This is probably the first call. We need to send a SERIALNO + * command to scd so that our session knows all cards. */ + err = scd_serialno (NULL, NULL); + if (err) + goto leave; + need_learn = 1; + } + + + if (opt_cards || opt_apps) + { + /* Note that with option --apps CARDS is here the list of all + * apps. Format is "SERIALNO APPNAME {APPNAME}". We print the + * card number in the first column. */ + if (opt_apps) + err = scd_applist (&cards, opt_cards); + else + err = scd_cardlist (&cards); if (err) goto leave; for (count = 0, sl = cards; sl; sl = sl->next, count++) - tty_fprintf (fp, "%d %s\n", count, sl->d); + { + if (info && info->serialno) + { + s = strchr (sl->d, ' '); + if (s) + snlen = s - sl->d; + else + snlen = strlen (sl->d); + star = (strlen (info->serialno) == snlen + && !memcmp (info->serialno, sl->d, snlen)); + } + else + star = 0; + tty_fprintf (fp, "%d%c %s\n", count, star? '*':' ', sl->d); + } } else { if (cardno != -1) { + /* Switch to the requested card. */ err = scd_cardlist (&cards); if (err) goto leave; @@ -1058,12 +1123,23 @@ cmd_list (card_info_t info, char *argstr) err = gpg_error (GPG_ERR_INV_INDEX); goto leave; } - err = scd_serialno (NULL, sl->d); - if (err) - goto leave; + err = scd_switchcard (sl->d); + need_learn = 1; } - err = scd_learn (info); + if (appstr && *appstr) + { + /* Switch to the requested app. */ + err = scd_switchapp (appstr); + if (err) + goto leave; + need_learn = 1; + } + + if (need_learn) + err = scd_learn (info); + else + err = 0; if (!err) list_card (info); } @@ -3341,7 +3417,7 @@ interactive_loop (void) if (!info) { - /* Copy the pending help arg into our answer. Noe that + /* Copy the pending help arg into our answer. Note that * help_arg points into answer. */ p = xstrdup (help_arg); help_arg = NULL; diff --git a/tools/gpg-card.h b/tools/gpg-card.h index 35db14d25..c6f73405f 100644 --- a/tools/gpg-card.h +++ b/tools/gpg-card.h @@ -204,6 +204,10 @@ const char *app_type_string (app_type_t app_type); gpg_error_t scd_apdu (const char *hexapdu, unsigned int *r_sw, unsigned char **r_data, size_t *r_datalen); + +gpg_error_t scd_switchcard (const char *serialno); +gpg_error_t scd_switchapp (const char *appname); + gpg_error_t scd_learn (card_info_t info); gpg_error_t scd_getattr (const char *name, struct card_info_s *info); gpg_error_t scd_setattr (const char *name, @@ -214,10 +218,12 @@ gpg_error_t scd_writekey (const char *keyref, int force, const char *keygrip); gpg_error_t scd_genkey (const char *keyref, int force, const char *algo, u32 *createtime); gpg_error_t scd_serialno (char **r_serialno, const char *demand); + gpg_error_t scd_readcert (const char *certidstr, void **r_buf, size_t *r_buflen); gpg_error_t scd_readkey (const char *keyrefstr, gcry_sexp_t *r_result); gpg_error_t scd_cardlist (strlist_t *result); +gpg_error_t scd_applist (strlist_t *result, int all); gpg_error_t scd_change_pin (const char *pinref, int reset_mode); gpg_error_t scd_checkpin (const char *serialno);