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 <wk@gnupg.org>
This commit is contained in:
Werner Koch 2019-01-29 13:28:10 +01:00
parent 9325c92284
commit 79bed504e5
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
6 changed files with 190 additions and 85 deletions

View File

@ -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;

View File

@ -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++)

View File

@ -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;
}

View File

@ -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))

View File

@ -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. */

View File

@ -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");