scd: Implement PIN changing and unblocking for PIV cards.

* scd/app-piv.c: Some refactoring
(do_change_chv): Implement.

Signed-off-by: Werner Koch <wk@gnupg.org>
This commit is contained in:
Werner Koch 2019-02-06 09:45:54 +01:00
parent 3231ecdafd
commit e9e876cb55
No known key found for this signature in database
GPG Key ID: E3FDFF218E45B72B
2 changed files with 278 additions and 99 deletions

View File

@ -20,6 +20,24 @@
/* Some notes:
* - Specs for PIV are at http://dx.doi.org/10.6028/NIST.SP.800-73-4
*
* - Access control matrix:
* | Action | 9B | PIN | PUK | |
* |--------------+-----+-----+-----+------------------------------|
* | Generate key | yes | | | |
* | Change 9B | yes | | | |
* | Change retry | yes | yes | | Yubikey only |
* | Import key | yes | | | |
* | Import cert | yes | | | |
* | Change CHUID | yes | | | |
* | Reset card | | | | PIN and PUK in blocked state |
* | Verify PIN | | yes | | |
* | Sign data | | yes | | |
* | Decrypt data | | yes | | |
* | Change PIN | | yes | | |
* | Change PUK | | | yes | |
* | Unblock PIN | | | yes | New PIN required |
* |---------------------------------------------------------------|
* (9B indicates the 24 byte PIV Card Application Administration Key)
*/
#include <config.h>
@ -389,10 +407,10 @@ dump_all_do (int slot)
/* Parse the key reference KEYREFSTR which is expected to hold a key
* reference for a PIN object. Return the one octet keyref or -1 for
* reference for a CHV object. Return the one octet keyref or -1 for
* an invalid reference. */
static int
parse_pin_keyref (const char *keyrefstr)
parse_chv_keyref (const char *keyrefstr)
{
if (!keyrefstr)
return -1;
@ -457,7 +475,7 @@ get_chv_status (app_t app, const char *keyrefstr)
int result;
int keyref;
keyref = parse_pin_keyref (keyrefstr);
keyref = parse_chv_keyref (keyrefstr);
if (!keyrefstr)
return -1;
@ -467,7 +485,7 @@ get_chv_status (app_t app, const char *keyrefstr)
apdu[3] = keyref;
if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL))
result = -5; /* No need to verification. */
else if (sw == 0x6a88)
else if (sw == 0x6a88 || sw == 0x6a80)
result = -2; /* No such PIN. */
else if (sw == 0x6983)
result = -3; /* PIN is blocked. */
@ -540,7 +558,7 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name)
}
else if (table[idx].special == -4) /* CHV-STATUS */
{
int tmp[3];
int tmp[4];
tmp[0] = get_chv_status (app, "PIV.00");
tmp[1] = get_chv_status (app, "PIV.80");
@ -1177,40 +1195,29 @@ make_prompt (app_t app, int remaining, const char *firstline)
}
/* Verify the Application PIN KEYREF. */
/* Helper for verify_chv to ask for the PIN and to prepare/pad it. On
* success the result is stored at (R_PIN,R_PINLEN). */
static gpg_error_t
verify_pin (app_t app, int keyref,
gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg)
ask_and_prepare_chv (app_t app, int keyref, int ask_new, int remaining,
gpg_error_t (*pincb)(void*,const char *,char **),
void *pincb_arg, char **r_pin, unsigned int *r_pinlen)
{
gpg_error_t err;
unsigned char apdu[4];
unsigned int sw;
int remaining;
const char *label;
char *prompt;
char *pinvalue = NULL;
unsigned int pinlen;
char pinbuffer[8];
char *pinbuffer = NULL;
int minlen, maxlen, padding, onlydigits;
/* First check whether a verify is at all needed. This is done with
* P1 being 0 and no Lc and command data send. */
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0x00;
apdu[3] = keyref;
if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL))
{
/* No need to verification. */
return 0; /* All fine. */
}
if ((sw & 0xfff0) == 0x63C0)
remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */
else
*r_pin = NULL;
*r_pinlen = 0;
if (ask_new)
remaining = -1;
if (remaining != -1)
log_debug ("piv: PIN %2X has %d attempts left\n", keyref, remaining);
log_debug ("piv: CHV %02X has %d attempts left\n", keyref, remaining);
switch (keyref)
{
@ -1219,21 +1226,24 @@ verify_pin (app_t app, int keyref,
maxlen = 8;
padding = 1;
onlydigits = 1;
label = _("||Please enter the Global-PIN of your PIV card");
label = (ask_new? _("|N|Please enter the new Global-PIN")
/**/ : _("||Please enter the Global-PIN of your PIV card"));
break;
case 0x80:
minlen = 6;
maxlen = 8;
padding = 1;
onlydigits = 1;
label = _("||Please enter the PIN of your PIV card");
label = (ask_new? _("|N|Please enter the new PIN")
/**/ : _("||Please enter the PIN of your PIV card"));
break;
case 0x81:
minlen = 8;
maxlen = 8;
padding = 0;
onlydigits = 0;
label = _("||Please enter the Unblocking Key of your PIV card");
label = (ask_new? _("|N|Please enter the new Unblocking Key")
/**/ :_("||Please enter the Unblocking Key of your PIV card"));
break;
case 0x96:
@ -1245,8 +1255,6 @@ verify_pin (app_t app, int keyref,
default:
return gpg_error (GPG_ERR_INV_ID);
}
log_assert (sizeof pinbuffer >= maxlen);
/* Ask for the PIN. */
prompt = make_prompt (app, remaining, label);
@ -1282,21 +1290,72 @@ verify_pin (app_t app, int keyref,
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
pinbuffer = xtrymalloc_secure (maxlen);
if (!pinbuffer)
{
err = gpg_error_from_syserror ();
wipememory (pinvalue, pinlen);
xfree (pinvalue);
return err;
}
memcpy (pinbuffer, pinvalue, pinlen);
wipememory (pinvalue, pinlen);
xfree (pinvalue);
if (padding)
{
memset (pinbuffer + pinlen, 0xff, maxlen - pinlen);
wipememory (pinvalue, pinlen);
pinlen = maxlen;
}
else
wipememory (pinvalue, pinlen);
xfree (pinvalue);
err = iso7816_verify (app->slot, keyref, pinbuffer, pinlen);
wipememory (pinbuffer, pinlen);
*r_pin = pinbuffer;
*r_pinlen = pinlen;
return 0;
}
/* Verify the card holder verification identified by KEYREF. This is
* either the Appication PIN or the Global PIN. */
static gpg_error_t
verify_chv (app_t app, int keyref,
gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg)
{
gpg_error_t err;
unsigned char apdu[4];
unsigned int sw;
int remaining;
char *pin = NULL;
unsigned int pinlen;
/* First check whether a verify is at all needed. This is done with
* P1 being 0 and no Lc and command data send. */
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0x00;
apdu[3] = keyref;
if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL))
{
/* No need to verification. */
return 0; /* All fine. */
}
if ((sw & 0xfff0) == 0x63C0)
remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */
else
remaining = -1;
err = ask_and_prepare_chv (app, keyref, 0, remaining, pincb, pincb_arg,
&pin, &pinlen);
if (err)
log_error ("PIN %02X verification failed: %s\n", keyref,gpg_strerror (err));
return err;
err = iso7816_verify (app->slot, keyref, pin, pinlen);
wipememory (pin, pinlen);
xfree (pin);
if (err)
log_error ("CHV %02X verification failed: %s\n",
keyref, gpg_strerror (err));
return err;
}
@ -1309,40 +1368,41 @@ verify_pin (app_t app, int keyref,
* PIV.81 - The PIN Unblocking key
* The supported flags are:
* APP_CHANGE_FLAG_CLEAR Clear the PIN verification state.
* APP_CHANGE_FLAG_RESET Reset a PIN using the PUK. Only
* allowed with PIV.80.
*/
static gpg_error_t
do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr,
do_change_chv (app_t app, ctrl_t ctrl, const char *pwidstr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
int keyref;
int keyref, targetkeyref;
unsigned char apdu[4];
char *newpin = NULL;
unsigned int sw;
int remaining;
char *oldpin = NULL;
/* size_t newpinlen; */
/* size_t oldpinlen; */
/* const char *newdesc; */
/* int pwid; */
pininfo_t pininfo;
unsigned int oldpinlen;
char *newpin = NULL;
unsigned int newpinlen;
(void)ctrl;
(void)pincb;
(void)pincb_arg;
/* The minimum and maximum lengths are enforced by PIV. */
memset (&pininfo, 0, sizeof pininfo);
pininfo.minlen = 6;
pininfo.maxlen = 8;
/* Check for unknown flags. */
if ((flags & ~(APP_CHANGE_FLAG_CLEAR|APP_CHANGE_FLAG_RESET)))
{
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
goto leave;
}
keyref = parse_pin_keyref (pwidstr);
/* Parse the keyref. */
targetkeyref = keyref = parse_chv_keyref (pwidstr);
if (keyref == -1)
return gpg_error (GPG_ERR_INV_ID);
if ((flags & ~APP_CHANGE_FLAG_CLEAR))
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
/* First see whether the special --clear mode has been requested. */
if ((flags & APP_CHANGE_FLAG_CLEAR))
@ -1355,7 +1415,82 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr,
goto leave;
}
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
/* Prepare reset mode. */
if ((flags & APP_CHANGE_FLAG_RESET))
{
if (keyref == 0x81)
{
err = gpg_error (GPG_ERR_INV_ID); /* Can't reset the PUK. */
goto leave;
}
/* Set the keyref to the PUK and keep the TARGETKEYREF. */
keyref = 0x81;
}
/* Get the remaining tries count. This is done by using the check
* for verified state feature. */
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0x00;
apdu[3] = keyref;
if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL))
remaining = -1; /* Already verified, thus full number of tries. */
else if ((sw & 0xfff0) == 0x63C0)
remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */
else
remaining = -1;
/* Ask for the old pin or puk. */
err = ask_and_prepare_chv (app, keyref, 0, remaining, pincb, pincb_arg,
&oldpin, &oldpinlen);
if (err)
return err;
/* Verify the old pin so that we don't prompt for the new pin if the
* old is wrong. This is not possible for the PUK, though. */
if (keyref != 0x81)
{
err = iso7816_verify (app->slot, keyref, oldpin, oldpinlen);
if (err)
{
log_error ("CHV %02X verification failed: %s\n",
keyref, gpg_strerror (err));
goto leave;
}
}
/* Ask for the new pin. */
err = ask_and_prepare_chv (app, targetkeyref, 1, -1, pincb, pincb_arg,
&newpin, &newpinlen);
if (err)
return err;
if ((flags & APP_CHANGE_FLAG_RESET))
{
char *buf = xtrymalloc_secure (oldpinlen + newpinlen);
if (!buf)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (buf, oldpin, oldpinlen);
memcpy (buf+oldpinlen, newpin, newpinlen);
err = iso7816_reset_retry_counter_with_rc (app->slot, targetkeyref,
buf, oldpinlen+newpinlen);
xfree (buf);
if (err)
log_error ("resetting CHV %02X using CHV %02X failed: %s\n",
targetkeyref, keyref, gpg_strerror (err));
}
else
{
err = iso7816_change_reference_data (app->slot, keyref,
oldpin, oldpinlen,
newpin, newpinlen);
if (err)
log_error ("CHV %02X changing PIN failed: %s\n",
keyref, gpg_strerror (err));
}
leave:
xfree (oldpin);
@ -1365,19 +1500,19 @@ do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr,
/* Perform a simple verify operation for the PIN specified by PWIDSTR.
* For valid values see do_change_pin. */
* For valid values see do_change_chv. */
static gpg_error_t
do_check_pin (app_t app, const char *pwidstr,
do_check_chv (app_t app, const char *pwidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
int keyref;
keyref = parse_pin_keyref (pwidstr);
keyref = parse_chv_keyref (pwidstr);
if (keyref == -1)
return gpg_error (GPG_ERR_INV_ID);
return verify_pin (app, keyref, pincb, pincb_arg);
return verify_chv (app, keyref, pincb, pincb_arg);
}
@ -1492,7 +1627,7 @@ do_auth (app_t app, const char *keyidstr,
}
/* Now verify the Application PIN. */
err = verify_pin (app, 0x80, pincb, pincb_arg);
err = verify_chv (app, 0x80, pincb, pincb_arg);
if (err)
return err;
@ -1675,8 +1810,8 @@ app_select_piv (app_t app)
/* app->fnc.sign = do_sign; */
app->fnc.auth = do_auth;
/* app->fnc.decipher = do_decipher; */
app->fnc.change_pin = do_change_pin;
app->fnc.check_pin = do_check_pin;
app->fnc.change_pin = do_change_chv;
app->fnc.check_pin = do_check_chv;
leave:

View File

@ -899,7 +899,7 @@ list_piv (card_info_t info, estream_t fp)
switch (info->chvinfo[i])
{
case -1: s = "[error]"; break;
case -2: s = "-"; break; /* No such PIN */
case -2: s = "-"; break; /* No such PIN or info not available. */
case -3: s = "[blocked]"; break;
case -5: s = "[verified]"; break;
default: s = "[?]"; break;
@ -950,15 +950,21 @@ static gpg_error_t
cmd_verify (card_info_t info, char *argstr)
{
gpg_error_t err;
const char *pinref;
if (!info)
return print_help ("verify [chvid]", 0);
if (info->apptype == APP_TYPE_OPENPGP)
err = scd_checkpin (info->serialno);
if (*argstr)
pinref = argstr;
else if (info->apptype == APP_TYPE_OPENPGP)
pinref = info->serialno;
else if (info->apptype == APP_TYPE_PIV)
pinref = "PIV.80";
else
err = scd_checkpin (argstr);
return gpg_error (GPG_ERR_MISSING_VALUE);
err = scd_checkpin (pinref);
if (err)
log_error ("verify failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
@ -1845,30 +1851,48 @@ cmd_generate (card_info_t info)
/* Sub-menu to change a PIN. The presented options may depend on the
* the ALLOW_ADMIN flag. */
static gpg_error_t
cmd_passwd (card_info_t info, int allow_admin)
cmd_passwd (card_info_t info, int allow_admin, char *argstr)
{
gpg_error_t err;
char *answer = NULL;
const char *pinref;
if (!info)
return print_help
("PASSWD\n\n"
("PASSWD [PINREF]\n\n"
"Menu to change or unblock the PINs. Note that the\n"
"presented menu options depend on the type of card\n"
"and whether the admin mode is enabled.",
"and whether the admin mode is enabled. For OpenPGP\n"
"and PIV cards defaults for PINREF are available.",
0);
/* Convenience message because we did this in gpg --card-edit too. */
if (info->apptype == APP_TYPE_OPENPGP)
log_info (_("OpenPGP card no. %s detected\n"),
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
if (!allow_admin)
if (!allow_admin || info->apptype != APP_TYPE_OPENPGP)
{
err = scd_change_pin ("OPENPGP.1", 0);
if (*argstr)
pinref = argstr;
else if (info->apptype == APP_TYPE_OPENPGP)
pinref = "OPENPGP.1";
else if (info->apptype == APP_TYPE_PIV)
pinref = "PIV.80";
else
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
err = scd_change_pin (pinref, 0);
if (err)
goto leave;
log_info ("PIN changed.\n");
if (info->apptype == APP_TYPE_PIV
&& !ascii_strcasecmp (pinref, "PIV.81"))
log_info ("PUK changed.\n");
else
log_info ("PIN changed.\n");
}
else if (info->apptype == APP_TYPE_OPENPGP)
{
@ -1959,23 +1983,43 @@ cmd_unblock (card_info_t info)
"command can be used to set a new PIN.",
0);
if (info->apptype == APP_TYPE_OPENPGP)
log_info (_("OpenPGP card no. %s detected\n"),
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
if (info->apptype == APP_TYPE_OPENPGP && !info->is_v2)
log_error (_("This command is only available for version 2 cards\n"));
else if (info->apptype == APP_TYPE_OPENPGP && !info->chvinfo[1])
log_error (_("Reset Code not or not anymore available\n"));
else if (info->apptype == APP_TYPE_OPENPGP)
if (info->apptype == APP_TYPE_OPENPGP)
{
err = scd_change_pin ("OPENPGP.2", 0);
if (!info->is_v2)
{
log_error (_("This command is only available for version 2 cards\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
}
else if (!info->chvinfo[1])
{
log_error (_("Reset Code not or not anymore available\n"));
err = gpg_error (GPG_ERR_PIN_BLOCKED);
}
else
{
err = scd_change_pin ("OPENPGP.2", 0);
if (!err)
log_info ("PIN changed.\n");
}
}
else if (info->apptype == APP_TYPE_PIV)
{
/* Unblock the Application PIN. */
err = scd_change_pin ("PIV.80", 1);
if (!err)
log_info ("PIN changed.\n");
log_info ("PIN unblocked and changed.\n");
}
else
log_info ("Unblocking not yet supported for '%s'\n",
app_type_string (info->apptype));
{
log_info ("Unblocking not yet supported for '%s'\n",
app_type_string (info->apptype));
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
return err;
}
@ -2079,15 +2123,15 @@ cmd_factoryreset (card_info_t info)
goto leave;
}
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
if (!termstate || is_yubikey)
{
if (is_yubikey)
log_info (_("Yubikey no. %s with PIV application detected\n"),
info->dispserialno? info->dispserialno : info->serialno);
else
if (!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))
{
/* Note: We won't see status-indicator 3 here because it
@ -2865,7 +2909,7 @@ dispatch_command (card_info_t info, const char *orig_command)
case cmdREADCERT: err = cmd_readcert (info, argstr); break;
case cmdFORCESIG: err = cmd_forcesig (info); break;
case cmdGENERATE: err = cmd_generate (info); break;
case cmdPASSWD: err = cmd_passwd (info, 1); break;
case cmdPASSWD: err = cmd_passwd (info, 1, argstr); break;
case cmdUNBLOCK: err = cmd_unblock (info); break;
case cmdFACTORYRESET: err = cmd_factoryreset (info); break;
case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break;
@ -3131,7 +3175,7 @@ interactive_loop (void)
case cmdREADCERT: err = cmd_readcert (info, argstr); break;
case cmdFORCESIG: err = cmd_forcesig (info); break;
case cmdGENERATE: err = cmd_generate (info); break;
case cmdPASSWD: err = cmd_passwd (info, allow_admin); break;
case cmdPASSWD: err = cmd_passwd (info, allow_admin, argstr); break;
case cmdUNBLOCK: err = cmd_unblock (info); break;
case cmdFACTORYRESET:
err = cmd_factoryreset (info);