* plaintext.c (handle_plaintext): Accept 'u' as a plaintext mode that
[gnupg.git] / g10 / cardglue.c
index 358eb90..55729bf 100644 (file)
  */
 
 #include <config.h>
-#ifdef ENABLE_CARD_SUPPORT
-/* 
-   Note, that most card related code has been taken from 1.9.x branch
-   and is maintained over there if at all possible.  Thus, if you make
-   changes here, please check that a similar change has been commited
-   to the 1.9.x branch.
-*/
+#ifndef ENABLE_CARD_SUPPORT
+#error  not configured for card support.
+#endif
 #include <stdio.h>
 #include <stdlib.h>
 #include <string.h>
@@ -40,6 +36,7 @@
 #include "util.h"
 #include "main.h"
 #include "status.h"
+#include "ttyio.h"
 #include "i18n.h"
 
 #include "cardglue.h"
@@ -188,6 +185,13 @@ app_set_default_reader_port (const char *portstr)
 }
 
 
+void
+card_set_reader_port (const char *portstr)
+{
+  app_set_default_reader_port (portstr);
+}
+
+
 /* Retrieve the serial number and the time of the last update of the
    card.  The serial number is returned as a malloced string (hex
    encoded) in SERIAL and the time of update is returned in STAMP.  If
@@ -246,7 +250,9 @@ open_card (void)
   int rc;
   APP app;
 
-  current_app = NULL;/* FIXME: Release it first.*/
+  card_close ();
+
+ retry:
   slot = apdu_open_reader (default_reader_port);
   if (slot == -1)
     {
@@ -257,19 +263,107 @@ open_card (void)
   app = xcalloc (1, sizeof *app);
   app->slot = slot;
   rc = app_select_openpgp (app, &app->serialno, &app->serialnolen);
+  if (rc && !opt.batch)
+    {
+      write_status_text (STATUS_CARDCTRL, "1");
+      
+      if ( cpr_get_answer_okay_cancel ("cardctrl.insert_card.okay",
+           _("Please insert the card and hit return or enter 'c' to cancel: "),
+                                       1) )
+        {
+          apdu_close_reader (slot);
+          xfree (app);
+          goto retry;
+        }
+    }
   if (rc)
     {
-/*        apdu_close_reader (slot); */
       log_info ("selecting openpgp failed: %s\n", gpg_strerror (rc));
+      apdu_close_reader (slot);
       xfree (app);
       return NULL;
     }
 
   app->initialized = 1;
   current_app = app;
+  if (is_status_enabled () )
+    {
+      int i;
+      char *p, *buf;
+
+      buf = xmalloc (5 + app->serialnolen * 2 + 1);
+      p = stpcpy (buf, "3 ");
+      for (i=0; i < app->serialnolen; p +=2, i++)
+        sprintf (p, "%02X", app->serialno[i]);
+      write_status_text (STATUS_CARDCTRL, buf);
+      xfree (buf);
+    }
+
   return app;
 }
 
+void
+card_close (void)
+{
+  if (current_app)
+    {
+      APP app = current_app;
+      current_app = NULL;
+
+      apdu_close_reader (app->slot);
+      xfree (app);
+    }
+}
+
+
+/* Check that the serial number of the current card (as described by
+   APP) matches SERIALNO.  If there is no match and we are not in
+   batch mode, present a prompt to insert the desired card.  The
+   function return 0 is the present card is okay, -1 if the user
+   selected to insert a new card or an error value.  Note that the
+   card context will be closed in all cases except for 0 as return
+   value. */
+static int
+check_card_serialno (APP app, const char *serialno)
+{
+  const char *s;
+  int ask = 0;
+  int n;
+  
+  for (s = serialno, n=0; *s != '/' && hexdigitp (s); s++, n++)
+    ;
+  if (n != 32)
+    {
+      log_error ("invalid serial number in keyring detected\n");
+      return gpg_error (GPG_ERR_INV_ID);
+    }
+  if (app->serialnolen != 16)
+    ask = 1;
+  for (s = serialno, n=0; !ask && n < 16; s += 2, n++)
+    if (app->serialno[n] != xtoi_2 (s))
+      ask = 1;
+  if (ask)
+    {
+      char buf[5+32+1];
+
+      card_close ();
+      tty_printf (_("Please remove the current card and "
+                    "insert the one with the serial number:\n"
+                    "   %.*s\n"), 32, serialno);
+
+      sprintf (buf, "1 %.32s", serialno);
+      write_status_text (STATUS_CARDCTRL, buf);
+
+      if ( cpr_get_answer_okay_cancel ("cardctrl.change_card.okay",
+                          _("Hit return when ready "
+                            "or enter 'c' to cancel: "),
+                                       1) )
+        return -1;
+      return gpg_error (GPG_ERR_INV_ID);
+    }
+  return 0;
+}
+
 
 
 /* Return a new malloced string by unescaping the string S.  Escaping
@@ -357,14 +451,17 @@ learn_status_cb (void *opaque, const char *line)
 
   if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
     {
+      xfree (parm->serialno);
       parm->serialno = store_serialno (line);
     }
   else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen))
     {
+      xfree (parm->disp_name);
       parm->disp_name = unescape_status_string (line);
     }
   else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen))
     {
+      xfree (parm->disp_lang);
       parm->disp_lang = unescape_status_string (line);
     }
   else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen))
@@ -373,10 +470,12 @@ learn_status_cb (void *opaque, const char *line)
     }
   else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen))
     {
+      xfree (parm->pubkey_url);
       parm->pubkey_url = unescape_status_string (line);
     }
   else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen))
     {
+      xfree (parm->login_data);
       parm->login_data = unescape_status_string (line);
     }
   else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen))
@@ -393,14 +492,14 @@ learn_status_cb (void *opaque, const char *line)
           while (spacep (p))
             p++;
           parm->chv1_cached = atoi (p);
-          while (!spacep (p))
+          while (*p && !spacep (p))
             p++;
           while (spacep (p))
             p++;
           for (i=0; *p && i < 3; i++)
             {
               parm->chvmaxlen[i] = atoi (p);
-              while (!spacep (p))
+              while (*p && !spacep (p))
                 p++;
               while (spacep (p))
                 p++;
@@ -408,7 +507,7 @@ learn_status_cb (void *opaque, const char *line)
           for (i=0; *p && i < 3; i++)
             {
               parm->chvretry[i] = atoi (p);
-              while (!spacep (p))
+              while (*p && !spacep (p))
                 p++;
               while (spacep (p))
                 p++;
@@ -419,7 +518,7 @@ learn_status_cb (void *opaque, const char *line)
   else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen))
     {
       int no = atoi (line);
-      while (!spacep (line))
+      while (* line && !spacep (line))
         line++;
       while (spacep (line))
         line++;
@@ -449,6 +548,7 @@ agent_learn (struct agent_card_info_s *info)
   if (!app)
     return gpg_error (GPG_ERR_CARD);
 
+  memset (info, 0, sizeof *info);
   memset (&ctrl, 0, sizeof ctrl);
   ctrl.status_cb = learn_status_cb;
   ctrl.status_cb_arg = info;
@@ -464,16 +564,40 @@ agent_learn (struct agent_card_info_s *info)
   return rc;
 }
 
+/* Get an attribite from the card. Make sure info is initialized. */
+int 
+agent_scd_getattr (const char *name, struct agent_card_info_s *info)
+{
+  APP app;
+  struct ctrl_ctx_s ctrl;
+
+  app = current_app? current_app : open_card ();
+  if (!app)
+    return gpg_error (GPG_ERR_CARD);
+
+  ctrl.status_cb = learn_status_cb;
+  ctrl.status_cb_arg = info;
+  return app->fnc.getattr (app, &ctrl, name);
+}
+
+
+
 static int 
 pin_cb (void *opaque, const char *info, char **retstr)
 {
   char *value;
   int canceled;
+  int isadmin = (info && strstr (info, "dmin"));
+
 
   *retstr = NULL;
   log_debug ("asking for PIN '%s'\n", info);
 
-  value = ask_passphrase (info, "Enter PIN: ", &canceled);
+  value = ask_passphrase (info, 
+                          isadmin? "passphrase.adminpin.ask"
+                                 : "passphrase.pin.ask", 
+                          isadmin?  _("Enter Admin PIN: ") : _("Enter PIN: "),
+                          &canceled);
   if (!value && canceled)
     return -1;
   else if (!value)
@@ -499,29 +623,103 @@ agent_scd_setattr (const char *name,
   return app->fnc.setattr (app, name, pin_cb, NULL, value, valuelen);
 }
 
+
+static int
+genkey_status_cb (void *opaque, const char *line)
+{
+  struct agent_card_genkey_s *parm = opaque;
+  const char *keyword = line;
+  int keywordlen;
+
+  log_debug ("got status line `%s'\n", line);
+  for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
+    ;
+  while (spacep (line))
+    line++;
+
+  if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen))
+    {
+      parm->fprvalid = unhexify_fpr (line, parm->fpr);
+    }
+  if (keywordlen == 8 && !memcmp (keyword, "KEY-DATA", keywordlen))
+    {
+      MPI a;
+      const char *name = line;
+      char *buf;
+
+      while (*line && !spacep (line))
+        line++;
+      while (spacep (line))
+        line++;
+
+      buf = xmalloc ( 2 + strlen (line) + 1);
+      strcpy (stpcpy (buf, "0x"), line);
+      a = mpi_alloc (300);
+      if( mpi_fromstr (a, buf) )
+        log_error ("error parsing received key data\n");
+      else if (*name == 'n' && spacep (name+1))
+        parm->n = a;
+      else if (*name == 'e' && spacep (name+1))
+        parm->e = a;
+      else
+        {
+          log_info ("unknown parameter name in received key data\n");
+          mpi_free (a);
+        }
+      xfree (buf);
+    }
+  else if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen))
+    {
+      parm->created_at = (u32)strtoul (line, NULL, 10);
+    }
+
+  return 0;
+}
+
 /* Send a GENKEY command to the SCdaemon. */
 int 
 agent_scd_genkey (struct agent_card_genkey_s *info, int keyno, int force)
 {
+  APP app;
+  char keynostr[20];
+  struct ctrl_ctx_s ctrl;
+
+  app = current_app? current_app : open_card ();
+  if (!app)
+    return gpg_error (GPG_ERR_CARD);
+
+  memset (info, 0, sizeof *info);
+  sprintf (keynostr, "%d", keyno);
+  ctrl.status_cb = genkey_status_cb;
+  ctrl.status_cb_arg = info;
 
-  return gpg_error (GPG_ERR_CARD);
+  return app->fnc.genkey (app, &ctrl, keynostr,
+                           force? 1:0,
+                           pin_cb, NULL);
 }
 
 /* Send a PKSIGN command to the SCdaemon. */
 int 
 agent_scd_pksign (const char *serialno, int hashalgo,
                   const unsigned char *indata, size_t indatalen,
-                  char **r_buf, size_t *r_buflen)
+                  unsigned char **r_buf, size_t *r_buflen)
 {
   APP app;
+  int rc;
 
   *r_buf = NULL;
   *r_buflen = 0;
+ retry:
   app = current_app? current_app : open_card ();
   if (!app)
     return gpg_error (GPG_ERR_CARD);
 
   /* Check that the card's serialnumber is as required.*/
+  rc = check_card_serialno (app, serialno);
+  if (rc == -1)
+    goto retry;
+  if (rc)
+    return rc;
 
   return app->fnc.sign (app, serialno, hashalgo,
                         pin_cb, NULL,
@@ -534,22 +732,63 @@ agent_scd_pksign (const char *serialno, int hashalgo,
 int 
 agent_scd_pkdecrypt (const char *serialno,
                      const unsigned char *indata, size_t indatalen,
-                     char **r_buf, size_t *r_buflen)
+                     unsigned char **r_buf, size_t *r_buflen)
 {
+  APP app;
+  int rc;
 
-  return gpg_error (GPG_ERR_CARD);
+  *r_buf = NULL;
+  *r_buflen = 0;
+ retry:
+  app = current_app? current_app : open_card ();
+  if (!app)
+    return gpg_error (GPG_ERR_CARD);
+
+  /* Check that the card's serialnumber is as required.*/
+  rc = check_card_serialno (app, serialno);
+  if (rc == -1)
+    goto retry;
+  if (rc)
+    return rc;
+
+  return app->fnc.decipher (app, serialno, 
+                            pin_cb, NULL,
+                            indata, indatalen,
+                            r_buf, r_buflen);
 }
 
 /* Change the PIN of an OpenPGP card or reset the retry counter. */
 int 
 agent_scd_change_pin (int chvno)
 {
+  APP app;
+  char chvnostr[20];
+  int reset = 0;
 
-  return gpg_error (GPG_ERR_CARD);
-}
+  reset = (chvno >= 100);
+  chvno %= 100;
+
+  app = current_app? current_app : open_card ();
+  if (!app)
+    return gpg_error (GPG_ERR_CARD);
 
+  sprintf (chvnostr, "%d", chvno);
+  return app->fnc.change_pin (app, NULL, chvnostr, reset,
+                              pin_cb, NULL);
+}
 
+/* Perform a CHECKPIN operation.  SERIALNO should be the seriial
+   number of the card - optioanlly followed by the fingerprint;
+   however the fingerprint is ignored here. */
+int
+agent_scd_checkpin (const char *serialnobuf)
+{
+  APP app;
 
+  app = current_app? current_app : open_card ();
+  if (!app)
+    return gpg_error (GPG_ERR_CARD);
 
-#endif /*ENABLE_CARD_SUPPORT*/
+  return app->fnc.check_pin (app, serialnobuf, pin_cb, NULL);
+}