aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorWerner Koch <[email protected]>2019-01-21 14:01:45 +0000
committerWerner Koch <[email protected]>2019-01-21 14:01:45 +0000
commitfa9d703de5c70ae925e8ca6604073506f24d641a (patch)
treeb6b6725f956dc5095047529c6cfa6de664b9ed96
parentscd: Add option --clear to PASSWD. (diff)
downloadgnupg-fa9d703de5c70ae925e8ca6604073506f24d641a.tar.gz
gnupg-fa9d703de5c70ae925e8ca6604073506f24d641a.zip
scd: Support CHV-STATUS and CHECKPIN for PIV.
* scd/app-piv.c (parse_pin_keyref): New. (get_chv_status): New. (do_getattr): Add name CHV-STATUS. (verify_pin): Add arg keyref to support other PINs. (do_change_pin): New. Right now limited to --clear. (do_check_pin): New. (app_select_piv): Register new commands. Signed-off-by: Werner Koch <[email protected]>
-rw-r--r--scd/app-piv.c237
1 files changed, 212 insertions, 25 deletions
diff --git a/scd/app-piv.c b/scd/app-piv.c
index 9b4047753..011b55229 100644
--- a/scd/app-piv.c
+++ b/scd/app-piv.c
@@ -364,6 +364,25 @@ 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
+ * an invalid reference. */
+static int
+parse_pin_keyref (const char *keyrefstr)
+{
+ if (!keyrefstr)
+ return -1;
+ else if (!ascii_strcasecmp (keyrefstr, "PIV.00"))
+ return 0x00;
+ else if (!ascii_strcasecmp (keyrefstr, "PIV.80"))
+ return 0x80;
+ else if (!ascii_strcasecmp (keyrefstr, "PIV.81"))
+ return 0x81;
+ else
+ return -1;
+}
+
+
/* Return an allocated string with the serial number in a format to be
* show to the user. With FAILMODE is true return NULL if such an
* abbreviated S/N is not available, else return the full serial
@@ -396,6 +415,47 @@ get_dispserialno (app_t app, int failmode)
}
+/* The verify command can be used to retrieve the security status of
+ * the card. Given the PIN name (e.g. "PIV.80" for thge application
+ * pin, a status is returned:
+ *
+ * -1 = Error retrieving the data,
+ * -2 = No such PIN,
+ * -3 = PIN blocked,
+ * -5 = Verify still valid,
+ * n >= 0 = Number of verification attempts left.
+ */
+static int
+get_chv_status (app_t app, const char *keyrefstr)
+{
+ unsigned char apdu[4];
+ unsigned int sw;
+ int result;
+ int keyref;
+
+ keyref = parse_pin_keyref (keyrefstr);
+ if (!keyrefstr)
+ return -1;
+
+ 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))
+ result = -5; /* No need to verification. */
+ else if (sw == 0x6a88)
+ result = -2; /* No such PIN. */
+ else if (sw == 0x6983)
+ result = -3; /* PIN is blocked. */
+ else if ((sw & 0xfff0) == 0x63C0)
+ result = (sw & 0x000f);
+ else
+ result = -1; /* Error. */
+
+ return result;
+}
+
+
/* Implementation of the GETATTR command. This is similar to the
* LEARN command but returns only one value via status lines. */
static gpg_error_t
@@ -408,7 +468,8 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name)
} table[] = {
{ "SERIALNO", 0x0000, -1 },
{ "$AUTHKEYID", 0x0000, -2 }, /* Default key for ssh. */
- { "$DISPSERIALNO",0x0000, -3 }
+ { "$DISPSERIALNO",0x0000, -3 },
+ { "CHV-STATUS", 0x0000, -4 }
};
gpg_error_t err = 0;
int idx;
@@ -450,6 +511,16 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name)
else
err = gpg_error (GPG_ERR_INV_NAME); /* No Abbreviated S/N. */
}
+ else if (table[idx].special == -4) /* CHV-STATUS */
+ {
+ int tmp[3];
+
+ tmp[0] = get_chv_status (app, "PIV.00");
+ tmp[1] = get_chv_status (app, "PIV.80");
+ tmp[2] = get_chv_status (app, "PIV.81");
+ err = send_status_printf (ctrl, table[idx].name, "%d %d %d",
+ tmp[0], tmp[1], tmp[2]);
+ }
else
{
relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err);
@@ -849,27 +920,28 @@ make_prompt (app_t app, int remaining, const char *firstline)
}
-
-/* Verify the Application PIN for use with data object DOBJ. */
+/* Verify the Application PIN KEYREF. */
static gpg_error_t
-verify_pin (app_t app, data_object_t dobj,
+verify_pin (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;
+ const char *label;
char *prompt;
char *pinvalue = NULL;
unsigned int pinlen;
char pinbuffer[8];
+ 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] = 0x80;
+ apdu[3] = keyref;
if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL))
{
/* No need to verification. */
@@ -881,11 +953,46 @@ verify_pin (app_t app, data_object_t dobj,
remaining = -1;
if (remaining != -1)
- log_debug ("piv: PIN for %s has %d attempts left\n",
- dobj->keyref, remaining);
+ log_debug ("piv: PIN %2X has %d attempts left\n", keyref, remaining);
+
+ switch (keyref)
+ {
+ case 0x00:
+ minlen = 6;
+ maxlen = 8;
+ padding = 1;
+ onlydigits = 1;
+ label = _("||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");
+ break;
+ case 0x81:
+ minlen = 8;
+ maxlen = 8;
+ padding = 0;
+ onlydigits = 0;
+ label = _("||Please enter the Unblocking Key of your PIV card");
+ break;
+
+ case 0x96:
+ case 0x97:
+ case 0x98:
+ case 0x9B:
+ return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+
+ default:
+ return gpg_error (GPG_ERR_INV_ID);
+ }
+ log_assert (sizeof pinbuffer >= maxlen);
+
/* Ask for the PIN. */
- prompt = make_prompt (app, remaining, _("||Please enter your PIV PIN"));
+ prompt = make_prompt (app, remaining, label);
err = pincb (pincb_arg, prompt, &pinvalue);
xfree (prompt);
prompt = NULL;
@@ -896,24 +1003,22 @@ verify_pin (app_t app, data_object_t dobj,
}
pinlen = pinvalue? strlen (pinvalue) : 0;
- if (pinlen < 6)
+ if (pinlen < minlen)
{
- log_error (_("PIN for is too short;"
- " minimum length is %d\n"), 6);
+ log_error (_("PIN for is too short; minimum length is %d\n"), minlen);
if (pinvalue)
wipememory (pinvalue, pinlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
- if (pinlen > sizeof pinbuffer)
+ if (pinlen > maxlen)
{
- log_error (_("PIN for is too long;"
- " maximum length is %d\n"), (int)sizeof pinbuffer);
+ log_error (_("PIN for is too long; maximum length is %d\n"), maxlen);
wipememory (pinvalue, pinlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
- if (strspn (pinvalue, "0123456789") != pinlen)
+ if (onlydigits && strspn (pinvalue, "0123456789") != pinlen)
{
log_error (_("PIN has invalid characters; only digits are allowed\n"));
wipememory (pinvalue, pinlen);
@@ -921,20 +1026,102 @@ verify_pin (app_t app, data_object_t dobj,
return gpg_error (GPG_ERR_BAD_PIN);
}
memcpy (pinbuffer, pinvalue, pinlen);
- memset (pinbuffer + pinlen, 0xff, sizeof(pinbuffer) - pinlen);
- wipememory (pinvalue, pinlen);
+ if (padding)
+ {
+ memset (pinbuffer + pinlen, 0xff, maxlen - pinlen);
+ wipememory (pinvalue, pinlen);
+ pinlen = maxlen;
+ }
+ else
+ wipememory (pinvalue, pinlen);
xfree (pinvalue);
- err = iso7816_verify (app->slot, 0x80,
- pinbuffer, sizeof pinbuffer);
- wipememory (pinbuffer, sizeof pinbuffer);
+ err = iso7816_verify (app->slot, keyref, pinbuffer, pinlen);
+ wipememory (pinbuffer, pinlen);
if (err)
- log_error ("PIN verification failed: %s\n", gpg_strerror (err));
+ log_error ("PIN %02X verification failed: %s\n", keyref,gpg_strerror (err));
return err;
}
+/* Handle the PASSWD command. Valid values for PWIDSTR are
+ * key references related to PINs; in particular:
+ * PIV.00 - The Global PIN
+ * PIV.80 - The Application PIN
+ * PIV.81 - The PIN Unblocking key
+ * The supported flags are:
+ * APP_CHANGE_FLAG_CLEAR Clear the PIN verification state.
+ */
+static gpg_error_t
+do_change_pin (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;
+ unsigned char apdu[4];
+
+ char *newpin = NULL;
+ char *oldpin = NULL;
+ size_t newpinlen;
+ size_t oldpinlen;
+ const char *newdesc;
+ int pwid;
+ pininfo_t pininfo;
+
+ (void)ctrl;
+
+ /* The minimum and maximum lengths are enforced by PIV. */
+ memset (&pininfo, 0, sizeof pininfo);
+ pininfo.minlen = 6;
+ pininfo.maxlen = 8;
+
+ keyref = parse_pin_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);
+
+ /* First see whether the special --clear mode has been requested. */
+ if ((flags & APP_CHANGE_FLAG_CLEAR))
+ {
+ apdu[0] = 0x00;
+ apdu[1] = ISO7816_VERIFY;
+ apdu[2] = 0xff;
+ apdu[3] = keyref;
+ err = iso7816_apdu_direct (app->slot, apdu, 4, 0, NULL, NULL, NULL);
+ goto leave;
+ }
+
+ err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+
+ leave:
+ xfree (oldpin);
+ xfree (newpin);
+ return err;
+}
+
+
+/* Perform a simple verify operation for the PIN specified by PWIDSTR.
+ * For valid values see do_change_pin. */
+static gpg_error_t
+do_check_pin (app_t app, const char *pwidstr,
+ gpg_error_t (*pincb)(void*, const char *, char **),
+ void *pincb_arg)
+{
+ int keyref;
+
+ keyref = parse_pin_keyref (pwidstr);
+ if (keyref == -1)
+ return gpg_error (GPG_ERR_INV_ID);
+
+ return verify_pin (app, keyref, pincb, pincb_arg);
+}
+
+
/* Compute a digital signature using the GENERAL AUTHENTICATE command
* on INDATA which is expected to be the raw message digest. The
* KEYIDSTR has the key reference or its OID (e.g. "PIV.9A"). The
@@ -1045,8 +1232,8 @@ do_auth (app_t app, const char *keyidstr,
goto leave;
}
- /* Now verify the PIN. */
- err = verify_pin (app, dobj, pincb, pincb_arg);
+ /* Now verify the Application PIN. */
+ err = verify_pin (app, 0x80, pincb, pincb_arg);
if (err)
return err;
@@ -1226,8 +1413,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_pin;
+ app->fnc.check_pin = do_check_pin;
leave: