From 79bed504e51034d960fcb858fb643901cad85913 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Tue, 29 Jan 2019 13:28:10 +0100 Subject: [PATCH] card: Support factory reset for Yubikey PIV application. * scd/app-common.h (struct app_ctx_s): Add field cardtype. * scd/app.c (app_new_register): Set cardtype for yubikey. (app_getattr): Add CARDTYPE. (app_write_learn_status): Emit new attribute. * scd/app-piv.c (do_getattr): Add CHV-USAGE. (do_learn_status): Emit it. * tools/card-tool.h (struct card_info_s): Add field cardtype. * tools/card-call-scd.c (learn_status_cb): Parse "CARDTYPE". * tools/gpg-card-tool.c (list_piv): Print PIN usage policy. (list_card): Print card type. (cmd_factoryreset): Implement for Yubikey with PIV. Signed-off-by: Werner Koch --- scd/app-common.h | 1 + scd/app-piv.c | 20 ++++- scd/app.c | 13 ++- tools/card-call-scd.c | 41 ++++++--- tools/card-tool.h | 2 + tools/gpg-card-tool.c | 198 +++++++++++++++++++++++++++--------------- 6 files changed, 190 insertions(+), 85 deletions(-) diff --git a/scd/app-common.h b/scd/app-common.h index b1661b524..98d8464dc 100644 --- a/scd/app-common.h +++ b/scd/app-common.h @@ -52,6 +52,7 @@ struct app_ctx_s { unsigned char *serialno; /* Serialnumber in raw form, allocated. */ size_t serialnolen; /* Length in octets of serialnumber. */ + const char *cardtype; /* NULL or string with the token's type. */ const char *apptype; unsigned int card_version; unsigned int card_status; diff --git a/scd/app-piv.c b/scd/app-piv.c index 69f12f43a..d984e9c7a 100644 --- a/scd/app-piv.c +++ b/scd/app-piv.c @@ -469,13 +469,16 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) { "SERIALNO", 0x0000, -1 }, { "$AUTHKEYID", 0x0000, -2 }, /* Default key for ssh. */ { "$DISPSERIALNO",0x0000, -3 }, - { "CHV-STATUS", 0x0000, -4 } + { "CHV-STATUS", 0x0000, -4 }, + { "CHV-USAGE", 0x007E, -5 } }; gpg_error_t err = 0; int idx; void *relptr; unsigned char *value; size_t valuelen; + const unsigned char *s; + size_t n; for (idx=0; (idx < DIM (table) && ascii_strcasecmp (table[idx].name, name)); idx++) @@ -521,6 +524,20 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name) err = send_status_printf (ctrl, table[idx].name, "%d %d %d", tmp[0], tmp[1], tmp[2]); } + else if (table[idx].special == -5) /* CHV-USAGE (aka PIN Usage Policy) */ + { + /* We return 2 hex bytes or nothing in case the discovery object + * is not supported. */ + relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err); + if (relptr) + { + s = find_tlv (value, valuelen, 0x7E, &n); + if (s && n && (s = find_tlv (s, n, 0x5F2F, &n)) && n >=2 ) + err = send_status_printf (ctrl, table[idx].name, "%02X %02X", + s[0], s[1]); + xfree (relptr); + } + } else { relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err); @@ -577,6 +594,7 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) (void)flags; + do_getattr (app, ctrl, "CHV-USAGE"); do_getattr (app, ctrl, "CHV-STATUS"); for (i=0; data_objects[i].tag; i++) diff --git a/scd/app.c b/scd/app.c index 219cee68d..c79a174fc 100644 --- a/scd/app.c +++ b/scd/app.c @@ -228,6 +228,7 @@ app_new_register (int slot, ctrl_t ctrl, const char *name, && !iso7816_apdu_direct (slot, "\x00\x1d\x00\x00\x00", 5, 0, NULL, &buf, &buflen)) { + app->cardtype = "yubikey"; if (opt.verbose) { log_info ("Yubico: config="); @@ -640,9 +641,12 @@ app_write_learn_status (app_t app, ctrl_t ctrl, unsigned int flags) if (!app->fnc.learn_status) return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); - /* We do not send APPTYPE if only keypairinfo is requested. */ + /* We do not send CARD and APPTYPE if only keypairinfo is requested. */ + if (app->cardtype && !(flags & 1)) + send_status_direct (ctrl, "CARDTYPE", app->cardtype); if (app->apptype && !(flags & 1)) send_status_direct (ctrl, "APPTYPE", app->apptype); + err = lock_app (app, ctrl); if (err) return err; @@ -721,6 +725,11 @@ app_getattr (app_t app, ctrl_t ctrl, const char *name) if (!app->ref_count) return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); + if (app->cardtype && name && !strcmp (name, "CARDTYPE")) + { + send_status_direct (ctrl, "CARDTYPE", app->cardtype); + return 0; + } if (app->apptype && name && !strcmp (name, "APPTYPE")) { send_status_direct (ctrl, "APPTYPE", app->apptype); @@ -744,7 +753,7 @@ app_getattr (app_t app, ctrl_t ctrl, const char *name) err = lock_app (app, ctrl); if (err) return err; - err = app->fnc.getattr (app, ctrl, name); + err = app->fnc.getattr (app, ctrl, name); unlock_app (app); return err; } diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index 2551b19f6..9a742a728 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -137,6 +137,7 @@ release_card_info (card_info_t info) return; xfree (info->reader); info->reader = NULL; + xfree (info->cardtype); info->cardtype = NULL; xfree (info->serialno); info->serialno = NULL; xfree (info->dispserialno); info->dispserialno = NULL; xfree (info->apptypestr); info->apptypestr = NULL; @@ -157,7 +158,7 @@ release_card_info (card_info_t info) xfree (info->kinfo); info->kinfo = kinfo; } - + info->chvusage[0] = info->chvusage[1] = 0; } @@ -724,6 +725,11 @@ learn_status_cb (void *opaque, const char *line) parm->is_v2 = (strlen (parm->serialno) >= 16 && xtoi_2 (parm->serialno+12) >= 2 ); } + else if (!memcmp (keyword, "CARDTYPE", keywordlen)) + { + xfree (parm->cardtype); + parm->cardtype = unescape_status_string (line); + } else if (!memcmp (keyword, "DISP-SEX", keywordlen)) { parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0; @@ -779,17 +785,26 @@ learn_status_cb (void *opaque, const char *line) break; case 9: - if (!memcmp (keyword, "DISP-NAME", keywordlen)) - { - xfree (parm->disp_name); - parm->disp_name = unescape_status_string (line); - } - else if (!memcmp (keyword, "DISP-LANG", keywordlen)) - { - xfree (parm->disp_lang); - parm->disp_lang = unescape_status_string (line); - } - break; + if (!memcmp (keyword, "DISP-NAME", keywordlen)) + { + xfree (parm->disp_name); + parm->disp_name = unescape_status_string (line); + } + else if (!memcmp (keyword, "DISP-LANG", keywordlen)) + { + xfree (parm->disp_lang); + parm->disp_lang = unescape_status_string (line); + } + else if (!memcmp (keyword, "CHV-USAGE", keywordlen)) + { + unsigned int byte1, byte2; + + byte1 = byte2 = 0; + sscanf (line, "%x %x", &byte1, &byte2); + parm->chvusage[0] = byte1; + parm->chvusage[1] = byte2; + } + break; case 10: if (!memcmp (keyword, "PUBKEY-URL", keywordlen)) @@ -839,7 +854,7 @@ learn_status_cb (void *opaque, const char *line) } else if (parm->apptype == APP_TYPE_PIV) { - for (i=0; *p && DIM (parm->chvinfo); i++) + for (i=0; *p && i < DIM (parm->chvinfo); i++) { parm->chvinfo[i] = atoi (p); while (*p && !spacep (p)) diff --git a/tools/card-tool.h b/tools/card-tool.h index bcc257ce2..b1d866228 100644 --- a/tools/card-tool.h +++ b/tools/card-tool.h @@ -104,6 +104,7 @@ struct card_info_s { int error; /* private. */ char *reader; /* Reader information. */ + char *cardtype; /* NULL or type of the card. */ char *apptypestr; /* Malloced application type string. */ app_type_t apptype;/* Translated from APPTYPESTR. */ char *serialno; /* malloced hex string. */ @@ -128,6 +129,7 @@ struct card_info_s int is_v2; /* True if this is a v2 openpgp card. */ int chvmaxlen[3]; /* Maximum allowed length of a CHV. */ int chvinfo[3]; /* Allowed retries for the CHV; 0 = blocked. */ + unsigned char chvusage[2]; /* Data object 5F2F */ struct key_attr key_attr[3]; /* OpenPGP card key attributes. */ struct { unsigned int ki:1; /* Key import available. */ diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 5ba44fcbb..4f7962060 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -763,15 +763,37 @@ static void list_piv (card_info_t info, estream_t fp) { static struct keyinfolabel_s keyinfolabels[] = { - { "PIV Authentication:", "PIV.9A" }, - { "Card Authenticat. :", "PIV.9E" }, - { "Digital Signature :", "PIV.9C" }, - { "Key Management ...:", "PIV.9D" }, + { "PIV authentication:", "PIV.9A" }, + { "Card authenticat. :", "PIV.9E" }, + { "Digital signature :", "PIV.9C" }, + { "Key management ...:", "PIV.9D" }, { NULL, NULL } }; const char *s; int i; + if (info->chvusage[0] || info->chvusage[1]) + { + tty_fprintf (fp, "PIN usage policy .:"); + if ((info->chvusage[0] & 0x40)) + tty_fprintf (fp, " app-pin"); + if ((info->chvusage[0] & 0x20)) + tty_fprintf (fp, " global-pin"); + if ((info->chvusage[0] & 0x10)) + tty_fprintf (fp, " occ"); + if ((info->chvusage[0] & 0x08)) + tty_fprintf (fp, " vci"); + if ((info->chvusage[0] & 0x08) && !(info->chvusage[0] & 0x04)) + tty_fprintf (fp, " pairing"); + + if (info->chvusage[1] == 0x10) + tty_fprintf (fp, " primary:card"); + else if (info->chvusage[1] == 0x20) + tty_fprintf (fp, " primary:global"); + + tty_fprintf (fp, "\n"); + } + tty_fprintf (fp, "PIN retry counter :"); for (i=0; i < DIM (info->chvinfo); i++) { @@ -790,7 +812,7 @@ list_piv (card_info_t info, estream_t fp) tty_fprintf (fp, " %s", s); } } - tty_fprintf (fp, "\n", s); + tty_fprintf (fp, "\n"); list_all_kinfo (info, keyinfolabels, fp); } @@ -804,9 +826,11 @@ list_card (card_info_t info) tty_fprintf (fp, "Reader ...........: %s\n", info->reader? info->reader : "[none]"); + if (info->cardtype) + tty_fprintf (fp, "Card type ........: %s\n", info->cardtype); tty_fprintf (fp, "Serial number ....: %s\n", info->serialno? info->serialno : "[none]"); - tty_fprintf (fp, "Application Type .: %s%s%s%s\n", + tty_fprintf (fp, "Application type .: %s%s%s%s\n", app_type_string (info->apptype), info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"", info->apptype == APP_TYPE_UNKNOWN && info->apptypestr @@ -1836,26 +1860,32 @@ cmd_factoryreset (card_info_t info) char *answer = NULL; int termstate = 0; int any_apdu = 0; + int is_yubikey = 0; int i; if (!info) return print_help ("FACTORY-RESET\n\n" - "Do a complete reset of an OpenPGP card. This deletes all\n" - "data and keys and resets the PINs to their default. This\n" - "mainly used by developers with scratch cards. Don't worry,\n" - "you need to confirm before the command proceeds.", - APP_TYPE_OPENPGP, 0); + "Do a complete reset of some OpenPGP and PIV cards. This\n" + "deletes all data and keys and resets the PINs to their default.\n" + "This is mainly used by developers with scratch cards. Don't\n" + "worry, you need to confirm before the command proceeds.", + APP_TYPE_OPENPGP, APP_TYPE_PIV, 0); - if (info->apptype != APP_TYPE_OPENPGP) - { - log_info ("Note: This is an OpenPGP only command.\n"); - return gpg_error (GPG_ERR_NOT_SUPPORTED); - } + /* We support the factory reset for most OpenPGP cards and Yubikeys + * with the PIV application. */ + if (info->apptype == APP_TYPE_OPENPGP) + ; + else if (info->apptype == APP_TYPE_PIV + && info->cardtype && !strcmp (info->cardtype, "yubikey")) + is_yubikey = 1; + else - /* The code below basically does the same what this - * gpg-connect-agent script does: + return gpg_error (GPG_ERR_NOT_SUPPORTED); + + /* For an OpenPGP card the code below basically does the same what + * this gpg-connect-agent script does: * * scd reset * scd serialno undefined @@ -1873,7 +1903,8 @@ cmd_factoryreset (card_info_t info) * scd reset * /echo Card has been reset to factory defaults * - * but tries to find out something about the card first. + * For a PIV application on a Yubikey it merely issues the Yubikey + * specific resset command. */ err = scd_learn (info); @@ -1886,17 +1917,24 @@ cmd_factoryreset (card_info_t info) goto leave; } - if (!termstate) + if (!termstate || is_yubikey) { - log_info (_("OpenPGP card no. %s detected\n"), - info->dispserialno? info->dispserialno : info->serialno); - if (!(info->status_indicator == 3 || info->status_indicator == 5)) + if (is_yubikey) + log_info (_("Yubikey no. %s with PIV application detected\n"), + info->dispserialno? info->dispserialno : info->serialno); + else { - /* Note: We won't see status-indicator 3 here because it is not - * possible to select a card application in termination state. */ - log_error (_("This command is not supported by this card\n")); - err = gpg_error (GPG_ERR_NOT_SUPPORTED); - goto leave; + log_info (_("OpenPGP card no. %s detected\n"), + info->dispserialno? info->dispserialno : info->serialno); + if (!(info->status_indicator == 3 || info->status_indicator == 5)) + { + /* Note: We won't see status-indicator 3 here because it + * is not possible to select a card application in + * termination state. */ + log_error (_("This command is not supported by this card\n")); + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + goto leave; + } } tty_printf ("\n"); @@ -1924,51 +1962,73 @@ cmd_factoryreset (card_info_t info) goto leave; } + + if (is_yubikey) + { + /* The PIV application si already selected, we only need to + * send the special reset APDU after having blocked PIN and + * PUK. Note that blocking the PUK is done using the + * unblock PIN command. */ + any_apdu = 1; + for (i=0; i < 5; i++) + send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff); + for (i=0; i < 5; i++) + send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF", + "RESET RETRY COUNTER", 0xffff); + err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0); + if (err) + goto leave; + } + else /* OpenPGP card. */ + { + any_apdu = 1; + /* We need to select a card application before we can send APDUs + * to the card without scdaemon doing anything on its own. */ + err = send_apdu (NULL, "RESET", 0); + if (err) + goto leave; + err = send_apdu ("undefined", "dummy select ", 0); + if (err) + goto leave; + /* Select the OpenPGP application. */ + err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0); + if (err) + goto leave; + + /* Do some dummy verifies with wrong PINs to set the retry + * counter to zero. We can't easily use the card version 2.1 + * feature of presenting the admin PIN to allow the terminate + * command because there is no machinery in scdaemon to catch + * the verify command and ask for the PIN when the "APDU" + * command is used. + * Here, the length of dummy wrong PIN is 32-byte, also + * supporting authentication with KDF DO. */ + for (i=0; i < 4; i++) + send_apdu ("0020008120" + "40404040404040404040404040404040" + "40404040404040404040404040404040", "VERIFY", 0xffff); + for (i=0; i < 4; i++) + send_apdu ("0020008320" + "40404040404040404040404040404040" + "40404040404040404040404040404040", "VERIFY", 0xffff); + + /* Send terminate datafile command. */ + err = send_apdu ("00e60000", "TERMINATE DF", 0x6985); + if (err) + goto leave; + } + } + + if (!is_yubikey) + { any_apdu = 1; - /* We need to select a card application before we can send APDUs - * to the card without scdaemon doing anything on its own. */ - err = send_apdu (NULL, "RESET", 0); - if (err) - goto leave; - err = send_apdu ("undefined", "dummy select ", 0); - if (err) - goto leave; - - /* Select the OpenPGP application. */ - err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0); - if (err) - goto leave; - - /* Do some dummy verifies with wrong PINs to set the retry - * counter to zero. We can't easily use the card version 2.1 - * feature of presenting the admin PIN to allow the terminate - * command because there is no machinery in scdaemon to catch - * the verify command and ask for the PIN when the "APDU" - * command is used. - * Here, the length of dummy wrong PIN is 32-byte, also - * supporting authentication with KDF DO. */ - for (i=0; i < 4; i++) - send_apdu ("0020008120" - "40404040404040404040404040404040" - "40404040404040404040404040404040", "VERIFY", 0xffff); - for (i=0; i < 4; i++) - send_apdu ("0020008320" - "40404040404040404040404040404040" - "40404040404040404040404040404040", "VERIFY", 0xffff); - - /* Send terminate datafile command. */ - err = send_apdu ("00e60000", "TERMINATE DF", 0x6985); + /* Send activate datafile command. This is used without + * confirmation if the card is already in termination state. */ + err = send_apdu ("00440000", "ACTIVATE DF", 0); if (err) goto leave; } - any_apdu = 1; - /* Send activate datafile command. This is used without - * confirmation if the card is already in termination state. */ - err = send_apdu ("00440000", "ACTIVATE DF", 0); - if (err) - goto leave; - /* Finally we reset the card reader once more. */ err = send_apdu (NULL, "RESET", 0); if (err) @@ -1979,7 +2039,7 @@ cmd_factoryreset (card_info_t info) err = scd_serialno (&answer, NULL); leave: - if (err && any_apdu) + if (err && any_apdu && !is_yubikey) { log_info ("Due to an error the card might be in an inconsistent state\n" "You should run the LIST command to check this.\n");