gpg: Add sub-command "factory-reset" to --card-edit.
authorWerner Koch <wk@gnupg.org>
Mon, 15 Dec 2014 16:38:40 +0000 (17:38 +0100)
committerWerner Koch <wk@gnupg.org>
Mon, 15 Dec 2014 16:38:40 +0000 (17:38 +0100)
* common/util.h (GPG_ERR_OBJ_TERM_STATE): New.
* scd/iso7816.c (map_sw): Add this error code.
* scd/app-openpgp.c (do_getattr): Return the life cycle indicator.
* scd/app.c (select_application): Allow a return value of
GPG_ERR_OBJ_TERM_STATE.
* scd/scdaemon.c (set_debug): Print the DBG_READER value.
* g10/call-agent.c (start_agent): Print a status line for the
termination state.
(agent_scd_learn): Make arg "info" optional.
(agent_scd_apdu): New.
* g10/card-util.c (send_apdu): New.
(factory_reset): New.
(card_edit): Add command factory-reset.

Signed-off-by: Werner Koch <wk@gnupg.org>
common/util.h
doc/DETAILS
g10/call-agent.c
g10/call-agent.h
g10/card-util.c
scd/apdu.h
scd/app-openpgp.c
scd/app.c
scd/iso7816.c
scd/scdaemon.c

index a6f8606..94878bc 100644 (file)
@@ -38,7 +38,8 @@
 /* These error codes are used but not defined in the required
    libgpg-error version.  Define them here. */
 #if GPG_ERROR_VERSION_NUMBER < 0x011200  /* 1.18 */
-# define GPG_ERR_FORBIDDEN    251
+# define GPG_ERR_OBJ_TERM_STATE 225
+# define GPG_ERR_FORBIDDEN      251
 #endif
 
 
index 9ad616c..ba2725f 100644 (file)
@@ -765,6 +765,7 @@ pkd:0:1024:B665B1435F4C2 .... FF26ABB:
       - 4 :: No card available
       - 5 :: No card reader available
       - 6 :: No card support available
+      - 7 :: Card is in termination state
 
 *** SC_OP_FAILURE [<code>]
     An operation on a smartcard definitely failed.  Currently there is
index 43a5c4e..0450b81 100644 (file)
@@ -343,6 +343,9 @@ start_agent (ctrl_t ctrl, int for_card)
             case GPG_ERR_NO_SCDAEMON:
               write_status_text (STATUS_CARDCTRL, "6");
               break;
+            case GPG_ERR_OBJ_TERM_STATE:
+              write_status_text (STATUS_CARDCTRL, "7");
+              break;
             default:
               write_status_text (STATUS_CARDCTRL, "4");
               log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc));
@@ -586,6 +589,8 @@ learn_status_cb (void *opaque, const char *line)
                     parm->extcap.ki = abool;
                   else if (!strcmp (p, "aac"))
                     parm->extcap.aac = abool;
+                  else if (!strcmp (p, "si"))
+                    parm->status_indicator = strtoul (p2, NULL, 10);
                 }
             }
           xfree (buf);
@@ -657,6 +662,9 @@ agent_scd_learn (struct agent_card_info_s *info)
   struct default_inq_parm_s parm;
   struct agent_card_info_s dummyinfo;
 
+  if (!info)
+    info = &dummyinfo;
+  memset (info, 0, sizeof *info);
   memset (&parm, 0, sizeof parm);
 
   rc = start_agent (NULL, 1);
@@ -675,11 +683,7 @@ agent_scd_learn (struct agent_card_info_s *info)
   if (rc)
     return rc;
 
-  if (!info)
-    info = &dummyinfo;
-
   parm.ctx = agent_ctx;
-  memset (info, 0, sizeof *info);
   rc = assuan_transact (agent_ctx, "LEARN --sendinfo",
                         dummy_data_cb, NULL, default_inq_cb, &parm,
                         learn_status_cb, info);
@@ -694,6 +698,63 @@ agent_scd_learn (struct agent_card_info_s *info)
 }
 
 
+/* Send an APDU to the current card.  On success the status word is
+   stored at R_SW.  With HEXAPDU being NULL only a RESET command is
+   send to scd.  With HEXAPDU being the string "undefined" the command
+   "SERIALNO undefined" is send to scd. */
+gpg_error_t
+agent_scd_apdu (const char *hexapdu, unsigned int *r_sw)
+{
+  gpg_error_t err;
+
+  /* Start the agent but not with the card flag so that we do not
+     autoselect the openpgp application.  */
+  err = start_agent (NULL, 0);
+  if (err)
+    return err;
+
+  if (!hexapdu)
+    {
+      err = assuan_transact (agent_ctx, "SCD RESET",
+                             NULL, NULL, NULL, NULL, NULL, NULL);
+
+    }
+  else if (!strcmp (hexapdu, "undefined"))
+    {
+      err = assuan_transact (agent_ctx, "SCD SERIALNO undefined",
+                             NULL, NULL, NULL, NULL, NULL, NULL);
+    }
+  else
+    {
+      char line[ASSUAN_LINELENGTH];
+      membuf_t mb;
+      unsigned char *data;
+      size_t datalen;
+
+      init_membuf (&mb, 256);
+
+      snprintf (line, DIM(line)-1, "SCD APDU %s", hexapdu);
+      err = assuan_transact (agent_ctx, line,
+                             membuf_data_cb, &mb, NULL, NULL, NULL, NULL);
+      if (!err)
+        {
+          data = get_membuf (&mb, &datalen);
+          if (!data)
+            err = gpg_error_from_syserror ();
+          else if (datalen < 2) /* Ooops */
+            err = gpg_error (GPG_ERR_CARD);
+          else
+            {
+              *r_sw = (data[datalen-2] << 8) | data[datalen-1];
+            }
+          xfree (data);
+        }
+    }
+
+  return err;
+}
+
+
 int
 agent_keytocard (const char *hexgrip, int keyno, int force,
                  const char *serialno, const char *timestamp)
index a24941e..bcb5ae9 100644 (file)
@@ -61,6 +61,7 @@ struct agent_card_info_s
     unsigned int ki:1;     /* Key import available.  */
     unsigned int aac:1;    /* Algorithm attributes are changeable.  */
   } extcap;
+  unsigned int status_indicator;
 };
 
 struct agent_card_genkey_s {
@@ -78,6 +79,9 @@ void agent_release_card_info (struct agent_card_info_s *info);
 /* Return card info. */
 int agent_scd_learn (struct agent_card_info_s *info);
 
+/* Send an APDU to the card.  */
+gpg_error_t agent_scd_apdu (const char *hexapdu, unsigned int *r_sw);
+
 /* Update INFO with the attribute NAME. */
 int agent_scd_getattr (const char *name, struct agent_card_info_s *info);
 
index 0535c1d..b030fad 100644 (file)
@@ -1635,6 +1635,169 @@ card_store_subkey (KBNODE node, int use)
 }
 
 
+
+/* Direct sending of an hex encoded APDU with error printing.  */
+static gpg_error_t
+send_apdu (const char *hexapdu, const char *desc, unsigned int ignore)
+{
+  gpg_error_t err;
+  unsigned int sw;
+
+  err = agent_scd_apdu (hexapdu, &sw);
+  if (err)
+    tty_printf ("sending card command %s failed: %s\n", desc,
+                gpg_strerror (err));
+  else if (!hexapdu || !strcmp (hexapdu, "undefined"))
+    ;
+  else if (ignore == 0xffff)
+    ; /* Ignore all status words.  */
+  else if (sw != 0x9000)
+    {
+      switch (sw)
+        {
+        case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break;
+        case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break;
+        case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break;
+        default: err = gpg_error (GPG_ERR_CARD);
+        }
+      if (!(ignore && ignore == sw))
+        tty_printf ("card command %s failed: %s (0x%04x)\n", desc,
+                    gpg_strerror (err),  sw);
+    }
+  return err;
+}
+
+
+/* Do a factory reset after confirmation.  */
+static void
+factory_reset (void)
+{
+  struct agent_card_info_s info;
+  gpg_error_t err;
+  char *answer = NULL;
+  int termstate = 0;
+  int i;
+
+  /*  The code below basically does the same what this
+      gpg-connect-agent script does:
+
+        scd reset
+        scd serialno undefined
+        scd apdu 00 A4 04 00 06 D2 76 00 01 24 01
+        scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+        scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+        scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+        scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
+        scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+        scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+        scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+        scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
+        scd apdu 00 e6 00 00
+        scd reset
+        scd serialno undefined
+        scd apdu 00 A4 04 00 06 D2 76 00 01 24 01
+        scd apdu 00 44 00 00
+        /echo Card has been reset to factory defaults
+
+      but tries to find out something about the card first.
+   */
+
+  err = agent_scd_learn (&info);
+  if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE
+      && gpg_err_source (err) == GPG_ERR_SOURCE_SCD)
+    termstate = 1;
+  else if (err)
+    {
+      log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err));
+      return;
+    }
+
+  if (!termstate)
+    {
+      log_info (_("OpenPGP card no. %s detected\n"),
+                info.serialno? info.serialno : "[none]");
+      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"));
+          goto leave;
+        }
+
+      tty_printf ("\n");
+      log_info (_("Note: This command destroys all keys stored on the card!\n"));
+      tty_printf ("\n");
+      if (!cpr_get_answer_is_yes ("cardedit.factory-reset.proceed",
+                                  _("Continue? (y/N) ")))
+        goto leave;
+
+
+      answer = cpr_get ("cardedit.factory-reset.really",
+                        _("Really do a factory reset? (enter \"yes\") "));
+      cpr_kill_prompt ();
+      trim_spaces (answer);
+      if (strcmp (answer, "yes"))
+        goto leave;
+
+      /* 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. */
+      for (i=0; i < 4; i++)
+        send_apdu ("00200081084040404040404040", "VERIFY", 0xffff);
+      for (i=0; i < 4; i++)
+        send_apdu ("00200083084040404040404040", "VERIFY", 0xffff);
+
+      /* Send terminate datafile command.  */
+      err = send_apdu ("00e60000", "TERMINATE DF", 0x6985);
+      if (err)
+        goto leave;
+    }
+
+  /* The card is in termination state - reset and select again.  */
+  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. (no error checking here). */
+  send_apdu ("00A4040006D27600012401", "SELECT AID", 0xffff);
+
+  /* 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)
+    goto leave;
+
+ leave:
+  xfree (answer);
+  agent_release_card_info (&info);
+}
+
+
 \f
 /* Data used by the command parser.  This needs to be outside of the
    function scope to allow readline based command completion.  */
@@ -1644,7 +1807,7 @@ enum cmdids
     cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdDEBUG, cmdVERIFY,
     cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSEX, cmdCAFPR,
     cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT,
-    cmdREADCERT, cmdUNBLOCK,
+    cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET,
     cmdINVCMD
   };
 
@@ -1676,6 +1839,7 @@ static struct
     { "passwd"  , cmdPASSWD, 0, N_("menu to change or unblock the PIN")},
     { "verify"  , cmdVERIFY, 0, N_("verify the PIN and list all data")},
     { "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code") },
+    { "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")},
     /* Note, that we do not announce these command yet. */
     { "privatedo", cmdPRIVATEDO, 0, NULL },
     { "readcert", cmdREADCERT, 0, NULL },
@@ -1848,7 +2012,7 @@ card_edit (ctrl_t ctrl, strlist_t commands)
           for (i=0; cmds[i].name; i++ )
             if(cmds[i].desc
               && (!cmds[i].admin_only || (cmds[i].admin_only && allow_admin)))
-              tty_printf("%-10s %s\n", cmds[i].name, _(cmds[i].desc) );
+              tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
           break;
 
        case cmdADMIN:
@@ -1953,6 +2117,10 @@ card_edit (ctrl_t ctrl, strlist_t commands)
           change_pin (1, allow_admin);
           break;
 
+        case cmdFACTORYRESET:
+          factory_reset ();
+          break;
+
         case cmdQUIT:
           goto leave;
 
index 2e518b1..7e30f76 100644 (file)
@@ -53,7 +53,7 @@ enum {
   SW_CLA_NOT_SUP    = 0x6e00,
   SW_SUCCESS        = 0x9000,
 
-  /* The follwoing statuswords are no real ones but used to map host
+  /* The following statuswords are no real ones but used to map host
      OS errors into status words.  A status word is 16 bit so that
      those values can't be issued by a card. */
   SW_HOST_OUT_OF_CORE = 0x10001,  /* No way yet to differentiate
index ac290c9..daf0310 100644 (file)
@@ -1073,10 +1073,10 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name)
     }
   if (table[idx].special == -2)
     {
-      char tmp[100];
+      char tmp[110];
 
       snprintf (tmp, sizeof tmp,
-                "gc=%d ki=%d fc=%d pd=%d mcl3=%u aac=%d sm=%d",
+                "gc=%d ki=%d fc=%d pd=%d mcl3=%u aac=%d sm=%d si=%u",
                 app->app_local->extcap.get_challenge,
                 app->app_local->extcap.key_import,
                 app->app_local->extcap.change_force_chv,
@@ -1085,7 +1085,8 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name)
                 app->app_local->extcap.algo_attr_change,
                 (app->app_local->extcap.sm_supported
                  ? (app->app_local->extcap.sm_aes128? 7 : 2)
-                 : 0));
+                 : 0),
+                app->app_local->status_indicator);
       send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
       return 0;
     }
index 1694ea1..5fa06b0 100644 (file)
--- a/scd/app.c
+++ b/scd/app.c
@@ -389,7 +389,7 @@ select_application (ctrl_t ctrl, int slot, const char *name, app_t *r_app)
     err = app_select_dinsig (app);
   if (err && is_app_allowed ("sc-hsm") && (!name || !strcmp (name, "sc-hsm")))
     err = app_select_sc_hsm (app);
-  if (err && name)
+  if (err && name && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE)
     err = gpg_error (GPG_ERR_NOT_SUPPORTED);
 
  leave:
index f1dbcff..3c43a4c 100644 (file)
@@ -64,7 +64,7 @@ map_sw (int sw)
   switch (sw)
     {
     case SW_EEPROM_FAILURE: ec = GPG_ERR_HARDWARE; break;
-    case SW_TERM_STATE:     ec = GPG_ERR_CARD; break;
+    case SW_TERM_STATE:     ec = GPG_ERR_OBJ_TERM_STATE; break;
     case SW_WRONG_LENGTH:   ec = GPG_ERR_INV_VALUE; break;
     case SW_SM_NOT_SUP:     ec = GPG_ERR_NOT_SUPPORTED; break;
     case SW_CC_NOT_SUP:     ec = GPG_ERR_NOT_SUPPORTED; break;
index 763ce2d..7c786c2 100644 (file)
@@ -344,7 +344,7 @@ set_debug (const char *level)
   gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
 
   if (opt.debug)
-    log_info ("enabled debug flags:%s%s%s%s%s%s%s%s%s\n",
+    log_info ("enabled debug flags:%s%s%s%s%s%s%s%s%s%s\n",
               (opt.debug & DBG_COMMAND_VALUE)? " command":"",
               (opt.debug & DBG_MPI_VALUE    )? " mpi":"",
               (opt.debug & DBG_CRYPTO_VALUE )? " crypto":"",
@@ -353,7 +353,8 @@ set_debug (const char *level)
               (opt.debug & DBG_MEMSTAT_VALUE)? " memstat":"",
               (opt.debug & DBG_HASHING_VALUE)? " hashing":"",
               (opt.debug & DBG_ASSUAN_VALUE )? " assuan":"",
-              (opt.debug & DBG_CARD_IO_VALUE)? " cardio":"");
+              (opt.debug & DBG_CARD_IO_VALUE)? " cardio":"",
+              (opt.debug & DBG_READER_VALUE )? " reader":"");
 }