diff --git a/tools/Makefile.am b/tools/Makefile.am index ad0f223b4..69f4098ca 100644 --- a/tools/Makefile.am +++ b/tools/Makefile.am @@ -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) \ diff --git a/tools/card-call-scd.c b/tools/card-call-scd.c index 97fb6d9f1..8610da8b6 100644 --- a/tools/card-call-scd.c +++ b/tools/card-call-scd.c @@ -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); } diff --git a/tools/card-tool-misc.c b/tools/card-tool-misc.c index 06fcb6705..d0fb55dab 100644 --- a/tools/card-tool-misc.c +++ b/tools/card-tool-misc.c @@ -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 index 000000000..a03915abf --- /dev/null +++ b/tools/card-tool-yubikey.c @@ -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 . + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +#include +#include +#include +#include +#include +#include + +#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; +} diff --git a/tools/card-tool.h b/tools/card-tool.h index f49f253ce..f83ebf983 100644 --- a/tools/card-tool.h +++ b/tools/card-tool.h @@ -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*/ diff --git a/tools/gpg-card-tool.c b/tools/gpg-card-tool.c index 93153b12f..2bc2e5f90 100644 --- a/tools/gpg-card-tool.c +++ b/tools/gpg-card-tool.c @@ -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 args\n\n" + "Various commands pertaining to Yubikey tokens with 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; +} + + /* 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: