card: New command "yubikey".
authorWerner Koch <wk@gnupg.org>
Wed, 13 Feb 2019 08:46:36 +0000 (09:46 +0100)
committerWerner Koch <wk@gnupg.org>
Wed, 13 Feb 2019 08:49:07 +0000 (09:49 +0100)
* tools/card-tool-yubikey.c: New.
* tools/Makefile.am (gpg_card_tool_SOURCES): Add it.
* tools/card-call-scd.c (scd_apdu): Allow returning data.
* tools/card-tool-misc.c (send_apdu): New.  Move from gpg-card-tool.c
and let it return data.  Change all callers.

* tools/gpg-card-tool.c (cmd_writecert): Prepend the certref with the
current application type.
(cmd_yubikey): New.
--

This command allows listing of active applications and to enable or
disable selected applications.  This is in particular useful to
disable the OpenPGP application so that the PIV support can easily be
tested.

Signed-off-by: Werner Koch <wk@gnupg.org>
tools/Makefile.am
tools/card-call-scd.c
tools/card-tool-misc.c
tools/card-tool-yubikey.c [new file with mode: 0644]
tools/card-tool.h
tools/gpg-card-tool.c

index ad0f223..69f4098 100644 (file)
@@ -129,6 +129,7 @@ gpg_card_tool_SOURCES   = \
        card-tool.h     \
        card-call-scd.c \
        card-tool-keys.c \
+       card-tool-yubikey.c \
        card-tool-misc.c
 
 gpg_card_tool_LDADD     = ../common/libgpgrl.a $(common_libs) \
index 97fb6d9..8610da8 100644 (file)
@@ -445,12 +445,20 @@ store_serialno (const char *line)
 /* Send an APDU to the current card.  On success the status word is
  * stored at R_SW inless R_SW is NULL.  With HEXAPDU being NULL only a
  * RESET command is send to scd.  With HEXAPDU being the string
- * "undefined" the command "SERIALNO undefined" is send to scd. */
+ * "undefined" the command "SERIALNO undefined" is send to scd.  If
+ * R_DATA is not NULL the data is without the status code is stored
+ * there.  Caller must release it.  */
 gpg_error_t
-scd_apdu (const char *hexapdu, unsigned int *r_sw)
+scd_apdu (const char *hexapdu, unsigned int *r_sw,
+          unsigned char **r_data, size_t *r_datalen)
 {
   gpg_error_t err;
 
+  if (r_data)
+    *r_data = NULL;
+  if (r_datalen)
+    *r_datalen = 0;
+
   err = start_agent (START_AGENT_NO_STARTUP_CMDS);
   if (err)
     return err;
@@ -489,6 +497,12 @@ scd_apdu (const char *hexapdu, unsigned int *r_sw)
             {
               if (r_sw)
                 *r_sw = buf16_to_uint (data+datalen-2);
+              if (r_data && r_datalen)
+                {
+                  *r_data = data;
+                  *r_datalen = datalen - 2;
+                  data = NULL;
+                }
             }
           xfree (data);
         }
index 06fcb67..d0fb55d 100644 (file)
@@ -77,3 +77,37 @@ hex_to_buffer (const char *string, size_t *r_length)
   *r_length = n;
   return buffer;
 }
+
+
+/* Direct sending of an hex encoded APDU with error printing.  This is
+ * a simple wrapper around scd_apdu.  */
+gpg_error_t
+send_apdu (const char *hexapdu, const char *desc, unsigned int ignore,
+           unsigned char **r_data, size_t *r_datalen)
+{
+  gpg_error_t err;
+  unsigned int sw;
+
+  err = scd_apdu (hexapdu, &sw, r_data, r_datalen);
+  if (err)
+    log_error ("sending card command %s failed: %s\n", desc,
+               gpg_strerror (err));
+  else if (!hexapdu || !strcmp (hexapdu, "undefined"))
+    ;
+  else if (ignore == 0xffff)
+    ; /* Ignore all status words.  */
+  else if (sw != 0x9000)
+    {
+      switch (sw)
+        {
+        case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break;
+        case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break;
+        case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break;
+        default: err = gpg_error (GPG_ERR_CARD);
+        }
+      if (!(ignore && ignore == sw))
+        log_error ("card command %s failed: %s (0x%04x)\n", desc,
+                   gpg_strerror (err),  sw);
+    }
+  return err;
+}
diff --git a/tools/card-tool-yubikey.c b/tools/card-tool-yubikey.c
new file mode 100644 (file)
index 0000000..a03915a
--- /dev/null
@@ -0,0 +1,438 @@
+/* card-tool-yubikey.c - Yubikey specific functions.
+ * 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/>.
+ * SPDX-License-Identifier: GPL-3.0-or-later
+ */
+
+#include <config.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <errno.h>
+#include <unistd.h>
+
+#include "../common/util.h"
+#include "../common/i18n.h"
+#include "../common/tlv.h"
+#include "../common/ttyio.h"
+#include "card-tool.h"
+
+
+/* Object to describe requested interface options.  */
+struct iface_s {
+  unsigned int usb:1;
+  unsigned int nfc:1;
+};
+
+
+/* Bit flags as used by the fields in struct ykapps_s. */
+#define YKAPP_USB_SUPPORTED  0x01
+#define YKAPP_USB_ENABLED    0x02
+#define YKAPP_NFC_SUPPORTED  0x04
+#define YKAPP_NFC_ENABLED    0x08
+#define YKAPP_SELECTED       0x80  /* Selected by the command.  */
+
+/* An object to describe the applications on a Yubikey.  Each field
+ * has 8 bits to hold the above flag values.  */
+struct ykapps_s {
+  unsigned int otp:8;
+  unsigned int u2f:8;
+  unsigned int opgp:8;
+  unsigned int piv:8;
+  unsigned int oath:8;
+  unsigned int fido2:8;
+};
+
+
+
+/* Helper to parse an unsigned integer config value consisting of bit
+ * flags.  TAG select the config item and MASK is the mask ORed into
+ * the value for a set bit.  The function modifies YK.  */
+static gpg_error_t
+parse_ul_config_value (struct ykapps_s *yk,
+                       const unsigned char *config, size_t configlen,
+                       int tag, unsigned int mask)
+{
+  const unsigned char *s;
+  size_t n;
+  unsigned long ul = 0;
+  int i;
+
+  s = find_tlv (config, configlen, tag, &n);
+  if (s && n)
+    {
+      if (n > sizeof ul)
+        {
+          log_error ("too large integer in Yubikey config tag %02x detected\n",
+                     tag);
+          if (opt.verbose)
+            log_printhex (config, configlen, "config:");
+          return gpg_error (GPG_ERR_CARD);
+        }
+      for (i=0; i < n; i++)
+        {
+          ul <<=8;
+          ul |= s[i];
+        }
+      if (ul & 0x01)
+        yk->otp |= mask;
+      if (ul & 0x02)
+        yk->u2f |= mask;
+      if (ul & 0x08)
+        yk->opgp |= mask;
+      if (ul & 0x10)
+        yk->piv |= mask;
+      if (ul & 0x20)
+        yk->oath |= mask;
+      if (ul & 0x200)
+        yk->fido2 |= mask;
+    }
+  return 0;
+}
+
+
+/* Create an unsigned integer config value for TAG from the data in YK
+ * and store it the provided 4 byte buffer RESULT. If ENABLE is true
+ * the respective APP_SELECTED bit in YK sets the corresponding bit
+ * flags, it is is false that bit flag is cleared.  IF APP_SELECTED is
+ * not set the bit flag is not changed.  */
+static void
+set_ul_config_value (struct ykapps_s *yk,
+                     unsigned int bitflag, int tag, unsigned int enable,
+                     unsigned char *result)
+{
+  unsigned long ul = 0;
+
+  /* First set the current values.  */
+  if ((yk->otp & bitflag))
+    ul |= 0x01;
+  if ((yk->u2f & bitflag))
+    ul |= 0x02;
+  if ((yk->opgp & bitflag))
+    ul |= 0x08;
+  if ((yk->piv & bitflag))
+    ul |= 0x10;
+  if ((yk->oath & bitflag))
+    ul |= 0x20;
+  if ((yk->fido2 & bitflag))
+    ul |= 0x200;
+
+  /* Then enable or disable the bits according to the selection flag.  */
+  if (enable)
+    {
+      if ((yk->otp & YKAPP_SELECTED))
+        ul |= 0x01;
+      if ((yk->u2f & YKAPP_SELECTED))
+        ul |= 0x02;
+      if ((yk->opgp & YKAPP_SELECTED))
+        ul |= 0x08;
+      if ((yk->piv & YKAPP_SELECTED))
+        ul |= 0x10;
+      if ((yk->oath & YKAPP_SELECTED))
+        ul |= 0x20;
+      if ((yk->fido2 & YKAPP_SELECTED))
+        ul |= 0x200;
+    }
+  else
+    {
+      if ((yk->otp & YKAPP_SELECTED))
+        ul &= ~0x01;
+      if ((yk->u2f & YKAPP_SELECTED))
+        ul &= ~0x02;
+      if ((yk->opgp & YKAPP_SELECTED))
+        ul &= ~0x08;
+      if ((yk->piv & YKAPP_SELECTED))
+        ul &= ~0x10;
+      if ((yk->oath & YKAPP_SELECTED))
+        ul &= ~0x20;
+      if ((yk->fido2 & YKAPP_SELECTED))
+        ul &= ~0x200;
+    }
+
+  /* Make sure that we do not disable the CCID transport.  Without
+   * CCID we won't have any way to change the configuration again.  We
+   * would instead need one of the other Yubikey tools to enable an
+   * application and thus its transport again.  */
+  if (bitflag == YKAPP_USB_ENABLED && !(ul & (0x08|0x10|0x20)))
+    {
+      log_info ("Enabling PIV so that at least one CCI transport is enabled\n");
+      ul |= 0x10;
+    }
+
+  result[0] = tag;
+  result[1] = 2;
+  result[2] = ul >> 8;
+  result[3] = ul;
+}
+
+
+/* Print the info from YK.  */
+static void
+yk_list (estream_t fp, struct ykapps_s *yk)
+{
+  if (opt.interactive)
+    tty_fprintf (fp, ("Application  USB    NFC\n"
+                      "-----------------------\n"));
+  tty_fprintf (fp, "OTP          %s    %s\n",
+               (yk->otp & YKAPP_USB_SUPPORTED)?
+               (yk->otp & YKAPP_USB_ENABLED?   "yes" : "no ") : "-  ",
+               (yk->otp & YKAPP_NFC_SUPPORTED)?
+               (yk->otp & YKAPP_NFC_ENABLED?   "yes" : "no ") : "-  ");
+  tty_fprintf (fp, "U2F          %s    %s\n",
+               (yk->otp & YKAPP_USB_SUPPORTED)?
+               (yk->otp & YKAPP_USB_ENABLED?   "yes" : "no ") : "-  ",
+               (yk->otp & YKAPP_NFC_SUPPORTED)?
+               (yk->otp & YKAPP_NFC_ENABLED?   "yes" : "no ") : "-  ");
+  tty_fprintf (fp, "OPGP         %s    %s\n",
+               (yk->opgp & YKAPP_USB_SUPPORTED)?
+               (yk->opgp & YKAPP_USB_ENABLED?  "yes" : "no ") : "-  ",
+               (yk->opgp & YKAPP_NFC_SUPPORTED)?
+               (yk->opgp & YKAPP_NFC_ENABLED?  "yes" : "no ") : "-  ");
+  tty_fprintf (fp, "PIV          %s    %s\n",
+               (yk->piv & YKAPP_USB_SUPPORTED)?
+               (yk->piv & YKAPP_USB_ENABLED?   "yes" : "no ") : "-  ",
+               (yk->piv & YKAPP_NFC_SUPPORTED)?
+               (yk->piv & YKAPP_NFC_ENABLED?   "yes" : "no ") : "-  ");
+  tty_fprintf (fp, "OATH         %s    %s\n",
+               (yk->oath & YKAPP_USB_SUPPORTED)?
+               (yk->oath & YKAPP_USB_ENABLED?  "yes" : "no ") : "-  ",
+               (yk->oath & YKAPP_NFC_SUPPORTED)?
+               (yk->oath & YKAPP_NFC_ENABLED?  "yes" : "no ") : "-  ");
+  tty_fprintf (fp, "FIDO2        %s    %s\n",
+               (yk->fido2 & YKAPP_USB_SUPPORTED)?
+               (yk->fido2 & YKAPP_USB_ENABLED? "yes" : "no ") : "-  ",
+               (yk->fido2 & YKAPP_NFC_SUPPORTED)?
+               (yk->fido2 & YKAPP_NFC_ENABLED? "yes" : "no ") : "-  ");
+}
+
+
+/* Enable disable the apps as marked in YK with flag YKAPP_SELECTED.  */
+static gpg_error_t
+yk_enable_disable (struct ykapps_s *yk, struct iface_s *iface,
+                   const unsigned char *config, size_t configlen, int enable)
+{
+  gpg_error_t err = 0;
+  unsigned char apdu[100];
+  unsigned int apdulen;
+  /* const unsigned char *s; */
+  /* size_t n; */
+  char *hexapdu = NULL;
+
+  apdulen = 0;
+  apdu[apdulen++] = 0x00;
+  apdu[apdulen++] = 0x1c;  /* Write Config instruction.  */
+  apdu[apdulen++] = 0x00;
+  apdu[apdulen++] = 0x00;
+  apdu[apdulen++] = 0x00;  /* Lc will be fixed up later.  */
+  apdu[apdulen++] = 0x00;  /* Length of data will also be fixed up later.  */
+
+  /* The ykman tool has no way to set NFC and USB flags in one go.
+   * Reasoning about the Yubikey's firmware it seems plausible that
+   * combining should work.  Let's try it here if the user called for
+   * setting both interfaces.  */
+  if (iface->nfc)
+    {
+      set_ul_config_value (yk, YKAPP_NFC_ENABLED, 0x0e, enable, apdu+apdulen);
+      apdulen += 4;
+    }
+  if (iface->usb)
+    {
+      set_ul_config_value (yk, YKAPP_USB_ENABLED, 0x03, enable, apdu+apdulen);
+      apdulen += 4;
+      /* Yubikey's ykman copies parts of the config data when writing
+       * the config for USB.  Below is a commented example on how that
+       * can be done.  */
+      (void)config;
+      (void)configlen;
+      /* Copy the device flags.  */
+      /* s = find_tlv (config, configlen, 0x08, &n); */
+      /* if (s && n) */
+      /*   { */
+      /*     s -= 2; */
+      /*     n += 2; */
+      /*     if (apdulen + n > sizeof apdu) */
+      /*       { */
+      /*         err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); */
+      /*         goto leave; */
+      /*       } */
+      /*     memcpy (apdu+apdulen, s, n); */
+      /*     apdulen += n; */
+      /*   } */
+    }
+  if (iface->nfc || iface->usb)
+    {
+      if (apdulen + 2 > sizeof apdu)
+        {
+          err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
+          goto leave;
+        }
+      /* Disable the next two lines to let the card reboot.  Not doing
+       * this is however more convenient for this tool because further
+       * commands don't end up with an error.  It seems to be better
+       * that a "reset" command from gpg-card-tool is run at the
+       * user's discretion.  */
+      /* apdu[apdulen++] = 0x0c;  /\* Reboot tag *\/ */
+      /* apdu[apdulen++] = 0;     /\* No data for reboot.  *\/ */
+      /* Fixup the lngth bytes.  */
+      apdu[4] = apdulen - 6 + 1;
+      apdu[5] = apdulen - 6;
+
+      hexapdu = bin2hex (apdu, apdulen, NULL);
+      if (!hexapdu)
+        err = gpg_error_from_syserror ();
+      else
+        err = send_apdu (hexapdu, "YK.write_config", 0, NULL, NULL);
+    }
+
+ leave:
+  xfree (hexapdu);
+  return err;
+}
+
+
+/* Implementation part of cmd_yubikey.  ARGV is an array of size ARGc
+ * with the argumets given to the yubikey command.  Note that ARGV has
+ * no terminating NULL so that ARGC must be considred.  FP is the
+ * stream to output information.  This function must only be called on
+ * Yubikeys. */
+gpg_error_t
+yubikey_commands (estream_t fp, int argc, char *argv[])
+{
+  gpg_error_t err;
+  enum {ykLIST, ykENABLE, ykDISABLE } cmd;
+  struct iface_s iface = {0,0};
+  struct ykapps_s ykapps = {0};
+  unsigned char *config = NULL;
+  size_t configlen;
+  int i;
+
+  if (!argc)
+    return gpg_error (GPG_ERR_SYNTAX);
+
+  /* Parse command.  */
+  if (!ascii_strcasecmp (argv[0], "list"))
+    cmd = ykLIST;
+  else if (!ascii_strcasecmp (argv[0], "enable"))
+    cmd = ykENABLE;
+  else if (!ascii_strcasecmp (argv[0], "disable"))
+    cmd = ykDISABLE;
+  else
+    {
+      err = gpg_error (GPG_ERR_UNKNOWN_COMMAND);
+      goto leave;
+    }
+
+  /* Parse interface if needed.  */
+  if (cmd == ykLIST)
+    iface.usb = iface.nfc = 1;
+  else if (argc < 2)
+    {
+      err = gpg_error (GPG_ERR_SYNTAX);
+      goto leave;
+    }
+  else if (!ascii_strcasecmp (argv[1], "usb"))
+    iface.usb = 1;
+  else if (!ascii_strcasecmp (argv[1], "nfc"))
+    iface.nfc = 1;
+  else if (!ascii_strcasecmp (argv[1], "all") || !strcmp (argv[1], "*"))
+    iface.usb = iface.nfc = 1;
+  else
+    {
+      err = gpg_error (GPG_ERR_SYNTAX);
+      goto leave;
+    }
+
+  /* Parse list of applications.  */
+  for (i=2; i < argc; i++)
+    {
+      if (!ascii_strcasecmp (argv[i], "otp"))
+        ykapps.otp = 0x80;
+      else if (!ascii_strcasecmp (argv[i], "u2f"))
+        ykapps.u2f = 0x80;
+      else if (!ascii_strcasecmp (argv[i], "opgp")
+               ||!ascii_strcasecmp (argv[i], "openpgp"))
+        ykapps.opgp = 0x80;
+      else if (!ascii_strcasecmp (argv[i], "piv"))
+        ykapps.piv = 0x80;
+      else if (!ascii_strcasecmp (argv[i], "oath")
+               || !ascii_strcasecmp (argv[i], "oauth"))
+        ykapps.oath = 0x80;
+      else if (!ascii_strcasecmp (argv[i], "fido2"))
+        ykapps.fido2 = 0x80;
+      else if (!ascii_strcasecmp (argv[i], "all")|| !strcmp (argv[i], "*"))
+        {
+          ykapps.otp = ykapps.u2f = ykapps.opgp = ykapps.piv = ykapps.oath
+            = ykapps.fido2 = 0x80;
+        }
+      else
+        {
+          err = gpg_error (GPG_ERR_SYNTAX);
+          goto leave;
+        }
+    }
+
+  /* Select the Yubikey Manager application.  */
+  err = send_apdu ("00A4040008a000000527471117", "Select.YK-Manager", 0,
+                   NULL, NULL);
+  if (err)
+    goto leave;
+  /* Send the read config command.  */
+  err = send_apdu ("001D000000", "YK.read_config", 0, &config, &configlen);
+  if (err)
+    goto leave;
+  if (!configlen || *config > configlen - 1)
+    {
+      /* The length byte is shorter than the actual length. */
+      log_error ("Yubikey returned improper config data\n");
+      log_printhex (config, configlen, "config:");
+      err = gpg_error (GPG_ERR_CARD);
+      goto leave;
+    }
+  if (configlen-1 > *config)
+    {
+      log_info ("Extra config data ignored\n");
+      log_printhex (config, configlen, "config:");
+    }
+  configlen = *config;
+
+  err = parse_ul_config_value (&ykapps, config+1, configlen,
+                               0x01, YKAPP_USB_SUPPORTED);
+  if (!err)
+    err = parse_ul_config_value (&ykapps, config+1, configlen,
+                                 0x03, YKAPP_USB_ENABLED);
+  if (!err)
+    err = parse_ul_config_value (&ykapps, config+1, configlen,
+                                 0x0d, YKAPP_NFC_SUPPORTED);
+  if (!err)
+    err = parse_ul_config_value (&ykapps, config+1, configlen,
+                                 0x0e, YKAPP_NFC_ENABLED);
+  if (err)
+    goto leave;
+
+  switch (cmd)
+    {
+    case ykLIST: yk_list (fp, &ykapps); break;
+    case ykENABLE: err = yk_enable_disable (&ykapps, &iface,
+                                            config+1, configlen, 1); break;
+    case ykDISABLE: err = yk_enable_disable (&ykapps, &iface,
+                                             config+1, configlen, 0); break;
+    }
+
+ leave:
+  xfree (config);
+  return err;
+}
index f49f253..f83ebf9 100644 (file)
@@ -192,12 +192,16 @@ gpg_error_t test_get_matching_keys (const char *hexgrip);
 /*-- card-tool-misc.c --*/
 key_info_t find_kinfo (card_info_t info, const char *keyref);
 void *hex_to_buffer (const char *string, size_t *r_length);
+gpg_error_t send_apdu (const char *hexapdu, const char *desc,
+                       unsigned int ignore,
+                       unsigned char **r_data, size_t *r_datalen);
 
 /*-- card-call-scd.c --*/
 void release_card_info (card_info_t info);
 const char *app_type_string (app_type_t app_type);
 
-gpg_error_t scd_apdu (const char *hexapdu, unsigned int *r_sw);
+gpg_error_t scd_apdu (const char *hexapdu, unsigned int *r_sw,
+                      unsigned char **r_data, size_t *r_datalen);
 gpg_error_t scd_learn (card_info_t info);
 gpg_error_t scd_getattr (const char *name, struct card_info_s *info);
 gpg_error_t scd_setattr (const char *name,
@@ -218,6 +222,8 @@ gpg_error_t scd_checkpin (const char *serialno);
 
 unsigned long agent_get_s2k_count (void);
 
+/*-- card-tool-yubikey.c --*/
+gpg_error_t yubikey_commands (estream_t fp, int argc, char *argv[]);
 
 
 #endif /*GNUPG_CARD_TOOL_H*/
index 93153b1..2bc2e5f 100644 (file)
@@ -1596,6 +1596,16 @@ cmd_writecert (card_info_t info, char *argstr)
         }
       certref = certref_buffer = xstrdup ("OPENPGP.3");
     }
+  else /* Upcase the certref; prepend cardtype if needed.  */
+    {
+      if (!strchr (certref, '.'))
+        certref_buffer = xstrconcat (app_type_string (info->apptype), ".",
+                                     certref, NULL);
+      else
+        certref_buffer = xstrdup (certref);
+      ascii_strupr (certref_buffer);
+      certref = certref_buffer;
+    }
 
   if (opt_clear)
     {
@@ -2156,38 +2166,6 @@ cmd_unblock (card_info_t info)
 }
 
 
-/* Direct sending of an hex encoded APDU with error printing.  */
-static gpg_error_t
-send_apdu (const char *hexapdu, const char *desc, unsigned int ignore)
-{
-  gpg_error_t err;
-  unsigned int sw;
-
-  err = scd_apdu (hexapdu, &sw);
-  if (err)
-    log_error ("sending card command %s failed: %s\n", desc,
-               gpg_strerror (err));
-  else if (!hexapdu || !strcmp (hexapdu, "undefined"))
-    ;
-  else if (ignore == 0xffff)
-    ; /* Ignore all status words.  */
-  else if (sw != 0x9000)
-    {
-      switch (sw)
-        {
-        case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break;
-        case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break;
-        case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break;
-        default: err = gpg_error (GPG_ERR_CARD);
-        }
-      if (!(ignore && ignore == sw))
-        log_error ("card command %s failed: %s (0x%04x)\n", desc,
-                   gpg_strerror (err),  sw);
-    }
-  return err;
-}
-
-
 /* Note: On successful execution a redisplay should be scheduled.  If
  * this function fails the card may be in an unknown state. */
 static gpg_error_t
@@ -2308,11 +2286,12 @@ cmd_factoryreset (card_info_t info)
            * unblock PIN command.  */
           any_apdu = 1;
           for (i=0; i < 5; i++)
-            send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff);
+            send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff,
+                       NULL, NULL);
           for (i=0; i < 5; i++)
             send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
-                       "RESET RETRY COUNTER", 0xffff);
-          err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0);
+                       "RESET RETRY COUNTER", 0xffff, NULL, NULL);
+          err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0, NULL, NULL);
           if (err)
             goto leave;
         }
@@ -2321,14 +2300,15 @@ cmd_factoryreset (card_info_t info)
           any_apdu = 1;
           /* We need to select a card application before we can send APDUs
            * to the card without scdaemon doing anything on its own.  */
-          err = send_apdu (NULL, "RESET", 0);
+          err = send_apdu (NULL, "RESET", 0, NULL, NULL);
           if (err)
             goto leave;
-          err = send_apdu ("undefined", "dummy select ", 0);
+          err = send_apdu ("undefined", "dummy select ", 0, NULL, NULL);
           if (err)
             goto leave;
           /* Select the OpenPGP application.  */
-          err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0);
+          err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0,
+                           NULL, NULL);
           if (err)
             goto leave;
 
@@ -2343,14 +2323,16 @@ cmd_factoryreset (card_info_t info)
           for (i=0; i < 4; i++)
             send_apdu ("0020008120"
                        "40404040404040404040404040404040"
-                       "40404040404040404040404040404040", "VERIFY", 0xffff);
+                       "40404040404040404040404040404040", "VERIFY", 0xffff,
+                       NULL, NULL);
           for (i=0; i < 4; i++)
             send_apdu ("0020008320"
                        "40404040404040404040404040404040"
-                       "40404040404040404040404040404040", "VERIFY", 0xffff);
+                       "40404040404040404040404040404040", "VERIFY", 0xffff,
+                       NULL, NULL);
 
           /* Send terminate datafile command.  */
-          err = send_apdu ("00e60000", "TERMINATE DF", 0x6985);
+          err = send_apdu ("00e60000", "TERMINATE DF", 0x6985, NULL, NULL);
           if (err)
             goto leave;
         }
@@ -2361,13 +2343,13 @@ cmd_factoryreset (card_info_t info)
       any_apdu = 1;
       /* Send activate datafile command.  This is used without
        * confirmation if the card is already in termination state.  */
-      err = send_apdu ("00440000", "ACTIVATE DF", 0);
+      err = send_apdu ("00440000", "ACTIVATE DF", 0, NULL, NULL);
       if (err)
         goto leave;
     }
 
   /* Finally we reset the card reader once more.  */
-  err = send_apdu (NULL, "RESET", 0);
+  err = send_apdu (NULL, "RESET", 0, NULL, NULL);
   if (err)
     goto leave;
 
@@ -2859,7 +2841,60 @@ cmd_uif (card_info_t info, char *argstr)
 }
 
 
+static gpg_error_t
+cmd_yubikey (card_info_t info, char *argstr)
+{
+  gpg_error_t err, err2;
+  estream_t fp = opt.interactive? NULL : es_stdout;
+  char *words[20];
+  int nwords;
+
+  if (!info)
+    return print_help
+      ("YUBIKEY <cmd> args\n\n"
+       "Various commands pertaining to Yubikey tokens with <cmd> being:\n"
+       "\n"
+       "  LIST \n"
+       "\n"
+       "List supported and enabled applications.\n"
+       "\n"
+       "  ENABLE  usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n"
+       "  DISABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n"
+       "\n"
+       "Enable or disable the specified or all applications on the\n"
+       "given interface.",
+       0);
+
+  argstr = skip_options (argstr);
+
+  if (!info->cardtype || strcmp (info->cardtype, "yubikey"))
+    {
+      log_info ("This command can only be used with Yubikeys.\n");
+      err = gpg_error (GPG_ERR_NOT_SUPPORTED);
+      goto leave;
+    }
+
+  nwords = split_fields (argstr, words, DIM (words));
+  if (nwords < 1)
+    {
+      err = gpg_error (GPG_ERR_SYNTAX);
+      goto leave;
+    }
+
+
+  /* Note that we always do a learn to get a chance to the card back
+   * into a usable state.  */
+  err = yubikey_commands (fp, nwords, words);
+  err2 = scd_learn (info);
+  if (err2)
+    log_error ("Error re-reading card: %s\n", gpg_strerror (err));
+
+ leave:
+  return err;
+}
+
 \f
+
 /* Data used by the command parser.  This needs to be outside of the
  * function scope to allow readline based command completion.  */
 enum cmdids
@@ -2869,7 +2904,7 @@ enum cmdids
     cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR,
     cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT,
     cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP,
-    cmdKEYATTR, cmdUIF, cmdAUTHENTICATE,
+    cmdKEYATTR, cmdUIF, cmdAUTHENTICATE, cmdYUBIKEY,
     cmdINVCMD
   };
 
@@ -2907,10 +2942,10 @@ static struct
   { "kdf-setup", cmdKDFSETUP, 1, N_("setup KDF for PIN authentication")},
   { "key-attr", cmdKEYATTR, 1, N_("change the key attribute")},
   { "uif", cmdUIF, 1, N_("change the User Interaction Flag")},
-  /* Note, that we do not announce these command yet. */
   { "privatedo", cmdPRIVATEDO, 0, N_("change a private data object")},
   { "readcert",  cmdREADCERT,  0, N_("read a certificate from a data object")},
   { "writecert", cmdWRITECERT, 1, N_("store a certificate to a data object")},
+  { "yubikey",   cmdYUBIKEY,   0, N_("Yubikey management commands")},
   { NULL, cmdINVCMD, 0, NULL }
 };
 
@@ -3020,7 +3055,7 @@ dispatch_command (card_info_t info, const char *orig_command)
       else
         {
           flush_keyblock_cache ();
-          err = scd_apdu (NULL, NULL);
+          err = scd_apdu (NULL, NULL, NULL, NULL);
         }
       break;
 
@@ -3048,6 +3083,7 @@ dispatch_command (card_info_t info, const char *orig_command)
     case cmdKDFSETUP:     err = cmd_kdfsetup (info, argstr); break;
     case cmdKEYATTR:      err = cmd_keyattr (info, argstr); break;
     case cmdUIF:          err = cmd_uif (info, argstr); break;
+    case cmdYUBIKEY:      err = cmd_yubikey (info, argstr); break;
 
     case cmdINVCMD:
     default:
@@ -3262,7 +3298,7 @@ interactive_loop (void)
           else
             {
               flush_keyblock_cache ();
-              err = scd_apdu (NULL, NULL);
+              err = scd_apdu (NULL, NULL, NULL, NULL);
             }
           break;
 
@@ -3318,6 +3354,7 @@ interactive_loop (void)
         case cmdKDFSETUP:  err = cmd_kdfsetup (info, argstr); break;
         case cmdKEYATTR:   err = cmd_keyattr (info, argstr); break;
         case cmdUIF:       err = cmd_uif (info, argstr); break;
+        case cmdYUBIKEY:   err = cmd_yubikey (info, argstr); break;
 
         case cmdINVCMD:
         default: