[git] GnuPG - branch, master, updated. gnupg-2.2.7-320-gec56996

by Werner Koch cvs at cvs.gnupg.org
Sun Jan 20 11:47:01 CET 2019


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "The GNU Privacy Guard".

The branch, master has been updated
       via  ec56996029d95d4bd26e1badfe207232270c6247 (commit)
       via  70bb5c7931598590b1acfae90bf4657f5911d2d3 (commit)
      from  d93797c8a7892fe26672c551017468e9f8099ef6 (commit)

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit ec56996029d95d4bd26e1badfe207232270c6247
Author: Werner Koch <wk at gnupg.org>
Date:   Sun Jan 20 11:45:57 2019 +0100

    scd: Add very basic support for PIV cards.
    
    * 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.

diff --git a/scd/Makefile.am b/scd/Makefile.am
index cbd1f9f..0cc50dc 100644
--- a/scd/Makefile.am
+++ b/scd/Makefile.am
@@ -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 \
diff --git a/scd/app-common.h b/scd/app-common.h
index 38e6cc6..ff58318 100644
--- a/scd/app-common.h
+++ b/scd/app-common.h
@@ -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
index 0000000..9b40477
--- /dev/null
+++ b/scd/app-piv.c
@@ -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);
+
+
+
+
+

+/* 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;
+}
diff --git a/scd/app.c b/scd/app.c
index d16300e..800c954 100644
--- 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.

commit 70bb5c7931598590b1acfae90bf4657f5911d2d3
Author: Werner Koch <wk at gnupg.org>
Date:   Sun Jan 20 11:41:23 2019 +0100

    scd: One new and one improved 7816 function.
    
    * scd/apdu.c (apdu_send_direct): New arg R_SW.
    * scd/command.c (cmd_apdu): Ditto.
    * scd/iso7816.c (iso7816_apdu_direct): New arg R_SW.
    (iso7816_general_authenticate): New.
    * scd/app-nks.c (get_chv_status, get_nks_version): Pass NULL for new
    arg.
    --
    
    iso7816_general_authenticate will be used for the PIV card support.
    The new arg to iso7816_apdu_direct and apdu_send_direct allows to get
    the raw status word back without the need to handle an output buffer.
    
    Signed-off-by: Werner Koch <wk at gnupg.org>

diff --git a/scd/apdu.c b/scd/apdu.c
index f3e2a12..816938a 100644
--- a/scd/apdu.c
+++ b/scd/apdu.c
@@ -3063,19 +3063,25 @@ apdu_send_simple (int slot, int extended_mode,
 
 
 /* This is a more generic version of the apdu sending routine.  It
-   takes an already formatted APDU in APDUDATA or length APDUDATALEN
-   and returns with an APDU including the status word.  With
-   HANDLE_MORE set to true this function will handle the MORE DATA
-   status and return all APDUs concatenated with one status word at
-   the end.  If EXTENDED_LENGTH is != 0 extended lengths are allowed
-   with a max. result data length of EXTENDED_LENGTH bytes.  The
-   function does not return a regular status word but 0 on success.
-   If the slot is locked, the function returns immediately with an
-   error.  */
+ * takes an already formatted APDU in APDUDATA or length APDUDATALEN
+ * and returns with an APDU including the status word.  With
+ * HANDLE_MORE set to true this function will handle the MORE DATA
+ * status and return all APDUs concatenated with one status word at
+ * the end.  If EXTENDED_LENGTH is != 0 extended lengths are allowed
+ * with a max. result data length of EXTENDED_LENGTH bytes.  The
+ * function does not return a regular status word but 0 on success.
+ * If the slot is locked, the function returns immediately with an
+ * error.
+ *
+ * Out of historical reasons the function returns 0 on success and
+ * outs the status word at the end of the result to be able to get the
+ * status word in the case of a not provided RETBUF, R_SW can be used
+ * to store the SW.  But note that R_SW qill only be set if the
+ * function returns 0. */
 int
 apdu_send_direct (int slot, size_t extended_length,
                   const unsigned char *apdudata, size_t apdudatalen,
-                  int handle_more,
+                  int handle_more, unsigned int *r_sw,
                   unsigned char **retbuf, size_t *retbuflen)
 {
 #define SHORT_RESULT_BUFFER_SIZE 258
@@ -3282,9 +3288,13 @@ apdu_send_direct (int slot, size_t extended_length,
       (*retbuf)[(*retbuflen)++] = sw;
     }
 
+  if (r_sw)
+    *r_sw = sw;
+
   if (DBG_CARD_IO && retbuf)
     log_printhex (*retbuf, *retbuflen, "      dump: ");
 
+
   return 0;
 }
 
diff --git a/scd/apdu.h b/scd/apdu.h
index 8621ddc..1392aab 100644
--- a/scd/apdu.h
+++ b/scd/apdu.h
@@ -138,7 +138,7 @@ int apdu_send_le (int slot, int extended_mode,
                   unsigned char **retbuf, size_t *retbuflen);
 int apdu_send_direct (int slot, size_t extended_length,
                       const unsigned char *apdudata, size_t apdudatalen,
-                      int handle_more,
+                      int handle_more, unsigned int *r_sw,
                       unsigned char **retbuf, size_t *retbuflen);
 const char *apdu_get_reader_name (int slot);
 
diff --git a/scd/app-nks.c b/scd/app-nks.c
index 9e720f0..801ab90 100644
--- a/scd/app-nks.c
+++ b/scd/app-nks.c
@@ -273,7 +273,7 @@ get_chv_status (app_t app, int sigg, int pwid)
   command[3] = pwid;
 
   if (apdu_send_direct (app->slot, 0, (unsigned char *)command,
-                        4, 0, &result, &resultlen))
+                        4, 0, NULL, &result, &resultlen))
     rc = -1; /* Error. */
   else if (resultlen < 2)
     rc = -1; /* Error. */
@@ -1300,7 +1300,7 @@ get_nks_version (int slot)
   int type;
 
   if (iso7816_apdu_direct (slot, "\x80\xaa\x06\x00\x00", 5, 0,
-                           &result, &resultlen))
+                           NULL, &result, &resultlen))
     return 2; /* NKS 2 does not support this command.  */
 
   /* Example value:    04 11 19 22 21 6A 20 80 03 03 01 01 01 00 00 00
diff --git a/scd/command.c b/scd/command.c
index 9df2611..ea4ccbc 100644
--- a/scd/command.c
+++ b/scd/command.c
@@ -333,7 +333,7 @@ static const char hlp_learn[] =
   "or a \"CANCEL\" to force the function to terminate with a Cancel\n"
   "error message.\n"
   "\n"
-  "With the option --keypairinfo only KEYPARIINFO lstatus lines are\n"
+  "With the option --keypairinfo only KEYPARIINFO status lines are\n"
   "returned.\n"
   "\n"
   "The response of this command is a list of status lines formatted as\n"
@@ -346,6 +346,7 @@ static const char hlp_learn[] =
   "    P15     = PKCS-15 structure used\n"
   "    DINSIG  = DIN SIG\n"
   "    OPENPGP = OpenPGP card\n"
+  "    PIV     = PIV card\n"
   "    NKS     = NetKey card\n"
   "\n"
   "are implemented.  These strings are aliases for the AID\n"
@@ -1663,7 +1664,7 @@ cmd_apdu (assuan_context_t ctx, char *line)
 
       rc = apdu_send_direct (app->slot, exlen,
                              apdu, apdulen, handle_more,
-                             &result, &resultlen);
+                             NULL, &result, &resultlen);
       if (rc)
         log_error ("apdu_send_direct failed: %s\n", gpg_strerror (rc));
       else
diff --git a/scd/iso7816.c b/scd/iso7816.c
index 43c0bcd..c8a2138 100644
--- a/scd/iso7816.c
+++ b/scd/iso7816.c
@@ -50,6 +50,7 @@
 #define CMD_PUT_DATA    0xDA
 #define CMD_MSE         0x22
 #define CMD_PSO         0x2A
+#define CMD_GENERAL_AUTHENTICATE  0x87
 #define CMD_INTERNAL_AUTHENTICATE 0x88
 #define CMD_GENERATE_KEYPAIR      0x47
 #define CMD_GET_CHALLENGE         0x84
@@ -225,24 +226,28 @@ iso7816_list_directory (int slot, int list_dirs,
    internally.  The return value is a gpg error code (i.e. a mapped
    status word).  This is basically the same as apdu_send_direct but
    it maps the status word and does not return it in the result
-   buffer.  */
+   buffer.  However, it R_SW is not NULL the status word is stored
+   R_SW for closer inspection. */
 gpg_error_t
 iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen,
-                     int handle_more,
+                     int handle_more, unsigned int *r_sw,
                      unsigned char **result, size_t *resultlen)
 {
-  int sw;
+  int sw, sw2;
 
-  if (!result || !resultlen)
-    return gpg_error (GPG_ERR_INV_VALUE);
-  *result = NULL;
-  *resultlen = 0;
+  if (result)
+    {
+      *result = NULL;
+      *resultlen = 0;
+    }
 
   sw = apdu_send_direct (slot, 0, apdudata, apdudatalen, handle_more,
-                         result, resultlen);
+                         &sw2, result, resultlen);
   if (!sw)
     {
-      if (*resultlen < 2)
+      if (!result)
+        sw = sw2;
+      else if (*resultlen < 2)
         sw = SW_HOST_GENERAL_ERROR;
       else
         {
@@ -251,13 +256,15 @@ iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen,
           (*resultlen)--;
         }
     }
-  if (sw != SW_SUCCESS)
+  if (sw != SW_SUCCESS && result)
     {
       /* Make sure that pending buffers are released. */
       xfree (*result);
       *result = NULL;
       *resultlen = 0;
     }
+  if (r_sw)
+    *r_sw = sw;
   return map_sw (sw);
 }
 
@@ -621,7 +628,7 @@ iso7816_decipher (int slot, int extended_mode,
 }
 
 
-/*  For LE see do_generate_keypair.  */
+/* For LE see do_generate_keypair.  */
 gpg_error_t
 iso7816_internal_authenticate (int slot, int extended_mode,
                                const unsigned char *data, size_t datalen,
@@ -658,6 +665,44 @@ iso7816_internal_authenticate (int slot, int extended_mode,
 }
 
 
+/* For LE see do_generate_keypair.  */
+gpg_error_t
+iso7816_general_authenticate (int slot, int extended_mode,
+                              int algoref, int keyref,
+                              const unsigned char *data, size_t datalen,
+                              int le,
+                              unsigned char **result, size_t *resultlen)
+{
+  int sw;
+
+  if (!data || !datalen || !result || !resultlen)
+    return gpg_error (GPG_ERR_INV_VALUE);
+  *result = NULL;
+  *resultlen = 0;
+
+  if (!extended_mode)
+    le = 256;  /* Ignore provided Le and use what apdu_send uses. */
+  else if (le >= 0 && le < 256)
+    le = 256;
+
+  sw = apdu_send_le (slot, extended_mode,
+                     0x00, CMD_GENERAL_AUTHENTICATE, algoref, keyref,
+                     datalen, (const char*)data,
+                     le,
+                     result, resultlen);
+  if (sw != SW_SUCCESS)
+    {
+      /* Make sure that pending buffers are released. */
+      xfree (*result);
+      *result = NULL;
+      *resultlen = 0;
+      return map_sw (sw);
+    }
+
+  return 0;
+}
+
+
 /* LE is the expected return length.  This is usually 0 except if
    extended length mode is used and more than 256 byte will be
    returned.  In that case a value of -1 uses a large default
diff --git a/scd/iso7816.h b/scd/iso7816.h
index 332fc0e..4a366e6 100644
--- a/scd/iso7816.h
+++ b/scd/iso7816.h
@@ -63,7 +63,7 @@ gpg_error_t iso7816_list_directory (int slot, int list_dirs,
                                     unsigned char **result, size_t *resultlen);
 gpg_error_t iso7816_apdu_direct (int slot,
                                  const void *apdudata, size_t apdudatalen,
-                                 int handle_more,
+                                 int handle_more, unsigned int *r_sw,
                                  unsigned char **result, size_t *resultlen);
 gpg_error_t iso7816_check_pinpad (int slot, int command,
                                   pininfo_t *pininfo);
@@ -104,6 +104,13 @@ gpg_error_t iso7816_internal_authenticate (int slot, int extended_mode,
                                    const unsigned char *data, size_t datalen,
                                    int le,
                                    unsigned char **result, size_t *resultlen);
+gpg_error_t iso7816_general_authenticate (int slot, int extended_mode,
+                                          int algoref, int keyref,
+                                          const unsigned char *data,
+                                          size_t datalen,
+                                          int le,
+                                          unsigned char **result,
+                                          size_t *resultlen);
 gpg_error_t iso7816_generate_keypair (int slot, int extended_mode,
                                     const char *data, size_t datalen,
                                     int le,

-----------------------------------------------------------------------

Summary of changes:
 scd/Makefile.am  |    3 +-
 scd/apdu.c       |   30 +-
 scd/apdu.h       |    2 +-
 scd/app-common.h |    3 +
 scd/app-nks.c    |    4 +-
 scd/app-piv.c    | 1238 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 scd/app.c        |   58 +++
 scd/command.c    |    5 +-
 scd/iso7816.c    |   67 ++-
 scd/iso7816.h    |    9 +-
 10 files changed, 1391 insertions(+), 28 deletions(-)
 create mode 100644 scd/app-piv.c


hooks/post-receive
-- 
The GNU Privacy Guard
http://git.gnupg.org




More information about the Gnupg-commits mailing list