card: Support reading and writing PIV certificates
authorWerner Koch <wk@gnupg.org>
Thu, 7 Feb 2019 10:05:22 +0000 (11:05 +0100)
committerWerner Koch <wk@gnupg.org>
Thu, 7 Feb 2019 10:05:22 +0000 (11:05 +0100)
* scd/app-piv.c (add_tlv): New.
(put_data): New.
(do_writecert): New.
(do_setattr): Remove usused special mode 0.
* tools/gpg-card-tool.c (cmd_writecert): Allow other cards than
OPENPGP.
(cmd_readcert): Ditto.

Signed-off-by: Werner Koch <wk@gnupg.org>
scd/app-piv.c
tools/gpg-card-tool.c

index cfc4a27..59f2725 100644 (file)
@@ -426,6 +426,157 @@ dump_all_do (int slot)
 }
 
 
+/* Create a TLV tag and value and store it at BUFFER.  Return the
+ * length of tag and length.  A LENGTH greater than 65535 is
+ * truncated.  TAG must be less or equal to 2^16.  If BUFFER is NULL,
+ * only the required length is computed.  */
+static size_t
+add_tlv (unsigned char *buffer, unsigned int tag, size_t length)
+{
+  if (length > 0xffff)
+    length = 0xffff;
+
+  if (buffer)
+    {
+      unsigned char *p = buffer;
+
+      if (tag > 0xff)
+        *p++ = tag >> 8;
+      *p++ = tag;
+      if (length < 128)
+        *p++ = length;
+      else if (length < 256)
+        {
+          *p++ = 0x81;
+          *p++ = length;
+        }
+      else
+        {
+          *p++ = 0x82;
+          *p++ = length >> 8;
+          *p++ = length;
+        }
+
+      return p - buffer;
+    }
+  else
+    {
+      size_t n = 0;
+
+      if (tag > 0xff)
+        n++;
+      n++;
+      if (length < 128)
+        n++;
+      else if (length < 256)
+        n += 2;
+      else
+        n += 3;
+      return n;
+    }
+}
+
+
+/* Wrapper around iso7816_put_data_odd which also sets the tag into
+ * the '5C' data object.  The varargs are tuples of (int,size_t,void)
+ * with the tag, the length and the actual data.  A (0,0,NULL) tuple
+ * terminates the list.  Up to 10 tuples are supported.  */
+static gpg_error_t
+put_data (int slot, unsigned int tag, ...)
+{
+  gpg_error_t err;
+  va_list arg_ptr;
+  struct {
+    int tag;
+    size_t len;
+    const void *data;
+  } argv[10];
+  int i, argc;
+  unsigned char data5c[5];
+  size_t data5clen;
+  unsigned char *data = NULL;
+  size_t datalen;
+  unsigned char *p;
+  size_t n;
+
+  /* Collect all args.  Check that length is <= 2^16 to match the
+   * behaviour of add_tlv.  */
+  va_start (arg_ptr, tag);
+  argc = 0;
+  while (((argv[argc].tag = va_arg (arg_ptr, int))))
+    {
+      argv[argc].len = va_arg (arg_ptr, size_t);
+      argv[argc].data = va_arg (arg_ptr, const void *);
+      if (argc >= DIM (argv)-1 || argv[argc].len > 0xffff)
+        {
+          va_end (arg_ptr);
+          return GPG_ERR_EINVAL;
+        }
+      argc++;
+    }
+  va_end (arg_ptr);
+
+  /* Build the TLV with the tag to be updated.  */
+  data5c[0] = 0x5c; /* Tag list */
+  if (tag <= 0xff)
+    {
+      data5c[1] = 1;
+      data5c[2] = tag;
+      data5clen = 3;
+    }
+  else if (tag <= 0xffff)
+    {
+      data5c[1] = 2;
+      data5c[2] = (tag >> 8);
+      data5c[3] = tag;
+      data5clen = 4;
+    }
+  else
+    {
+      data5c[1] = 3;
+      data5c[2] = (tag >> 16);
+      data5c[3] = (tag >> 8);
+      data5c[4] = tag;
+      data5clen = 5;
+    }
+
+  /* Compute the required buffer length and allocate the buffer.  */
+  n = 0;
+  for (i=0; i < argc; i++)
+    {
+      n += add_tlv (NULL, argv[i].tag, argv[i].len);
+      n += argv[i].len;
+    }
+  datalen = data5clen + add_tlv (NULL, 0x53, n) + n;
+  data = xtrymalloc (datalen);
+  if (!data)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  /* Copy that data to the buffer.  */
+  p = data;
+  memcpy (p, data5c, data5clen);
+  p += data5clen;
+  p += add_tlv (p, 0x53, n);
+  for (i=0; i < argc; i++)
+    {
+      p += add_tlv (p, argv[i].tag, argv[i].len);
+      memcpy (p, argv[i].data, argv[i].len);
+      p += argv[i].len;
+    }
+  log_assert ( data + datalen == p );
+  log_printhex (data, datalen, "Put data");
+  err = iso7816_put_data_odd (slot, -1 /* use command chaining */,
+                              0x3fff, data, datalen);
+
+ leave:
+  xfree (data);
+  return err;
+}
+
+
 /* Parse the key reference KEYREFSTR which is expected to hold a key
  * reference for a CHV object.  Return the one octet keyref or -1 for
  * an invalid reference.  */
@@ -802,13 +953,6 @@ do_setattr (app_t app, const char *name,
 
   switch (table[idx].special)
     {
-    case 0:
-      err = iso7816_put_data (app->slot, 0, table[idx].tag, value, valuelen);
-      if (err)
-        log_error ("failed to set '%s': %s\n",
-                   table[idx].name, gpg_strerror (err));
-      break;
-
     case 1:
       err = auth_adm_key (app, value, valuelen);
       break;
@@ -2062,6 +2206,45 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype,
 }
 
 
+/* Write the certificate (CERT,CERTLEN) to the card at CERTREFSTR.
+ * CERTREFSTR is either the OID of the certificate's container data
+ * object or of the form "PIV.<two_hexdigit_keyref>". */
+static gpg_error_t
+do_writecert (app_t app, ctrl_t ctrl,
+              const char *certrefstr,
+              gpg_error_t (*pincb)(void*, const char *, char **),
+              void *pincb_arg,
+              const unsigned char *cert, size_t certlen)
+{
+  gpg_error_t err;
+  data_object_t dobj;
+
+  (void)ctrl;
+  (void)pincb;     /* Not used; instead authentication is needed.  */
+  (void)pincb_arg;
+
+  dobj = find_dobj_by_keyref (app, certrefstr);
+  if (!dobj || !*dobj->keyref)
+    return gpg_error (GPG_ERR_INV_ID);
+
+  /* FIXME: Check that the authentication has already been done.  */
+
+  flush_cached_data (app, dobj->tag);
+
+  err = put_data (app->slot, dobj->tag,
+                  (int)0x70, (size_t)certlen, cert,/* Certificate */
+                  (int)0x71, (size_t)1,       "",  /* No compress */
+                  (int)0xfe, (size_t)0,       "",  /* Empty LRC. */
+                  (int)0,    (size_t)0,       NULL);
+  if (err)
+    log_error ("piv: failed to write cert to %s: %s\n",
+               dobj->keyref, gpg_strerror (err));
+
+
+  return err;
+}
+
+
 /* Select the PIV application on the card in SLOT.  This function must
  * be used before any other PIV application functions. */
 gpg_error_t
@@ -2152,7 +2335,7 @@ app_select_piv (app_t app)
   app->fnc.readkey = do_readkey;
   app->fnc.getattr = do_getattr;
   app->fnc.setattr = do_setattr;
-  /* app->fnc.writecert = do_writecert; */
+  app->fnc.writecert = do_writecert;
   /* app->fnc.writekey = do_writekey; */
   app->fnc.genkey = do_genkey;
   /* app->fnc.sign = do_sign; */
index 08248f7..9170132 100644 (file)
@@ -1551,36 +1551,41 @@ cmd_writecert (card_info_t info, char *argstr)
 {
   gpg_error_t err;
   int opt_clear;
-  int do_no;
+  char *certref_buffer = NULL;
+  char *certref;
   char *data = NULL;
   size_t datalen;
 
   if (!info)
     return print_help
-      ("WRITECERT [--clear] 3 < FILE\n\n"
+      ("WRITECERT [--clear] CERTREF < FILE\n\n"
        "Write a certificate for key 3.  Unless --clear is given\n"
-       "the file argement is mandatory.  The option --clear removes\n"
+       "the file argument is mandatory.  The option --clear removes\n"
        "the certificate from the card.",
-       APP_TYPE_OPENPGP, 0);
+       APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
 
   opt_clear = has_leading_option (argstr, "--clear");
   argstr = skip_options (argstr);
 
-  if (digitp (argstr))
+  certref = argstr;
+  if ((argstr = strchr (certref, ' ')))
     {
-      do_no = atoi (argstr);
-      while (digitp (argstr))
-        argstr++;
-      while (spacep (argstr))
-        argstr++;
+      *argstr++ = 0;
+      trim_spaces (certref);
+      trim_spaces (argstr);
     }
-  else
-    do_no = 0;
+  else /* Let argstr point to an empty string.  */
+    argstr = certref + strlen (certref);
 
-  if (do_no != 3)
+  if (info->apptype == APP_TYPE_OPENPGP)
     {
-      err = gpg_error (GPG_ERR_INV_ARG);
-      goto leave;
+      if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3"))
+        {
+          err = gpg_error (GPG_ERR_INV_ID);
+          log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n");
+          goto leave;
+        }
+      certref = certref_buffer = xstrdup ("OPENPGP.3");
     }
 
   if (opt_clear)
@@ -1602,10 +1607,11 @@ cmd_writecert (card_info_t info, char *argstr)
       goto leave;
     }
 
-  err = scd_writecert ("OPENPGP.3", data, datalen);
+  err = scd_writecert (certref, data, datalen);
 
  leave:
   xfree (data);
+  xfree (certref_buffer);
   return err;
 }
 
@@ -1614,37 +1620,42 @@ static gpg_error_t
 cmd_readcert (card_info_t info, char *argstr)
 {
   gpg_error_t err;
-  int do_no;
+  char *certref_buffer = NULL;
+  char *certref;
   void *data = NULL;
   size_t datalen;
   const char *fname;
 
   if (!info)
     return print_help
-      ("READCERT 3 > FILE\n\n"
+      ("READCERT CERTREF > FILE\n\n"
        "Read the certificate for key 3 and store it in FILE.",
-       APP_TYPE_OPENPGP, 0);
+       APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
 
   argstr = skip_options (argstr);
 
-  if (digitp (argstr))
+  certref = argstr;
+  if ((argstr = strchr (certref, ' ')))
     {
-      do_no = atoi (argstr);
-      while (digitp (argstr))
-        argstr++;
-      while (spacep (argstr))
-        argstr++;
+      *argstr++ = 0;
+      trim_spaces (certref);
+      trim_spaces (argstr);
     }
-  else
-    do_no = 0;
+  else /* Let argstr point to an empty string.  */
+    argstr = certref + strlen (certref);
 
-  if (do_no != 3)
+  if (info->apptype == APP_TYPE_OPENPGP)
     {
-      err = gpg_error (GPG_ERR_INV_ARG);
-      goto leave;
+      if (ascii_strcasecmp (certref, "OPENPGP.3") && strcmp (certref, "3"))
+        {
+          err = gpg_error (GPG_ERR_INV_ID);
+          log_error ("Error: CERTREF must be \"3\" or \"OPENPGP.3\"\n");
+          goto leave;
+        }
+      certref = certref_buffer = xstrdup ("OPENPGP.3");
     }
 
-  if (*argstr == '>')  /* Read it from a file */
+  if (*argstr == '>')  /* Write it to a file */
     {
       for (argstr++; spacep (argstr); argstr++)
         ;
@@ -1656,7 +1667,7 @@ cmd_readcert (card_info_t info, char *argstr)
       goto leave;
     }
 
-  err = scd_readcert ("OPENPGP.3", &data, &datalen);
+  err = scd_readcert (certref, &data, &datalen);
   if (err)
     goto leave;
 
@@ -1664,6 +1675,7 @@ cmd_readcert (card_info_t info, char *argstr)
 
  leave:
   xfree (data);
+  xfree (certref_buffer);
   return err;
 }