card: Support factory reset for Yubikey PIV application.
authorWerner Koch <wk@gnupg.org>
Tue, 29 Jan 2019 12:28:10 +0000 (13:28 +0100)
committerWerner Koch <wk@gnupg.org>
Tue, 29 Jan 2019 12:46:52 +0000 (13:46 +0100)
* 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>
scd/app-common.h
scd/app-piv.c
scd/app.c
tools/card-call-scd.c
tools/card-tool.h
tools/gpg-card-tool.c

index b1661b5..98d8464 100644 (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;
index 69f12f4..d984e9c 100644 (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++)
index 219cee6..c79a174 100644 (file)
--- 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;
 }
index 2551b19..9a742a7 100644 (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))
index bcc257c..b1d8662 100644 (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.  */
index 5ba44fc..4f79620 100644 (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
+
+    return gpg_error (GPG_ERR_NOT_SUPPORTED);
 
-  /* The code below basically does the same what this
-   * gpg-connect-agent script does:
+  /* 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;
         }
 
-      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;
+      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;
+        }
+    }
 
-      /* 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 (!is_yubikey)
+    {
+      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;
     }
 
-  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");