scd: Add very basic support for PIV cards.
authorWerner Koch <wk@gnupg.org>
Sun, 20 Jan 2019 10:45:57 +0000 (11:45 +0100)
committerWerner Koch <wk@gnupg.org>
Sun, 20 Jan 2019 10:45:57 +0000 (11:45 +0100)
* scd/app-piv.c: New.
* scd/Makefile.am (card_apps): Add app-piv.c
* scd/app.c (app_new_register): Try to get a Yubikey serial number.
Detect the PIV application.
(get_supported_applications): Add "piv".
--

Right now this allows the use of the authentication key (9A) for SSH
authentication.  More support will follow soon.

Tested with Yubikey-5.

scd/Makefile.am
scd/app-common.h
scd/app-piv.c [new file with mode: 0644]
scd/app.c

index cbd1f9f..0cc50dc 100644 (file)
@@ -33,7 +33,8 @@ AM_CFLAGS =  $(LIBGCRYPT_CFLAGS) \
             $(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS)
 
 
-card_apps = app-openpgp.c app-nks.c app-dinsig.c app-p15.c app-geldkarte.c app-sc-hsm.c
+card_apps = app-openpgp.c app-piv.c app-nks.c app-dinsig.c app-p15.c \
+           app-geldkarte.c app-sc-hsm.c
 
 scdaemon_SOURCES = \
        scdaemon.c scdaemon.h \
index 38e6cc6..ff58318 100644 (file)
@@ -206,5 +206,8 @@ gpg_error_t app_select_geldkarte (app_t app);
 /*-- app-sc-hsm.c --*/
 gpg_error_t app_select_sc_hsm (app_t app);
 
+/*-- app-piv.c --*/
+gpg_error_t app_select_piv (app_t app);
+
 
 #endif /*GNUPG_SCD_APP_COMMON_H*/
diff --git a/scd/app-piv.c b/scd/app-piv.c
new file mode 100644 (file)
index 0000000..9b40477
--- /dev/null
@@ -0,0 +1,1238 @@
+/* app-piv.c - The OpenPGP card application.
+ * Copyright (C) 2019 g10 Code GmbH
+ *
+ * This file is part of GnuPG.
+ *
+ * GnuPG is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * GnuPG is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
+ */
+
+/* Some notes:
+ * - Specs for PIV are at http://dx.doi.org/10.6028/NIST.SP.800-73-4
+ *
+ */
+
+#include <config.h>
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <string.h>
+#include <time.h>
+
+#include "scdaemon.h"
+
+#include "../common/util.h"
+#include "../common/i18n.h"
+#include "iso7816.h"
+#include "app-common.h"
+#include "../common/tlv.h"
+#include "../common/host2net.h"
+#include "apdu.h" /* We use apdu_send_direct.  */
+
+#define PIV_ALGORITHM_3DES_ECB_0 0x00
+#define PIV_ALGORITHM_2DES_ECB   0x01
+#define PIV_ALGORITHM_2DES_CBC   0x02
+#define PIV_ALGORITHM_3DES_ECB   0x03
+#define PIV_ALGORITHM_3DES_CBC   0x04
+#define PIV_ALGORITHM_RSA        0x07
+#define PIV_ALGORITHM_AES128_ECB 0x08
+#define PIV_ALGORITHM_AES128_CBC 0x09
+#define PIV_ALGORITHM_AES192_ECB 0x0A
+#define PIV_ALGORITHM_AES192_CBC 0x0B
+#define PIV_ALGORITHM_AES256_ECB 0x0C
+#define PIV_ALGORITHM_AES256_CBC 0x0D
+#define PIV_ALGORITHM_ECC_P256   0x11
+#define PIV_ALGORITHM_ECC_P384   0x14
+
+
+
+/* A table describing the DOs of a PIV card.  */
+struct data_object_s
+{
+  unsigned int tag;
+  unsigned int mandatory:1;
+  unsigned int acr_contact:2;     /* 0=always, 1=VCI, 2=PIN, 3=PINorOCC */
+  unsigned int acr_contactless:2; /* 0=always, 1=VCI, 2=VCIandPIN,
+                                                      3=VCIand(PINorOCC) */
+  unsigned int binary:1;          /* Data is not human readable.  */
+  unsigned int dont_cache:1;      /* Data item will not be cached.  */
+  unsigned int flush_on_error:1;  /* Flush cached item on error.  */
+  unsigned int keypair:1;         /* Has a public key for a keypair.  */
+  char keyref[3];                 /* The key reference.  */
+  char *oidsuffix; /* Suffix of the OID, prefix is "2.16.840.1.101.3.7." */
+  char *desc;                     /* Description of the DO.  */
+};
+typedef struct data_object_s *data_object_t;
+static struct data_object_s data_objects[] = {
+  { 0x5FC107, 1, 0,1, 1, 0,0, 0, "",   "1.219.0", "Card Capability Container"},
+  { 0x5FC102, 1, 0,0, 1, 0,0, 0, "",   "2.48.0",  "Cardholder Unique Id" },
+  { 0x5FC105, 1, 0,1, 1, 0,0, 1, "9A", "2.1.1",   "Cert PIV Authentication" },
+  { 0x5FC103, 1, 2,2, 1, 0,0, 0, "",   "2.96.16", "Cardholder Fingerprints" },
+  { 0x5FC106, 1, 0,1, 1, 0,0, 0, "",   "2.144.0", "Security Object" },
+  { 0x5FC108, 1, 2,2, 1, 0,0, 0, "",   "2.96.48", "Cardholder Facial Image" },
+  { 0x5FC101, 1, 0,0, 1, 0,0, 1, "9E", "2.5.0",   "Cert Card Authentication"},
+  { 0x5FC10A, 0, 0,1, 1, 0,0, 1, "9C", "2.1.0",   "Cert Digital Signature" },
+  { 0x5FC10B, 0, 0,1, 1, 0,0, 1, "9D", "2.1.2",   "Cert Key Management" },
+  { 0x5FC109, 0, 3,3, 0, 0,0, 0, "",   "2.48.1",  "Printed Information" },
+  { 0x7E,     0, 0,0, 1, 0,0, 0, "",   "2.96.80", "Discovery Object" },
+  { 0x5FC10C, 0, 0,1, 1, 0,0, 0, "",   "2.96.96", "Key History Object" },
+  { 0x5FC10D, 0, 0,1, 1, 0,0, 0, "82", "2.16.1",  "Retired Cert Key Mgm 1" },
+  { 0x5FC10E, 0, 0,1, 1, 0,0, 0, "83", "2.16.2",  "Retired Cert Key Mgm 2" },
+  { 0x5FC10F, 0, 0,1, 1, 0,0, 0, "84", "2.16.3",  "Retired Cert Key Mgm 3" },
+  { 0x5FC110, 0, 0,1, 1, 0,0, 0, "85", "2.16.4",  "Retired Cert Key Mgm 4" },
+  { 0x5FC111, 0, 0,1, 1, 0,0, 0, "86", "2.16.5",  "Retired Cert Key Mgm 5" },
+  { 0x5FC112, 0, 0,1, 1, 0,0, 0, "87", "2.16.6",  "Retired Cert Key Mgm 6" },
+  { 0x5FC113, 0, 0,1, 1, 0,0, 0, "88", "2.16.7",  "Retired Cert Key Mgm 7" },
+  { 0x5FC114, 0, 0,1, 1, 0,0, 0, "89", "2.16.8",  "Retired Cert Key Mgm 8" },
+  { 0x5FC115, 0, 0,1, 1, 0,0, 0, "8A", "2.16.9",  "Retired Cert Key Mgm 9" },
+  { 0x5FC116, 0, 0,1, 1, 0,0, 0, "8B", "2.16.10", "Retired Cert Key Mgm 10" },
+  { 0x5FC117, 0, 0,1, 1, 0,0, 0, "8C", "2.16.11", "Retired Cert Key Mgm 11" },
+  { 0x5FC118, 0, 0,1, 1, 0,0, 0, "8D", "2.16.12", "Retired Cert Key Mgm 12" },
+  { 0x5FC119, 0, 0,1, 1, 0,0, 0, "8E", "2.16.13", "Retired Cert Key Mgm 13" },
+  { 0x5FC11A, 0, 0,1, 1, 0,0, 0, "8F", "2.16.14", "Retired Cert Key Mgm 14" },
+  { 0x5FC11B, 0, 0,1, 1, 0,0, 0, "90", "2.16.15", "Retired Cert Key Mgm 15" },
+  { 0x5FC11C, 0, 0,1, 1, 0,0, 0, "91", "2.16.16", "Retired Cert Key Mgm 16" },
+  { 0x5FC11D, 0, 0,1, 1, 0,0, 0, "92", "2.16.17", "Retired Cert Key Mgm 17" },
+  { 0x5FC11E, 0, 0,1, 1, 0,0, 0, "93", "2.16.18", "Retired Cert Key Mgm 18" },
+  { 0x5FC11F, 0, 0,1, 1, 0,0, 0, "94", "2.16.19", "Retired Cert Key Mgm 19" },
+  { 0x5FC120, 0, 0,1, 1, 0,0, 0, "95", "2.16.20", "Retired Cert Key Mgm 20" },
+  { 0x5FC121, 0, 2,2, 1, 0,0, 0, "",   "2.16.21", "Cardholder Iris Images" },
+  { 0x7F61,   0, 0,0, 1, 0,0, 0, "",   "2.16.22", "BIT Group Template" },
+  { 0x5FC122, 0, 0,0, 1, 0,0, 0, "",   "2.16.23", "SM Cert Signer" },
+  { 0x5FC123, 0, 3,3, 1, 0,0, 0, "",   "2.16.24", "Pairing Code Ref Data" },
+  { 0 }
+  /* Other key reference values without a tag:
+   * "00" Global PIN (not cleared by application switching)
+   * "04" PIV Secure Messaging Key
+   * "80" PIV Application PIN
+   * "81" PIN Unblocking Key
+   * "96" Primary Finger OCC
+   * "97" Secondary Finger OCC
+   * "98" Pairing Code
+   * "9B" PIV Card Application Administration Key
+   */
+};
+
+
+/* One cache item for DOs.  */
+struct cache_s {
+  struct cache_s *next;
+  int tag;
+  size_t length;
+  unsigned char data[1];
+};
+
+
+/* Object with application specific data.  */
+struct app_local_s {
+  /* A linked list with cached DOs.  */
+  struct cache_s *cache;
+
+  /* Various flags.  */
+  struct
+  {
+    unsigned int dummy:1;
+  } flags;
+
+};
+
+
+/***** Local prototypes  *****/
+static gpg_error_t get_keygrip_by_tag (app_t app, unsigned int tag,
+                                       char **r_keygripstr);
+
+
+
+
+\f
+/* Deconstructor. */
+static void
+do_deinit (app_t app)
+{
+  if (app && app->app_local)
+    {
+      struct cache_s *c, *c2;
+
+      for (c = app->app_local->cache; c; c = c2)
+        {
+          c2 = c->next;
+          xfree (c);
+        }
+
+      xfree (app->app_local);
+      app->app_local = NULL;
+    }
+}
+
+
+/* Wrapper around iso7816_get_data which first tries to get the data
+ * from the cache.  With GET_IMMEDIATE passed as true, the cache is
+ * bypassed.  The tag-53 container is also removed.  */
+static gpg_error_t
+get_cached_data (app_t app, int tag,
+                 unsigned char **result, size_t *resultlen,
+                 int get_immediate)
+{
+  gpg_error_t err;
+  int i;
+  unsigned char *p;
+  const unsigned char *s;
+  size_t len, n;
+  struct cache_s *c;
+
+  *result = NULL;
+  *resultlen = 0;
+
+  if (!get_immediate)
+    {
+      for (c=app->app_local->cache; c; c = c->next)
+        if (c->tag == tag)
+          {
+            if(c->length)
+              {
+                p = xtrymalloc (c->length);
+                if (!p)
+                  return gpg_error_from_syserror ();
+                memcpy (p, c->data, c->length);
+                *result = p;
+              }
+
+            *resultlen = c->length;
+
+            return 0;
+          }
+    }
+
+  err = iso7816_get_data_odd (app->slot, 0, tag, &p, &len);
+  if (err)
+    return err;
+
+  /* Unless the Discovery Object or the BIT Group Template is
+   * requested, remove the outer container.
+   * (SP800-73.4 Part 2, section 3.1.2)   */
+  if (tag == 0x7E || tag == 0x7F61)
+    ;
+  else if (len && *p == 0x53 && (s = find_tlv (p, len, 0x53, &n)))
+    {
+      memmove (p, s, n);
+      len = n;
+    }
+
+  if (len)
+    *result = p;
+  *resultlen = len;
+
+  /* Check whether we should cache this object. */
+  if (get_immediate)
+    return 0;
+
+  for (i=0; data_objects[i].tag; i++)
+    if (data_objects[i].tag == tag)
+      {
+        if (data_objects[i].dont_cache)
+          return 0;
+        break;
+      }
+
+  /* Okay, cache it. */
+  for (c=app->app_local->cache; c; c = c->next)
+    log_assert (c->tag != tag);
+
+  c = xtrymalloc (sizeof *c + len);
+  if (c)
+    {
+      if (len)
+        memcpy (c->data, p, len);
+      else
+        xfree (p);
+      c->length = len;
+      c->tag = tag;
+      c->next = app->app_local->cache;
+      app->app_local->cache = c;
+    }
+
+  return 0;
+}
+
+
+/* Get the DO identified by TAG from the card in SLOT and return a
+ * buffer with its content in RESULT and NBYTES.  The return value is
+ * NULL if not found or a pointer which must be used to release the
+ * buffer holding value.  */
+static void *
+get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes,
+            int *r_err)
+{
+  gpg_error_t err;
+  int i;
+  unsigned char *buffer;
+  size_t buflen;
+  unsigned char *value;
+  size_t valuelen;
+  gpg_error_t dummyerr;
+
+  if (!r_err)
+    r_err = &dummyerr;
+
+  *result = NULL;
+  *nbytes = 0;
+  *r_err = 0;
+  for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++)
+    ;
+
+  value = NULL;
+  err = gpg_error (GPG_ERR_ENOENT);
+
+  if (!value) /* Not in a constructed DO, try simple. */
+    {
+      err = get_cached_data (app, tag, &buffer, &buflen,
+                             data_objects[i].dont_cache);
+      if (!err)
+        {
+          value = buffer;
+          valuelen = buflen;
+        }
+    }
+
+  if (!err)
+    {
+      *nbytes = valuelen;
+      *result = value;
+      return buffer;
+    }
+
+  *r_err = err;
+  return NULL;
+}
+
+
+static void
+dump_all_do (int slot)
+{
+  gpg_error_t err;
+  int i;
+  unsigned char *buffer;
+  size_t buflen;
+
+  for (i=0; data_objects[i].tag; i++)
+    {
+      /* We don't try extended length APDU because such large DO would
+         be pretty useless in a log file.  */
+      err = iso7816_get_data_odd (slot, 0, data_objects[i].tag,
+                                 &buffer, &buflen);
+      if (err)
+        {
+          if (gpg_err_code (err) == GPG_ERR_ENOENT
+              && !data_objects[i].mandatory)
+            ;
+          else
+            log_info ("DO '%s' not available: %s\n",
+                      data_objects[i].desc, gpg_strerror (err));
+        }
+      else
+        {
+          if (data_objects[i].binary)
+            {
+              log_info ("DO '%s': ", data_objects[i].desc);
+              if (buflen > 16 && opt.verbose < 2)
+                {
+                  log_printhex (buffer, 16, NULL);
+                  log_printf ("[...]\n");
+                }
+              else
+                log_printhex (buffer, buflen, "");
+            }
+          else
+            log_info ("DO '%s': '%.*s'\n",
+                      data_objects[i].desc,
+                      (int)buflen, buffer);
+
+        }
+      xfree (buffer); buffer = NULL;
+    }
+}
+
+
+/* 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
+ * number as a hex string.  May return NULL on malloc problem.  */
+static char *
+get_dispserialno (app_t app, int failmode)
+{
+  char *result;
+
+  if (app->serialno && app->serialnolen == 3+1+4
+      && !memcmp (app->serialno, "\xff\x02\x00", 3))
+    {
+      /* This is a 4 byte S/N of a Yubikey which seems to be printed
+       * on the token in decimal.  Maybe they will print larger S/N
+       * also in decimal but we can't be sure, thus do it only for
+       * these 32 bit numbers.  */
+      unsigned long sn;
+      sn  = app->serialno[4] * 16777216;
+      sn += app->serialno[5] * 65536;
+      sn += app->serialno[6] * 256;
+      sn += app->serialno[7];
+      result = xtryasprintf ("yk-%lu", sn);
+    }
+  else if (failmode)
+    result = NULL;  /* No Abbreviated S/N.  */
+  else
+    result = app_get_serialno (app);
+
+  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
+do_getattr (app_t app, ctrl_t ctrl, const char *name)
+{
+  static struct {
+    const char *name;
+    int tag;
+    int special;
+  } table[] = {
+    { "SERIALNO",     0x0000, -1 },
+    { "$AUTHKEYID",   0x0000, -2 }, /* Default key for ssh.  */
+    { "$DISPSERIALNO",0x0000, -3 }
+  };
+  gpg_error_t err = 0;
+  int idx;
+  void *relptr;
+  unsigned char *value;
+  size_t valuelen;
+
+  for (idx=0; (idx < DIM (table)
+               && ascii_strcasecmp (table[idx].name, name)); idx++)
+    ;
+  if (!(idx < DIM (table)))
+    err = gpg_error (GPG_ERR_INV_NAME);
+  else if (table[idx].special == -1)
+    {
+      char *serial = app_get_serialno (app);
+
+      if (serial)
+        {
+          send_status_direct (ctrl, "SERIALNO", serial);
+          xfree (serial);
+        }
+    }
+  else if (table[idx].special == -2)
+    {
+      char const tmp[] = "PIV.9A"; /* Cert PIV Authenticate.  */
+      send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
+    }
+  else if (table[idx].special == -3)
+    {
+      char *tmp = get_dispserialno (app, 1);
+
+      if (tmp)
+        {
+          send_status_info (ctrl, table[idx].name,
+                            tmp, strlen (tmp),
+                            NULL, (size_t)0);
+          xfree (tmp);
+        }
+      else
+        err = gpg_error (GPG_ERR_INV_NAME);  /* No Abbreviated S/N.  */
+    }
+  else
+    {
+      relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err);
+      if (relptr)
+        {
+          send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0);
+          xfree (relptr);
+        }
+    }
+
+  return err;
+}
+
+
+/* Send the KEYPAIRINFO back.  DOBJ describes the data object carrying
+ * the key.  This is used by the LEARN command. */
+static gpg_error_t
+send_keypair_and_cert_info (app_t app, ctrl_t ctrl, data_object_t dobj,
+                            int only_keypair)
+{
+  gpg_error_t err = 0;
+  char *keygripstr = NULL;
+  char idbuf[50];
+
+  err = get_keygrip_by_tag (app, dobj->tag, &keygripstr);
+  if (err)
+    goto leave;
+
+  snprintf (idbuf, sizeof idbuf, "PIV.%s", dobj->keyref);
+  send_status_info (ctrl, "KEYPAIRINFO",
+                    keygripstr, strlen (keygripstr),
+                    idbuf, strlen (idbuf),
+                    NULL, (size_t)0);
+  if (!only_keypair)
+    {
+      /* All certificates are of type 100 (Regular X.509 Cert).  */
+      send_status_info (ctrl, "CERTINFO",
+                        "100", 3,
+                        idbuf, strlen (idbuf),
+                        NULL, (size_t)0);
+    }
+
+ leave:
+  xfree (keygripstr);
+  return err;
+}
+
+
+/* Handle the LEARN command for OpenPGP.  */
+static gpg_error_t
+do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
+{
+  int i;
+
+  (void)flags;
+
+  for (i=0; data_objects[i].tag; i++)
+    if (data_objects[i].keypair)
+      send_keypair_and_cert_info (app, ctrl, data_objects + i, !!(flags & 1));
+
+  return 0;
+}
+
+
+/* Core of do-readcert which fetches the certificate based on the
+ * given tag and returns it in a freshly allocated buffer stored at
+ * R_CERT and the length of the certificate stored at R_CERTLEN.  */
+static gpg_error_t
+readcert_by_tag (app_t app, unsigned int tag,
+                 unsigned char **r_cert, size_t *r_certlen)
+{
+  gpg_error_t err;
+  unsigned char *buffer;
+  size_t buflen;
+  void *relptr;
+  const unsigned char *s;
+  size_t n;
+
+  *r_cert = NULL;
+  *r_certlen = 0;
+
+  relptr = get_one_do (app, tag, &buffer, &buflen, NULL);
+  if (!relptr || !buflen)
+   {
+      err = gpg_error (GPG_ERR_NOT_FOUND);
+      goto leave;
+    }
+
+  s = find_tlv (buffer, buflen, 0x71, &n);
+  if (!s || n != 1)
+    {
+      log_error ("piv: no or invalid CertInfo in 0x%X\n", tag);
+      err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+      goto leave;
+    }
+  if (*s == 0x01)
+    {
+      log_error ("piv: gzip compression not yet supported (tag 0x%X)\n", tag);
+      err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
+      goto leave;
+    }
+  if (*s)
+    {
+      log_error ("piv: invalid CertInfo 0x%02x in 0x%X\n", *s, tag);
+      err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+      goto leave;
+    }
+
+  /* Note: We don't check that the LRC octet has a length of zero as
+   * required by the specs.  */
+
+  /* Get the cert from the container.  */
+  s = find_tlv (buffer, buflen, 0x70, &n);
+  if (!s || !n)
+    {
+      err = gpg_error (GPG_ERR_NOT_FOUND);
+      goto leave;
+    }
+
+  if (!(*r_cert = xtrymalloc (n)))
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  memcpy (*r_cert, s, n);
+  *r_certlen = n;
+  err = 0;
+
+ leave:
+  xfree (relptr);
+  return err;
+}
+
+
+/* Get the keygrip of a key from the certificate stored at TAG.
+ * Caller must free the string at R_KEYGRIPSTR. */
+static gpg_error_t
+get_keygrip_by_tag (app_t app, unsigned int tag, char **r_keygripstr)
+{
+  gpg_error_t err;
+  unsigned char *certbuf = NULL;
+  size_t certbuflen;
+  ksba_cert_t cert = NULL;
+
+  *r_keygripstr = xtrymalloc (40+1);
+  if (!r_keygripstr)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  /* We need to get the public key from the certificate.  */
+  err = readcert_by_tag (app, tag, &certbuf, &certbuflen);
+  if (err)
+    goto leave;
+
+  /* Compute the keygrip.  */
+  err = ksba_cert_new (&cert);
+  if (err)
+    goto leave;
+  err = ksba_cert_init_from_mem (cert, certbuf, certbuflen);
+  if (err)
+    goto leave;
+  err = app_help_get_keygrip_string (cert, *r_keygripstr);
+
+
+ leave:
+  ksba_cert_release (cert);
+  xfree (certbuf);
+  if (err)
+    {
+      xfree (*r_keygripstr);
+      *r_keygripstr = NULL;
+    }
+  return err;
+}
+
+
+/* Locate the data object from the given KEYREF.  The KEYREF may also
+ * be the corresponding OID of the key object.  Returns the data
+ * object or NULL if not found.  */
+static data_object_t
+find_dobj_by_keyref (app_t app, const char *keyref)
+{
+  int i;
+
+  (void)app;
+
+  if (!ascii_strncasecmp (keyref, "PIV.", 4))
+    {
+      keyref += 4;
+      for (i=0; data_objects[i].tag; i++)
+        if (*data_objects[i].keyref
+            && !ascii_strcasecmp (keyref, data_objects[i].keyref))
+          {
+            return data_objects + i;
+          }
+    }
+  else if (!strncmp (keyref, "2.16.840.1.101.3.7.", 19))
+    {
+      keyref += 19;
+      for (i=0; data_objects[i].tag; i++)
+        if (*data_objects[i].keyref
+            && !strcmp (keyref, data_objects[i].oidsuffix))
+          {
+            return data_objects + i;
+          }
+    }
+
+  return NULL;
+}
+
+
+/* Read a certificate from the card and returned in a freshly
+ * allocated buffer stored at R_CERT and the length of the certificate
+ * stored at R_CERTLEN.  CERTID is either the OID of the cert's
+ * container or of the form "PIV.<two_hexdigit_keyref>"  */
+static gpg_error_t
+do_readcert (app_t app, const char *certid,
+             unsigned char **r_cert, size_t *r_certlen)
+{
+  data_object_t dobj;
+
+  *r_cert = NULL;
+  *r_certlen = 0;
+
+  dobj = find_dobj_by_keyref (app, certid);
+  if (!dobj)
+    return gpg_error (GPG_ERR_INV_ID);
+
+  return readcert_by_tag (app, dobj->tag, r_cert, r_certlen);
+}
+
+
+/* Given a data object DOBJ return the corresponding PIV algorithm and
+ * store it at R_ALGO.  The algorithm is taken from the corresponding
+ * certificate or from a cache.  */
+static gpg_error_t
+get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo)
+{
+  gpg_error_t err;
+  unsigned char *certbuf = NULL;
+  size_t certbuflen;
+  ksba_cert_t cert = NULL;
+  ksba_sexp_t k_pkey = NULL;
+  gcry_sexp_t s_pkey = NULL;
+  gcry_sexp_t l1 = NULL;
+  char *algoname = NULL;
+  int algo;
+  size_t n;
+  const char *curve_name;
+
+  *r_algo = 0;
+
+  err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen);
+  if (err)
+    goto leave;
+
+  err = ksba_cert_new (&cert);
+  if (err)
+    goto leave;
+
+  err = ksba_cert_init_from_mem (cert, certbuf, certbuflen);
+  if (err)
+    {
+      log_error ("piv: failed to parse the certificate %s: %s\n",
+                 dobj->keyref, gpg_strerror (err));
+      goto leave;
+    }
+  xfree (certbuf);
+  certbuf = NULL;
+
+  k_pkey = ksba_cert_get_public_key (cert);
+  if (!k_pkey)
+    {
+      err = gpg_error (GPG_ERR_NO_PUBKEY);
+      goto leave;
+    }
+  n = gcry_sexp_canon_len (k_pkey, 0, NULL, NULL);
+  err = gcry_sexp_new (&s_pkey, k_pkey, n, 0);
+  if (err)
+    goto leave;
+
+  l1 = gcry_sexp_find_token (s_pkey, "public-key", 0);
+  if (!l1)
+    {
+      err = gpg_error (GPG_ERR_NO_PUBKEY);
+      goto leave;
+    }
+
+  {
+    gcry_sexp_t l_tmp = gcry_sexp_cadr (l1);
+    gcry_sexp_release (l1);
+    l1 = l_tmp;
+  }
+  algoname = gcry_sexp_nth_string (l1, 0);
+  if (!algoname)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+  algo = gcry_pk_map_name (algoname);
+  switch (algo)
+    {
+    case GCRY_PK_RSA:
+      algo = PIV_ALGORITHM_RSA;
+      break;
+
+    case GCRY_PK_ECC:
+    case GCRY_PK_ECDSA:
+    case GCRY_PK_ECDH:
+      curve_name = gcry_pk_get_curve (s_pkey, 0, NULL);
+      if (curve_name && !strcmp (curve_name, "NIST P-256"))
+        algo = PIV_ALGORITHM_ECC_P256;
+      else if (curve_name && !strcmp (curve_name, "NIST P-384"))
+        algo = PIV_ALGORITHM_ECC_P384;
+      else
+        {
+          err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
+          log_error ("piv: certificate %s, curve '%s': %s\n",
+                     dobj->keyref, curve_name, gpg_strerror (err));
+          goto leave;
+        }
+      break;
+
+    default:
+      err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+      log_error ("piv: certificate %s, pubkey algo '%s': %s\n",
+                 dobj->keyref, algoname, gpg_strerror (err));
+      goto leave;
+    }
+  *r_algo = algo;
+
+ leave:
+  gcry_free (algoname);
+  gcry_sexp_release (l1);
+  gcry_sexp_release (s_pkey);
+  ksba_free (k_pkey);
+  xfree (certbuf);
+  return err;
+}
+
+
+/* Return an allocated string to be used as prompt.  Returns NULL on
+ * malloc error.  */
+static char *
+make_prompt (app_t app, int remaining, const char *firstline)
+{
+  char *serial, *tmpbuf, *result;
+
+  serial = get_dispserialno (app, 0);
+  if (!serial)
+    return NULL;
+
+  /* TRANSLATORS: Put a \x1f right before a colon.  This can be
+   * used by pinentry to nicely align the names and values.  Keep
+   * the %s at the start and end of the string.  */
+  result = xtryasprintf (_("%s"
+                           "Number\x1f: %s%%0A"
+                           "Holder\x1f: %s"
+                           "%s"),
+                         "\x1e",
+                         serial,
+                         "Unknown", /* Fixme */
+                         "");
+  xfree (serial);
+
+  /* Append a "remaining attempts" info if needed.  */
+  if (remaining != -1 && remaining < 3)
+    {
+      char *rembuf;
+
+      /* TRANSLATORS: This is the number of remaining attempts to
+       * enter a PIN.  Use %%0A (double-percent,0A) for a linefeed. */
+      rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining);
+      if (rembuf)
+        {
+          tmpbuf = strconcat (firstline, "%0A%0A", result,
+                              "%0A%0A", rembuf, NULL);
+          xfree (rembuf);
+        }
+      else
+        tmpbuf = NULL;
+      xfree (result);
+      result = tmpbuf;
+    }
+  else
+    {
+      tmpbuf = strconcat (firstline, "%0A%0A", result, NULL);
+      xfree (result);
+      result = tmpbuf;
+    }
+
+  return result;
+}
+
+
+
+/* Verify the Application PIN for use with data object DOBJ.  */
+static gpg_error_t
+verify_pin (app_t app, data_object_t dobj,
+            gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg)
+{
+  gpg_error_t err;
+  unsigned char apdu[4];
+  unsigned int sw;
+  int remaining;
+  char *prompt;
+  char *pinvalue = NULL;
+  unsigned int pinlen;
+  char pinbuffer[8];
+
+  /* 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;
+  if (!iso7816_apdu_direct (app->slot, apdu, 4, 0, &sw, NULL, NULL))
+    {
+      /* No need to verification.  */
+      return 0;  /* All fine.  */
+    }
+  if ((sw & 0xfff0) == 0x63C0)
+    remaining = (sw & 0x000f); /* PIN has REMAINING tries left.  */
+  else
+    remaining = -1;
+
+  if (remaining != -1)
+    log_debug ("piv: PIN for %s has %d attempts left\n",
+               dobj->keyref, remaining);
+
+  /* Ask for the PIN.  */
+  prompt = make_prompt (app, remaining, _("||Please enter your PIV PIN"));
+  err = pincb (pincb_arg, prompt, &pinvalue);
+  xfree (prompt);
+  prompt = NULL;
+  if (err)
+    {
+      log_info (_("PIN callback returned error: %s\n"), gpg_strerror (err));
+      return err;
+    }
+
+  pinlen = pinvalue? strlen (pinvalue) : 0;
+  if (pinlen < 6)
+    {
+      log_error (_("PIN for is too short;"
+                   " minimum length is %d\n"), 6);
+      if (pinvalue)
+        wipememory (pinvalue, pinlen);
+      xfree (pinvalue);
+      return gpg_error (GPG_ERR_BAD_PIN);
+    }
+  if (pinlen > sizeof pinbuffer)
+    {
+      log_error (_("PIN for is too long;"
+                   " maximum length is %d\n"), (int)sizeof pinbuffer);
+      wipememory (pinvalue, pinlen);
+      xfree (pinvalue);
+      return gpg_error (GPG_ERR_BAD_PIN);
+    }
+  if (strspn (pinvalue, "0123456789") != pinlen)
+    {
+      log_error (_("PIN has invalid characters; only digits are allowed\n"));
+      wipememory (pinvalue, pinlen);
+      xfree (pinvalue);
+      return gpg_error (GPG_ERR_BAD_PIN);
+    }
+  memcpy (pinbuffer, pinvalue, pinlen);
+  memset (pinbuffer + pinlen, 0xff, sizeof(pinbuffer) - pinlen);
+  wipememory (pinvalue, pinlen);
+  xfree (pinvalue);
+
+  err = iso7816_verify (app->slot, 0x80,
+                        pinbuffer, sizeof pinbuffer);
+  wipememory (pinbuffer, sizeof pinbuffer);
+  if (err)
+    log_error ("PIN verification failed: %s\n", gpg_strerror (err));
+
+  return err;
+}
+
+
+/* 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
+ * result is stored at (R_OUTDATA,R_OUTDATALEN); on error (NULL,0) is
+ * stored there and an error code returned.  For ECDSA the result is
+ * the simple concatenation of R and S without any DER encoding.  R
+ * and S are left extended with zeroes to make sure they have an equal
+ * length.
+ */
+static gpg_error_t
+do_auth (app_t app, const char *keyidstr,
+         gpg_error_t (*pincb)(void*, const char *, char **),
+         void *pincb_arg,
+         const void *indata_arg, size_t indatalen,
+         unsigned char **r_outdata, size_t *r_outdatalen)
+{
+  const unsigned char *indata = indata_arg;
+  gpg_error_t err;
+  data_object_t dobj;
+  unsigned char tmpl[2+2+2+128];
+  size_t tmpllen;
+  unsigned char *outdata = NULL;
+  size_t outdatalen;
+  const unsigned char *s;
+  size_t n;
+  int keyref, algo;
+
+  if (!keyidstr || !*keyidstr)
+    {
+      err = gpg_error (GPG_ERR_INV_VALUE);
+      goto leave;
+    }
+
+  /* Fixme: Shall we support the KEYID/FINGERPRINT syntax?  Does it
+   * make sense for X.509 certs?  */
+
+  dobj = find_dobj_by_keyref (app, keyidstr);
+  if (!dobj)
+    {
+      err = gpg_error (GPG_ERR_INV_ID);
+      goto leave;
+    }
+  keyref = xtoi_2 (dobj->keyref);
+
+  err = get_key_algorithm_by_dobj (app, dobj, &algo);
+  if (err)
+    goto leave;
+
+  /* We need to remove the ASN.1 prefix from INDATA.  We use TEMPL as
+   * a temporary buffer for the OID.  */
+  if (algo == PIV_ALGORITHM_ECC_P256)
+    {
+      tmpllen = sizeof tmpl;
+      err = gcry_md_get_asnoid (GCRY_MD_SHA256, &tmpl, &tmpllen);
+      if (err)
+        {
+          err = gpg_error (GPG_ERR_INTERNAL);
+          log_debug ("piv: no OID for hash algo %d\n", GCRY_MD_SHA256);
+          goto leave;
+        }
+      if (indatalen != tmpllen + 32 || memcmp (indata, tmpl, tmpllen))
+        {
+          err = GPG_ERR_INV_VALUE;
+          log_error ("piv: bad formatted input for ECC-P256 auth\n");
+          goto leave;
+        }
+      indata +=tmpllen;
+      indatalen -= tmpllen;
+    }
+  else if (algo == PIV_ALGORITHM_ECC_P384)
+    {
+      tmpllen = sizeof tmpl;
+      err = gcry_md_get_asnoid (GCRY_MD_SHA384, &tmpl, &tmpllen);
+      if (err)
+        {
+          err = gpg_error (GPG_ERR_INTERNAL);
+          log_debug ("piv: no OID for hash algo %d\n", GCRY_MD_SHA384);
+          goto leave;
+        }
+      if (indatalen != tmpllen + 48 || memcmp (indata, tmpl, tmpllen))
+        {
+          err = GPG_ERR_INV_VALUE;
+          log_error ("piv: bad formatted input for ECC-P384 auth\n");
+          goto leave;
+        }
+      indata += tmpllen;
+      indatalen -= tmpllen;
+    }
+  else if (algo == PIV_ALGORITHM_RSA)
+    {
+      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+      log_error ("piv: FIXME: implement RSA authentication\n");
+      goto leave;
+    }
+  else
+    {
+      err = gpg_error (GPG_ERR_INTERNAL);
+      log_debug ("piv: unknown PIV  algo %d from helper function\n", algo);
+      goto leave;
+    }
+
+  /* Because we don't have a dynamic template builder we make sure
+   * that we can encode all lengths in one octet.  FIXME: Use add_tls
+   * from app-openpgp as a base for an strconcat like function. */
+  if (indatalen >= 100)
+    {
+      err = gpg_error (GPG_ERR_TOO_LARGE);
+      goto leave;
+    }
+
+  /* Now verify the PIN.  */
+  err = verify_pin (app, dobj, pincb, pincb_arg);
+  if (err)
+    return err;
+
+  /* Build the Dynamic Authentication Template.  */
+  tmpl[0] = 0x7c;
+  tmpl[1] = indatalen + 4;
+  tmpl[2] = 0x82; /* Response. */
+  tmpl[3] = 0;    /* Must be 0 to get the tag in the answer.  */
+  tmpl[4] = 0x81; /* Challenge. */
+  tmpl[5] = indatalen;
+  memcpy (tmpl+6, indata, indatalen);
+  tmpllen = indatalen + 6;
+
+  /* Note: the -1 requests command chaining.  */
+  err = iso7816_general_authenticate (app->slot, -1,
+                                      algo, keyref,
+                                      tmpl, (int)tmpllen, 0,
+                                      &outdata, &outdatalen);
+  if (err)
+    goto leave;
+
+  /* Parse the response.  */
+  if (outdatalen && *outdata == 0x7c
+      && (s = find_tlv (outdata, outdatalen, 0x82, &n)))
+    {
+      const unsigned char *rval, *sval;
+      size_t rlen, rlenx, slen, slenx, resultlen;
+      char *result;
+      /* The result of an ECDSA signature is
+       *   SEQUENCE { r INTEGER, s INTEGER }
+       * We re-pack that by concatenating R and S and making sure that
+       * both have the same length.  We simplify parsing by using
+       * find_tlv and not a proper DER parser.  */
+      s = find_tlv (s, n, 0x30, &n);
+      if (!s)
+        goto bad_der;
+      rval = find_tlv (s, n, 0x02, &rlen);
+      if (!rval)
+        goto bad_der;
+      log_assert (n >= (rval-s)+rlen);
+      sval = find_tlv (rval+rlen, n-((rval-s)+rlen), 0x02, &slen);
+      if (!rval)
+        goto bad_der;
+      rlenx = slenx = 0;
+      if (rlen > slen)
+        slenx = rlen - slen;
+      else if (slen > rlen)
+        rlenx = slen - rlen;
+
+      resultlen = rlen + rlenx + slen + slenx;
+      result = xtrycalloc (1, resultlen);
+      if (!result)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+      memcpy (result + rlenx, rval, rlen);
+      memcpy (result + rlenx + rlen + slenx, sval, slen);
+      xfree (outdata);
+      outdata = result;
+      outdatalen = resultlen;
+    }
+  else
+    {
+    bad_der:
+      err = gpg_error (GPG_ERR_CARD);
+      log_error ("piv: response does not contain a proper result\n");
+      goto leave;
+    }
+
+ leave:
+  if (err)
+    {
+      xfree (outdata);
+      *r_outdata = NULL;
+      *r_outdatalen = 0;
+    }
+  else
+    {
+      *r_outdata = outdata;
+      *r_outdatalen = outdatalen;
+    }
+  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
+app_select_piv (app_t app)
+{
+  static char const aid[] = { 0xA0, 0x00, 0x00, 0x03, 0x08, /* RID=NIST */
+                              0x00, 0x00, 0x10, 0x00        /* PIX=PIV  */ };
+  int slot = app->slot;
+  gpg_error_t err;
+  unsigned char *apt = NULL;
+  size_t aptlen;
+  const unsigned char *s;
+  size_t n;
+
+  /* Note that we select using the AID without the 2 octet version
+   * number.  This allows for better reporting of future specs.  We
+   * need to use the use-zero-for-P2-flag.  */
+  err = iso7816_select_application_ext (slot, aid, sizeof aid, 0x0001,
+                                        &apt, &aptlen);
+  if (err)
+    goto leave;
+
+  app->apptype = "PIV";
+  app->did_chv1 = 0;
+  app->did_chv2 = 0;
+  app->did_chv3 = 0;
+  app->app_local = NULL;
+
+  /* Check the Application Property Template.  */
+  if (opt.verbose)
+    {
+      /* We  use a separate log_info to avoid the "DBG:" prefix.  */
+      log_info ("piv: APT=");
+      log_printhex (apt, aptlen, "");
+    }
+
+  s = find_tlv (apt, aptlen, 0x4F, &n);
+  if (!s || n != 6 || memcmp (s, aid+5, 4))
+    {
+      /* The PIX does not match.  */
+      log_error ("piv: missing or invalid DO 0x4F in APT\n");
+      err = gpg_error (GPG_ERR_CARD);
+      goto leave;
+    }
+  if (s[4] != 1 || s[5] != 0)
+    {
+      log_error ("piv: unknown PIV version %u.%u\n", s[4], s[5]);
+      err = gpg_error (GPG_ERR_CARD);
+      goto leave;
+    }
+  app->card_version = ((s[4] << 8) | s[5]);
+
+  s = find_tlv (apt, aptlen, 0x79, &n);
+  if (!s || n < 7)
+    {
+      log_error ("piv: missing or invalid DO 0x79 in APT\n");
+      err = gpg_error (GPG_ERR_CARD);
+      goto leave;
+    }
+  s = find_tlv (s, n, 0x4F, &n);
+  if (!s || n != 5 || memcmp (s, aid, 5))
+    {
+      /* The RID does not match.  */
+      log_error ("piv: missing or invalid DO 0x79.4F in APT\n");
+      err = gpg_error (GPG_ERR_CARD);
+      goto leave;
+    }
+
+  app->app_local = xtrycalloc (1, sizeof *app->app_local);
+  if (!app->app_local)
+    {
+      err = gpg_error_from_syserror ();
+      goto leave;
+    }
+
+
+  /* FIXME: Parse the optional and conditional DOs in the APT.  */
+
+  if (opt.verbose)
+    dump_all_do (slot);
+
+  app->fnc.deinit = do_deinit;
+  app->fnc.learn_status = do_learn_status;
+  app->fnc.readcert = do_readcert;
+  app->fnc.readkey = NULL;
+  app->fnc.getattr = do_getattr;
+  /* app->fnc.setattr = do_setattr; */
+  /* app->fnc.writecert = do_writecert; */
+  /* app->fnc.writekey = do_writekey; */
+  /* app->fnc.genkey = do_genkey; */
+  /* 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; */
+
+
+leave:
+  xfree (apt);
+  if (err)
+    do_deinit (app);
+  return err;
+}
index d16300e..800c954 100644 (file)
--- a/scd/app.c
+++ b/scd/app.c
@@ -211,6 +211,60 @@ app_new_register (int slot, ctrl_t ctrl, const char *name,
   if (!want_undefined)
     {
       err = iso7816_select_file (slot, 0x3F00, 1);
+      if (gpg_err_code (err) == GPG_ERR_CARD)
+        {
+          /* Might be SW==0x7D00.  Let's test whether it is a Yubikey
+           * by selecting its manager application and then reading the
+           * config.  */
+          static char const yk_aid[] =
+            { 0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 }; /*MGR*/
+          unsigned char *buf;
+          size_t buflen;
+          const unsigned char *s0, *s1;
+          size_t n;
+
+          if (!iso7816_select_application (slot, yk_aid, sizeof yk_aid,
+                                           0x0001)
+              && !iso7816_apdu_direct (slot, "\x00\x1d\x00\x00\x00", 5, 0,
+                                       NULL, &buf, &buflen))
+            {
+              if (opt.verbose)
+                {
+                  log_info ("Yubico: config=");
+                  log_printhex (buf, buflen, "");
+                }
+
+              /* We skip the first byte which seems to be the total
+               * length of the config data.  */
+              if (buflen > 1)
+                {
+                  s0 = find_tlv (buf+1, buflen-1, 0x04, &n);  /* Form factor */
+                  if (s0 && n == 1)
+                    {
+                      s1 = find_tlv (buf+1, buflen-1, 0x02, &n);  /* Serial */
+                      if (s1 && n >= 4)
+                        {
+                          app->serialno = xtrymalloc (3 + 1 + n);
+                          if (app->serialno)
+                            {
+                              app->serialnolen = 3 + 1 + n;
+                              app->serialno[0] = 0xff;
+                              app->serialno[1] = 0x02;
+                              app->serialno[2] = 0x0;
+                              app->serialno[3] = *s0;
+                              memcpy (app->serialno + 4, s1, n);
+                              /* Note that we do not clear the error
+                               * so that no further serial number
+                               * testing is done.  After all we just
+                               * set the serial number.  */
+                            }
+                        }
+                    }
+                }
+              xfree (buf);
+            }
+        }
+
       if (!err)
         err = iso7816_select_file (slot, 0x2F02, 0);
       if (!err)
@@ -270,6 +324,8 @@ app_new_register (int slot, ctrl_t ctrl, const char *name,
   if (err && is_app_allowed ("openpgp")
           && (!name || !strcmp (name, "openpgp")))
     err = app_select_openpgp (app);
+  if (err && is_app_allowed ("piv") && (!name || !strcmp (name, "piv")))
+    err = app_select_piv (app);
   if (err && is_app_allowed ("nks") && (!name || !strcmp (name, "nks")))
     err = app_select_nks (app);
   if (err && is_app_allowed ("p15") && (!name || !strcmp (name, "p15")))
@@ -409,6 +465,7 @@ get_supported_applications (void)
 {
   const char *list[] = {
     "openpgp",
+    "piv",
     "nks",
     "p15",
     "geldkarte",
@@ -509,6 +566,7 @@ release_application (app_t app, int locked_already)
      FF 00 00 = For serial numbers starting with an FF
      FF 01 00 = Some german p15 cards return an empty serial number so the
                 serial number from the EF(TokenInfo) is used instead.
+     FF 02 00 = Serial number from Yubikey config
      FF 7F 00 = No serialno.
 
      All other serial number not starting with FF are used as they are.