summaryrefslogtreecommitdiffstats
path: root/scd/ccid-driver.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:14:06 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 16:14:06 +0000
commiteee068778cb28ecf3c14e1bf843a95547d72c42d (patch)
tree0e07b30ddc5ea579d682d5dbe57998200d1c9ab7 /scd/ccid-driver.c
parentInitial commit. (diff)
downloadgnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.tar.xz
gnupg2-eee068778cb28ecf3c14e1bf843a95547d72c42d.zip
Adding upstream version 2.2.40.upstream/2.2.40
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'scd/ccid-driver.c')
-rw-r--r--scd/ccid-driver.c4080
1 files changed, 4080 insertions, 0 deletions
diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c
new file mode 100644
index 0000000..214165f
--- /dev/null
+++ b/scd/ccid-driver.c
@@ -0,0 +1,4080 @@
+/* ccid-driver.c - USB ChipCardInterfaceDevices driver
+ * Copyright (C) 2003, 2004, 2005, 2006, 2007
+ * 2008, 2009, 2013 Free Software Foundation, Inc.
+ * Written by Werner Koch.
+ *
+ * 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/>.
+ *
+ * ALTERNATIVELY, this file may be distributed under the terms of the
+ * following license, in which case the provisions of this license are
+ * required INSTEAD OF the GNU General Public License. If you wish to
+ * allow use of your version of this file only under the terms of the
+ * GNU General Public License, and not to allow others to use your
+ * version of this file under the terms of the following license,
+ * indicate your decision by deleting this paragraph and the license
+ * below.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, and the entire permission notice in its entirety,
+ * including the disclaimer of warranties.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ * 3. The name of the author may not be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
+ * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
+ * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+ * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
+ * OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+
+/* CCID (ChipCardInterfaceDevices) is a specification for accessing
+ smartcard via a reader connected to the USB.
+
+ This is a limited driver allowing to use some CCID drivers directly
+ without any other specila drivers. This is a fallback driver to be
+ used when nothing else works or the system should be kept minimal
+ for security reasons. It makes use of the libusb library to gain
+ portable access to USB.
+
+ This driver has been tested with the SCM SCR335 and SPR532
+ smartcard readers and requires that a reader implements APDU or
+ TPDU level exchange and does fully automatic initialization.
+*/
+
+#ifdef HAVE_CONFIG_H
+# include <config.h>
+#endif
+
+#if defined(HAVE_LIBUSB) || defined(TEST)
+
+#include <errno.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <time.h>
+#include <unistd.h>
+#ifdef HAVE_NPTH
+# include <npth.h>
+#endif /*HAVE_NPTH*/
+
+#include <libusb.h>
+
+#include "scdaemon.h"
+#include "iso7816.h"
+#define CCID_DRIVER_INCLUDE_USB_IDS 1
+#include "ccid-driver.h"
+
+#define DRVNAME "ccid-driver: "
+
+/* Max length of buffer with out CCID message header of 10-byte
+ Sending: 547 for RSA-4096 key import
+ APDU size = 540 (24+4+256+256)
+ command + lc + le = 4 + 3 + 0
+ Sending: write data object of cardholder certificate
+ APDU size = 2048
+ command + lc + le = 4 + 3 + 0
+ Receiving: 2048 for cardholder certificate
+*/
+#define CCID_MAX_BUF (2048+7+10)
+
+/* CCID command timeout. */
+#define CCID_CMD_TIMEOUT (5*1000)
+
+/* Number of supported devices. See MAX_READER in apdu.c. */
+#define CCID_MAX_DEVICE 4
+
+
+/* Depending on how this source is used we either define our error
+ output to go to stderr or to the GnuPG based logging functions. We
+ use the latter when GNUPG_MAJOR_VERSION or GNUPG_SCD_MAIN_HEADER
+ are defined. */
+#if defined(GNUPG_MAJOR_VERSION) || defined(GNUPG_SCD_MAIN_HEADER)
+
+#if defined(GNUPG_SCD_MAIN_HEADER)
+# include GNUPG_SCD_MAIN_HEADER
+#elif GNUPG_MAJOR_VERSION == 1 /* GnuPG Version is < 1.9. */
+# include "options.h"
+# include "util.h"
+# include "memory.h"
+# include "cardglue.h"
+# else /* This is the modularized GnuPG 1.9 or later. */
+# include "scdaemon.h"
+#endif
+
+
+# define DEBUGOUT(t) do { if (debug_level) \
+ log_debug (DRVNAME t); } while (0)
+# define DEBUGOUT_1(t,a) do { if (debug_level) \
+ log_debug (DRVNAME t,(a)); } while (0)
+# define DEBUGOUT_2(t,a,b) do { if (debug_level) \
+ log_debug (DRVNAME t,(a),(b)); } while (0)
+# define DEBUGOUT_3(t,a,b,c) do { if (debug_level) \
+ log_debug (DRVNAME t,(a),(b),(c));} while (0)
+# define DEBUGOUT_4(t,a,b,c,d) do { if (debug_level) \
+ log_debug (DRVNAME t,(a),(b),(c),(d));} while (0)
+# define DEBUGOUT_CONT(t) do { if (debug_level) \
+ log_printf (t); } while (0)
+# define DEBUGOUT_CONT_1(t,a) do { if (debug_level) \
+ log_printf (t,(a)); } while (0)
+# define DEBUGOUT_CONT_2(t,a,b) do { if (debug_level) \
+ log_printf (t,(a),(b)); } while (0)
+# define DEBUGOUT_CONT_3(t,a,b,c) do { if (debug_level) \
+ log_printf (t,(a),(b),(c)); } while (0)
+# define DEBUGOUT_LF() do { if (debug_level) \
+ log_printf ("\n"); } while (0)
+
+#else /* Other usage of this source - don't use gnupg specifics. */
+
+# define DEBUGOUT(t) do { if (debug_level) \
+ fprintf (stderr, DRVNAME t); } while (0)
+# define DEBUGOUT_1(t,a) do { if (debug_level) \
+ fprintf (stderr, DRVNAME t, (a)); } while (0)
+# define DEBUGOUT_2(t,a,b) do { if (debug_level) \
+ fprintf (stderr, DRVNAME t, (a), (b)); } while (0)
+# define DEBUGOUT_3(t,a,b,c) do { if (debug_level) \
+ fprintf (stderr, DRVNAME t, (a), (b), (c)); } while (0)
+# define DEBUGOUT_4(t,a,b,c,d) do { if (debug_level) \
+ fprintf (stderr, DRVNAME t, (a), (b), (c), (d));} while(0)
+# define DEBUGOUT_CONT(t) do { if (debug_level) \
+ fprintf (stderr, t); } while (0)
+# define DEBUGOUT_CONT_1(t,a) do { if (debug_level) \
+ fprintf (stderr, t, (a)); } while (0)
+# define DEBUGOUT_CONT_2(t,a,b) do { if (debug_level) \
+ fprintf (stderr, t, (a), (b)); } while (0)
+# define DEBUGOUT_CONT_3(t,a,b,c) do { if (debug_level) \
+ fprintf (stderr, t, (a), (b), (c)); } while (0)
+# define DEBUGOUT_LF() do { if (debug_level) \
+ putc ('\n', stderr); } while (0)
+
+#endif /* This source not used by scdaemon. */
+
+
+#ifndef EAGAIN
+#define EAGAIN EWOULDBLOCK
+#endif
+
+
+
+enum {
+ RDR_to_PC_NotifySlotChange= 0x50,
+ RDR_to_PC_HardwareError = 0x51,
+
+ PC_to_RDR_SetParameters = 0x61,
+ PC_to_RDR_IccPowerOn = 0x62,
+ PC_to_RDR_IccPowerOff = 0x63,
+ PC_to_RDR_GetSlotStatus = 0x65,
+ PC_to_RDR_Secure = 0x69,
+ PC_to_RDR_T0APDU = 0x6a,
+ PC_to_RDR_Escape = 0x6b,
+ PC_to_RDR_GetParameters = 0x6c,
+ PC_to_RDR_ResetParameters = 0x6d,
+ PC_to_RDR_IccClock = 0x6e,
+ PC_to_RDR_XfrBlock = 0x6f,
+ PC_to_RDR_Mechanical = 0x71,
+ PC_to_RDR_Abort = 0x72,
+ PC_to_RDR_SetDataRate = 0x73,
+
+ RDR_to_PC_DataBlock = 0x80,
+ RDR_to_PC_SlotStatus = 0x81,
+ RDR_to_PC_Parameters = 0x82,
+ RDR_to_PC_Escape = 0x83,
+ RDR_to_PC_DataRate = 0x84
+};
+
+
+/* Two macro to detect whether a CCID command has failed and to get
+ the error code. These macros assume that we can access the
+ mandatory first 10 bytes of a CCID message in BUF. */
+#define CCID_COMMAND_FAILED(buf) ((buf)[7] & 0x40)
+#define CCID_ERROR_CODE(buf) (((unsigned char *)(buf))[8])
+
+
+/* Store information on the driver's state. A pointer to such a
+ structure is used as handle for most functions. */
+struct ccid_driver_s
+{
+ libusb_device_handle *idev;
+ unsigned int bai;
+ unsigned short id_vendor;
+ unsigned short id_product;
+ int ifc_no;
+ int ep_bulk_out;
+ int ep_bulk_in;
+ int ep_intr;
+ int seqno;
+ unsigned char t1_ns;
+ unsigned char t1_nr;
+ unsigned char nonnull_nad;
+ int max_ifsd;
+ int max_ccid_msglen;
+ int ifsc;
+ unsigned char apdu_level:2; /* Reader supports short APDU level
+ exchange. With a value of 2 short
+ and extended level is supported.*/
+ unsigned int auto_voltage:1;
+ unsigned int auto_param:1;
+ unsigned int auto_pps:1;
+ unsigned int auto_ifsd:1;
+ unsigned int has_pinpad:2;
+ unsigned int enodev_seen:1;
+ int powered_off;
+
+ time_t last_progress; /* Last time we sent progress line. */
+
+ /* The progress callback and its first arg as supplied to
+ ccid_set_progress_cb. */
+ void (*progress_cb)(void *, const char *, int, int, int);
+ void *progress_cb_arg;
+
+ void (*prompt_cb)(void *, int);
+ void *prompt_cb_arg;
+
+ unsigned char intr_buf[64];
+ struct libusb_transfer *transfer;
+};
+
+
+/* Object to keep infos about found ccid devices. */
+struct ccid_dev_table {
+ int n; /* Index to ccid_usb_dev_list */
+ int interface_number;
+ int setting_number;
+ unsigned char *ifcdesc_extra;
+ int ep_bulk_out;
+ int ep_bulk_in;
+ int ep_intr;
+ size_t ifcdesc_extra_len;
+};
+
+
+static int initialized_usb; /* Tracks whether USB has been initialized. */
+static int debug_level; /* Flag to control the debug output.
+ 0 = No debugging
+ 1 = USB I/O info
+ 2 = Level 1 + T=1 protocol tracing
+ 3 = Level 2 + USB/I/O tracing of SlotStatus.
+ */
+static int ccid_usb_thread_is_alive;
+
+static libusb_device **ccid_usb_dev_list;
+static struct ccid_dev_table ccid_dev_table[CCID_MAX_DEVICE];
+
+
+
+static unsigned int compute_edc (const unsigned char *data, size_t datalen,
+ int use_crc);
+static int bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen,
+ int no_debug);
+static int bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length,
+ size_t *nread, int expected_type, int seqno, int timeout,
+ int no_debug);
+static int abort_cmd (ccid_driver_t handle, int seqno, int init);
+static int send_escape_cmd (ccid_driver_t handle, const unsigned char *data,
+ size_t datalen, unsigned char *result,
+ size_t resultmax, size_t *resultlen);
+
+
+static int
+map_libusb_error (int usberr)
+{
+ switch (usberr)
+ {
+ case 0: return 0;
+ case LIBUSB_ERROR_IO: return CCID_DRIVER_ERR_USB_IO;
+ case LIBUSB_ERROR_ACCESS: return CCID_DRIVER_ERR_USB_ACCESS;
+ case LIBUSB_ERROR_NO_DEVICE:return CCID_DRIVER_ERR_USB_NO_DEVICE;
+ case LIBUSB_ERROR_BUSY: return CCID_DRIVER_ERR_USB_BUSY;
+ case LIBUSB_ERROR_TIMEOUT: return CCID_DRIVER_ERR_USB_TIMEOUT;
+ case LIBUSB_ERROR_OVERFLOW: return CCID_DRIVER_ERR_USB_OVERFLOW;
+ }
+ return CCID_DRIVER_ERR_USB_OTHER;
+}
+
+
+/* Convert a little endian stored 4 byte value into an unsigned
+ integer. */
+static unsigned int
+convert_le_u32 (const unsigned char *buf)
+{
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | ((unsigned int)buf[3] << 24);
+}
+
+
+/* Convert a little endian stored 2 byte value into an unsigned
+ integer. */
+static unsigned int
+convert_le_u16 (const unsigned char *buf)
+{
+ return buf[0] | (buf[1] << 8);
+}
+
+static void
+set_msg_len (unsigned char *msg, unsigned int length)
+{
+ msg[1] = length;
+ msg[2] = length >> 8;
+ msg[3] = length >> 16;
+ msg[4] = length >> 24;
+}
+
+
+static void
+print_progress (ccid_driver_t handle)
+{
+ time_t ct = time (NULL);
+
+ /* We don't want to print progress lines too often. */
+ if (ct == handle->last_progress)
+ return;
+
+ if (handle->progress_cb)
+ handle->progress_cb (handle->progress_cb_arg, "card_busy", 'w', 0, 0);
+
+ handle->last_progress = ct;
+}
+
+
+
+/* Pint an error message for a failed CCID command including a textual
+ error code. MSG shall be the CCID message at a minimum of 10 bytes. */
+static void
+print_command_failed (const unsigned char *msg)
+{
+ const char *t;
+ char buffer[100];
+ int ec;
+
+ if (!debug_level)
+ return;
+
+ ec = CCID_ERROR_CODE (msg);
+ switch (ec)
+ {
+ case 0x00: t = "Command not supported"; break;
+
+ case 0xE0: t = "Slot busy"; break;
+ case 0xEF: t = "PIN cancelled"; break;
+ case 0xF0: t = "PIN timeout"; break;
+
+ case 0xF2: t = "Automatic sequence ongoing"; break;
+ case 0xF3: t = "Deactivated Protocol"; break;
+ case 0xF4: t = "Procedure byte conflict"; break;
+ case 0xF5: t = "ICC class not supported"; break;
+ case 0xF6: t = "ICC protocol not supported"; break;
+ case 0xF7: t = "Bad checksum in ATR"; break;
+ case 0xF8: t = "Bad TS in ATR"; break;
+
+ case 0xFB: t = "An all inclusive hardware error occurred"; break;
+ case 0xFC: t = "Overrun error while talking to the ICC"; break;
+ case 0xFD: t = "Parity error while talking to the ICC"; break;
+ case 0xFE: t = "CCID timed out while talking to the ICC"; break;
+ case 0xFF: t = "Host aborted the current activity"; break;
+
+ default:
+ if (ec > 0 && ec < 128)
+ sprintf (buffer, "Parameter error at offset %d", ec);
+ else
+ sprintf (buffer, "Error code %02X", ec);
+ t = buffer;
+ break;
+ }
+ DEBUGOUT_1 ("CCID command failed: %s\n", t);
+}
+
+
+static void
+print_pr_data (const unsigned char *data, size_t datalen, size_t off)
+{
+ int any = 0;
+
+ for (; off < datalen; off++)
+ {
+ if (!any || !(off % 16))
+ {
+ if (any)
+ DEBUGOUT_LF ();
+ DEBUGOUT_1 (" [%04lu] ", (unsigned long) off);
+ }
+ DEBUGOUT_CONT_1 (" %02X", data[off]);
+ any = 1;
+ }
+ if (any && (off % 16))
+ DEBUGOUT_LF ();
+}
+
+
+static void
+print_p2r_header (const char *name, const unsigned char *msg, size_t msglen)
+{
+ DEBUGOUT_1 ("%s:\n", name);
+ if (msglen < 7)
+ return;
+ DEBUGOUT_1 (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
+ DEBUGOUT_1 (" bSlot .............: %u\n", msg[5]);
+ DEBUGOUT_1 (" bSeq ..............: %u\n", msg[6]);
+}
+
+
+static void
+print_p2r_iccpoweron (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_IccPowerOn", msg, msglen);
+ if (msglen < 10)
+ return;
+ DEBUGOUT_2 (" bPowerSelect ......: 0x%02x (%s)\n", msg[7],
+ msg[7] == 0? "auto":
+ msg[7] == 1? "5.0 V":
+ msg[7] == 2? "3.0 V":
+ msg[7] == 3? "1.8 V":"");
+ print_pr_data (msg, msglen, 8);
+}
+
+
+static void
+print_p2r_iccpoweroff (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_IccPowerOff", msg, msglen);
+ print_pr_data (msg, msglen, 7);
+}
+
+
+static void
+print_p2r_getslotstatus (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_GetSlotStatus", msg, msglen);
+ print_pr_data (msg, msglen, 7);
+}
+
+
+static void
+print_p2r_xfrblock (const unsigned char *msg, size_t msglen)
+{
+ unsigned int val;
+
+ print_p2r_header ("PC_to_RDR_XfrBlock", msg, msglen);
+ if (msglen < 10)
+ return;
+ DEBUGOUT_1 (" bBWI ..............: 0x%02x\n", msg[7]);
+ val = convert_le_u16 (msg+8);
+ DEBUGOUT_2 (" wLevelParameter ...: 0x%04x%s\n", val,
+ val == 1? " (continued)":
+ val == 2? " (continues+ends)":
+ val == 3? " (continues+continued)":
+ val == 16? " (DataBlock-expected)":"");
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_p2r_getparameters (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_GetParameters", msg, msglen);
+ print_pr_data (msg, msglen, 7);
+}
+
+
+static void
+print_p2r_resetparameters (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_ResetParameters", msg, msglen);
+ print_pr_data (msg, msglen, 7);
+}
+
+
+static void
+print_p2r_setparameters (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_SetParameters", msg, msglen);
+ if (msglen < 10)
+ return;
+ DEBUGOUT_1 (" bProtocolNum ......: 0x%02x\n", msg[7]);
+ print_pr_data (msg, msglen, 8);
+}
+
+
+static void
+print_p2r_escape (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_Escape", msg, msglen);
+ print_pr_data (msg, msglen, 7);
+}
+
+
+static void
+print_p2r_iccclock (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_IccClock", msg, msglen);
+ if (msglen < 10)
+ return;
+ DEBUGOUT_1 (" bClockCommand .....: 0x%02x\n", msg[7]);
+ print_pr_data (msg, msglen, 8);
+}
+
+
+static void
+print_p2r_to0apdu (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_T0APDU", msg, msglen);
+ if (msglen < 10)
+ return;
+ DEBUGOUT_1 (" bmChanges .........: 0x%02x\n", msg[7]);
+ DEBUGOUT_1 (" bClassGetResponse .: 0x%02x\n", msg[8]);
+ DEBUGOUT_1 (" bClassEnvelope ....: 0x%02x\n", msg[9]);
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_p2r_secure (const unsigned char *msg, size_t msglen)
+{
+ unsigned int val;
+
+ print_p2r_header ("PC_to_RDR_Secure", msg, msglen);
+ if (msglen < 10)
+ return;
+ DEBUGOUT_1 (" bBMI ..............: 0x%02x\n", msg[7]);
+ val = convert_le_u16 (msg+8);
+ DEBUGOUT_2 (" wLevelParameter ...: 0x%04x%s\n", val,
+ val == 1? " (continued)":
+ val == 2? " (continues+ends)":
+ val == 3? " (continues+continued)":
+ val == 16? " (DataBlock-expected)":"");
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_p2r_mechanical (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_Mechanical", msg, msglen);
+ if (msglen < 10)
+ return;
+ DEBUGOUT_1 (" bFunction .........: 0x%02x\n", msg[7]);
+ print_pr_data (msg, msglen, 8);
+}
+
+
+static void
+print_p2r_abort (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_Abort", msg, msglen);
+ print_pr_data (msg, msglen, 7);
+}
+
+
+static void
+print_p2r_setdatarate (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("PC_to_RDR_SetDataRate", msg, msglen);
+ if (msglen < 10)
+ return;
+ print_pr_data (msg, msglen, 7);
+}
+
+
+static void
+print_p2r_unknown (const unsigned char *msg, size_t msglen)
+{
+ print_p2r_header ("Unknown PC_to_RDR command", msg, msglen);
+ if (msglen < 10)
+ return;
+ print_pr_data (msg, msglen, 0);
+}
+
+
+static void
+print_r2p_header (const char *name, const unsigned char *msg, size_t msglen)
+{
+ DEBUGOUT_1 ("%s:\n", name);
+ if (msglen < 9)
+ return;
+ DEBUGOUT_1 (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
+ DEBUGOUT_1 (" bSlot .............: %u\n", msg[5]);
+ DEBUGOUT_1 (" bSeq ..............: %u\n", msg[6]);
+ DEBUGOUT_1 (" bStatus ...........: %u\n", msg[7]);
+ if (msg[8])
+ DEBUGOUT_1 (" bError ............: %u\n", msg[8]);
+}
+
+
+static void
+print_r2p_datablock (const unsigned char *msg, size_t msglen)
+{
+ print_r2p_header ("RDR_to_PC_DataBlock", msg, msglen);
+ if (msglen < 10)
+ return;
+ if (msg[9])
+ DEBUGOUT_2 (" bChainParameter ...: 0x%02x%s\n", msg[9],
+ msg[9] == 1? " (continued)":
+ msg[9] == 2? " (continues+ends)":
+ msg[9] == 3? " (continues+continued)":
+ msg[9] == 16? " (XferBlock-expected)":"");
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_r2p_slotstatus (const unsigned char *msg, size_t msglen)
+{
+ print_r2p_header ("RDR_to_PC_SlotStatus", msg, msglen);
+ if (msglen < 10)
+ return;
+ DEBUGOUT_2 (" bClockStatus ......: 0x%02x%s\n", msg[9],
+ msg[9] == 0? " (running)":
+ msg[9] == 1? " (stopped-L)":
+ msg[9] == 2? " (stopped-H)":
+ msg[9] == 3? " (stopped)":"");
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_r2p_parameters (const unsigned char *msg, size_t msglen)
+{
+ print_r2p_header ("RDR_to_PC_Parameters", msg, msglen);
+ if (msglen < 10)
+ return;
+
+ DEBUGOUT_1 (" protocol ..........: T=%d\n", msg[9]);
+ if (msglen == 17 && msg[9] == 1)
+ {
+ /* Protocol T=1. */
+ DEBUGOUT_1 (" bmFindexDindex ....: %02X\n", msg[10]);
+ DEBUGOUT_1 (" bmTCCKST1 .........: %02X\n", msg[11]);
+ DEBUGOUT_1 (" bGuardTimeT1 ......: %02X\n", msg[12]);
+ DEBUGOUT_1 (" bmWaitingIntegersT1: %02X\n", msg[13]);
+ DEBUGOUT_1 (" bClockStop ........: %02X\n", msg[14]);
+ DEBUGOUT_1 (" bIFSC .............: %d\n", msg[15]);
+ DEBUGOUT_1 (" bNadValue .........: %d\n", msg[16]);
+ }
+ else
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_r2p_escape (const unsigned char *msg, size_t msglen)
+{
+ print_r2p_header ("RDR_to_PC_Escape", msg, msglen);
+ if (msglen < 10)
+ return;
+ DEBUGOUT_1 (" buffer[9] .........: %02X\n", msg[9]);
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_r2p_datarate (const unsigned char *msg, size_t msglen)
+{
+ print_r2p_header ("RDR_to_PC_DataRate", msg, msglen);
+ if (msglen < 10)
+ return;
+ if (msglen >= 18)
+ {
+ DEBUGOUT_1 (" dwClockFrequency ..: %u\n", convert_le_u32 (msg+10));
+ DEBUGOUT_1 (" dwDataRate ..... ..: %u\n", convert_le_u32 (msg+14));
+ print_pr_data (msg, msglen, 18);
+ }
+ else
+ print_pr_data (msg, msglen, 10);
+}
+
+
+static void
+print_r2p_unknown (const unsigned char *msg, size_t msglen)
+{
+ print_r2p_header ("Unknown RDR_to_PC command", msg, msglen);
+ if (msglen < 10)
+ return;
+ DEBUGOUT_1 (" bMessageType ......: %02X\n", msg[0]);
+ DEBUGOUT_1 (" buffer[9] .........: %02X\n", msg[9]);
+ print_pr_data (msg, msglen, 10);
+}
+
+
+/* Parse a CCID descriptor, optionally print all available features
+ and test whether this reader is usable by this driver. Returns 0
+ if it is usable.
+
+ Note, that this code is based on the one in lsusb.c of the
+ usb-utils package, I wrote on 2003-09-01. -wk. */
+static int
+parse_ccid_descriptor (ccid_driver_t handle, unsigned short bcd_device,
+ const unsigned char *buf, size_t buflen)
+{
+ unsigned int i;
+ unsigned int us;
+ int have_t1 = 0, have_tpdu=0;
+
+ handle->nonnull_nad = 0;
+ handle->auto_ifsd = 0;
+ handle->max_ifsd = 32;
+ handle->has_pinpad = 0;
+ handle->apdu_level = 0;
+ handle->auto_voltage = 0;
+ handle->auto_param = 0;
+ handle->auto_pps = 0;
+ DEBUGOUT_3 ("idVendor: %04X idProduct: %04X bcdDevice: %04X\n",
+ handle->id_vendor, handle->id_product, bcd_device);
+ if (buflen < 54 || buf[0] < 54)
+ {
+ DEBUGOUT ("CCID device descriptor is too short\n");
+ return -1;
+ }
+
+ DEBUGOUT ("ChipCard Interface Descriptor:\n");
+ DEBUGOUT_1 (" bLength %5u\n", buf[0]);
+ DEBUGOUT_1 (" bDescriptorType %5u\n", buf[1]);
+ DEBUGOUT_2 (" bcdCCID %2x.%02x", buf[3], buf[2]);
+ if (buf[3] != 1 || buf[2] != 0)
+ DEBUGOUT_CONT(" (Warning: Only accurate for version 1.0)");
+ DEBUGOUT_LF ();
+
+ DEBUGOUT_1 (" nMaxSlotIndex %5u\n", buf[4]);
+ DEBUGOUT_2 (" bVoltageSupport %5u %s\n",
+ buf[5], (buf[5] == 1? "5.0V" : buf[5] == 2? "3.0V"
+ : buf[5] == 3? "1.8V":"?"));
+
+ us = convert_le_u32 (buf+6);
+ DEBUGOUT_1 (" dwProtocols %5u ", us);
+ if ((us & 1))
+ DEBUGOUT_CONT (" T=0");
+ if ((us & 2))
+ {
+ DEBUGOUT_CONT (" T=1");
+ have_t1 = 1;
+ }
+ if ((us & ~3))
+ DEBUGOUT_CONT (" (Invalid values detected)");
+ DEBUGOUT_LF ();
+
+ us = convert_le_u32(buf+10);
+ DEBUGOUT_1 (" dwDefaultClock %5u\n", us);
+ us = convert_le_u32(buf+14);
+ DEBUGOUT_1 (" dwMaxiumumClock %5u\n", us);
+ DEBUGOUT_1 (" bNumClockSupported %5u\n", buf[18]);
+ us = convert_le_u32(buf+19);
+ DEBUGOUT_1 (" dwDataRate %7u bps\n", us);
+ us = convert_le_u32(buf+23);
+ DEBUGOUT_1 (" dwMaxDataRate %7u bps\n", us);
+ DEBUGOUT_1 (" bNumDataRatesSupp. %5u\n", buf[27]);
+
+ us = convert_le_u32(buf+28);
+ DEBUGOUT_1 (" dwMaxIFSD %5u\n", us);
+ handle->max_ifsd = us;
+
+ us = convert_le_u32(buf+32);
+ DEBUGOUT_1 (" dwSyncProtocols %08X ", us);
+ if ((us&1))
+ DEBUGOUT_CONT ( " 2-wire");
+ if ((us&2))
+ DEBUGOUT_CONT ( " 3-wire");
+ if ((us&4))
+ DEBUGOUT_CONT ( " I2C");
+ DEBUGOUT_LF ();
+
+ us = convert_le_u32(buf+36);
+ DEBUGOUT_1 (" dwMechanical %08X ", us);
+ if ((us & 1))
+ DEBUGOUT_CONT (" accept");
+ if ((us & 2))
+ DEBUGOUT_CONT (" eject");
+ if ((us & 4))
+ DEBUGOUT_CONT (" capture");
+ if ((us & 8))
+ DEBUGOUT_CONT (" lock");
+ DEBUGOUT_LF ();
+
+ us = convert_le_u32(buf+40);
+ DEBUGOUT_1 (" dwFeatures %08X\n", us);
+ if ((us & 0x0002))
+ {
+ DEBUGOUT (" Auto configuration based on ATR (assumes auto voltage)\n");
+ handle->auto_voltage = 1;
+ }
+ if ((us & 0x0004))
+ DEBUGOUT (" Auto activation on insert\n");
+ if ((us & 0x0008))
+ {
+ DEBUGOUT (" Auto voltage selection\n");
+ handle->auto_voltage = 1;
+ }
+ if ((us & 0x0010))
+ DEBUGOUT (" Auto clock change\n");
+ if ((us & 0x0020))
+ DEBUGOUT (" Auto baud rate change\n");
+ if ((us & 0x0040))
+ {
+ DEBUGOUT (" Auto parameter negotiation made by CCID\n");
+ handle->auto_param = 1;
+ }
+ else if ((us & 0x0080))
+ {
+ DEBUGOUT (" Auto PPS made by CCID\n");
+ handle->auto_pps = 1;
+ }
+ if ((us & (0x0040 | 0x0080)) == (0x0040 | 0x0080))
+ DEBUGOUT (" WARNING: conflicting negotiation features\n");
+
+ if ((us & 0x0100))
+ DEBUGOUT (" CCID can set ICC in clock stop mode\n");
+ if ((us & 0x0200))
+ {
+ DEBUGOUT (" NAD value other than 0x00 accepted\n");
+ handle->nonnull_nad = 1;
+ }
+ if ((us & 0x0400))
+ {
+ DEBUGOUT (" Auto IFSD exchange\n");
+ handle->auto_ifsd = 1;
+ }
+
+ if ((us & 0x00010000))
+ {
+ DEBUGOUT (" TPDU level exchange\n");
+ have_tpdu = 1;
+ }
+ else if ((us & 0x00020000))
+ {
+ DEBUGOUT (" Short APDU level exchange\n");
+ handle->apdu_level = 1;
+ }
+ else if ((us & 0x00040000))
+ {
+ DEBUGOUT (" Short and extended APDU level exchange\n");
+ handle->apdu_level = 2;
+ }
+ else if ((us & 0x00070000))
+ DEBUGOUT (" WARNING: conflicting exchange levels\n");
+
+ us = convert_le_u32(buf+44);
+ DEBUGOUT_1 (" dwMaxCCIDMsgLen %5u\n", us);
+ handle->max_ccid_msglen = us;
+
+ DEBUGOUT ( " bClassGetResponse ");
+ if (buf[48] == 0xff)
+ DEBUGOUT_CONT ("echo\n");
+ else
+ DEBUGOUT_CONT_1 (" %02X\n", buf[48]);
+
+ DEBUGOUT ( " bClassEnvelope ");
+ if (buf[49] == 0xff)
+ DEBUGOUT_CONT ("echo\n");
+ else
+ DEBUGOUT_CONT_1 (" %02X\n", buf[48]);
+
+ DEBUGOUT ( " wlcdLayout ");
+ if (!buf[50] && !buf[51])
+ DEBUGOUT_CONT ("none\n");
+ else
+ DEBUGOUT_CONT_2 ("%u cols %u lines\n", buf[50], buf[51]);
+
+ DEBUGOUT_1 (" bPINSupport %5u ", buf[52]);
+ if ((buf[52] & 1))
+ {
+ DEBUGOUT_CONT ( " verification");
+ handle->has_pinpad |= 1;
+ }
+ if ((buf[52] & 2))
+ {
+ DEBUGOUT_CONT ( " modification");
+ handle->has_pinpad |= 2;
+ }
+ DEBUGOUT_LF ();
+
+ DEBUGOUT_1 (" bMaxCCIDBusySlots %5u\n", buf[53]);
+
+ if (buf[0] > 54)
+ {
+ DEBUGOUT (" junk ");
+ for (i=54; i < buf[0]-54; i++)
+ DEBUGOUT_CONT_1 (" %02X", buf[i]);
+ DEBUGOUT_LF ();
+ }
+
+ if (!have_t1 || !(have_tpdu || handle->apdu_level))
+ {
+ DEBUGOUT ("this drivers requires that the reader supports T=1, "
+ "TPDU or APDU level exchange - this is not available\n");
+ return -1;
+ }
+
+
+ /* SCM drivers get stuck in their internal USB stack if they try to
+ send a frame of n*wMaxPacketSize back to us. Given that
+ wMaxPacketSize is 64 for these readers we set the IFSD to a value
+ lower than that:
+ 64 - 10 CCID header - 4 T1frame - 2 reserved = 48
+ Product Ids:
+ 0xe001 - SCR 331
+ 0x5111 - SCR 331-DI
+ 0x5115 - SCR 335
+ 0xe003 - SPR 532
+ The
+ 0x5117 - SCR 3320 USB ID-000 reader
+ seems to be very slow but enabling this workaround boosts the
+ performance to a more or less acceptable level (tested by David).
+
+ */
+ if (handle->id_vendor == VENDOR_SCM
+ && handle->max_ifsd > 48
+ && ( (handle->id_product == SCM_SCR331 && bcd_device < 0x0516)
+ ||(handle->id_product == SCM_SCR331DI && bcd_device < 0x0620)
+ ||(handle->id_product == SCM_SCR335 && bcd_device < 0x0514)
+ ||(handle->id_product == SCM_SPR532 && bcd_device < 0x0504)
+ ||(handle->id_product == SCM_SCR3320 && bcd_device < 0x0522)
+ ))
+ {
+ DEBUGOUT ("enabling workaround for buggy SCM readers\n");
+ handle->max_ifsd = 48;
+ }
+
+ if (handle->id_vendor == VENDOR_GEMPC)
+ {
+ DEBUGOUT ("enabling product quirk: disable non-null NAD\n");
+ handle->nonnull_nad = 0;
+ }
+
+ return 0;
+}
+
+
+static char *
+get_escaped_usb_string (libusb_device_handle *idev, int idx,
+ const char *prefix, const char *suffix)
+{
+ int rc;
+ unsigned char buf[280];
+ unsigned char *s;
+ unsigned int langid;
+ size_t i, n, len;
+ char *result;
+
+ if (!idx)
+ return NULL;
+
+ /* Fixme: The next line is for the current Valgrid without support
+ for USB IOCTLs. */
+ memset (buf, 0, sizeof buf);
+
+ /* First get the list of supported languages and use the first one.
+ If we do don't find it we try to use English. Note that this is
+ all in a 2 bute Unicode encoding using little endian. */
+#ifdef USE_NPTH
+ npth_unprotect ();
+#endif
+ rc = libusb_control_transfer (idev, LIBUSB_ENDPOINT_IN,
+ LIBUSB_REQUEST_GET_DESCRIPTOR,
+ (LIBUSB_DT_STRING << 8), 0,
+ buf, sizeof buf, 1000 /* ms timeout */);
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+ if (rc < 4)
+ langid = 0x0409; /* English. */
+ else
+ langid = (buf[3] << 8) | buf[2];
+
+#ifdef USE_NPTH
+ npth_unprotect ();
+#endif
+ rc = libusb_control_transfer (idev, LIBUSB_ENDPOINT_IN,
+ LIBUSB_REQUEST_GET_DESCRIPTOR,
+ (LIBUSB_DT_STRING << 8) + idx, langid,
+ buf, sizeof buf, 1000 /* ms timeout */);
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+ if (rc < 2 || buf[1] != LIBUSB_DT_STRING)
+ return NULL; /* Error or not a string. */
+ len = buf[0];
+ if (len > rc)
+ return NULL; /* Larger than our buffer. */
+
+ for (s=buf+2, i=2, n=0; i+1 < len; i += 2, s += 2)
+ {
+ if (s[1])
+ n++; /* High byte set. */
+ else if (*s <= 0x20 || *s >= 0x7f || *s == '%' || *s == ':')
+ n += 3 ;
+ else
+ n++;
+ }
+
+ result = malloc (strlen (prefix) + n + strlen (suffix) + 1);
+ if (!result)
+ return NULL;
+
+ strcpy (result, prefix);
+ n = strlen (prefix);
+ for (s=buf+2, i=2; i+1 < len; i += 2, s += 2)
+ {
+ if (s[1])
+ result[n++] = '\xff'; /* High byte set. */
+ else if (*s <= 0x20 || *s >= 0x7f || *s == '%' || *s == ':')
+ {
+ sprintf (result+n, "%%%02X", *s);
+ n += 3;
+ }
+ else
+ result[n++] = *s;
+ }
+ strcpy (result+n, suffix);
+
+ return result;
+}
+
+/* This function creates an reader id to be used to find the same
+ physical reader after a reset. It returns an allocated and possibly
+ percent escaped string or NULL if not enough memory is available. */
+static char *
+make_reader_id (libusb_device_handle *idev,
+ unsigned int vendor, unsigned int product,
+ unsigned char serialno_index)
+{
+ char *rid;
+ char prefix[20];
+
+ sprintf (prefix, "%04X:%04X:", (vendor & 0xffff), (product & 0xffff));
+ rid = get_escaped_usb_string (idev, serialno_index, prefix, ":0");
+ if (!rid)
+ {
+ rid = malloc (strlen (prefix) + 3 + 1);
+ if (!rid)
+ return NULL;
+ strcpy (rid, prefix);
+ strcat (rid, "X:0");
+ }
+ return rid;
+}
+
+
+/* Helper to find the endpoint from an interface descriptor. */
+static int
+find_endpoint (const struct libusb_interface_descriptor *ifcdesc, int mode)
+{
+ int no;
+ int want_bulk_in = 0;
+
+ if (mode == 1)
+ want_bulk_in = 0x80;
+ for (no=0; no < ifcdesc->bNumEndpoints; no++)
+ {
+ const struct libusb_endpoint_descriptor *ep = ifcdesc->endpoint + no;
+ if (ep->bDescriptorType != LIBUSB_DT_ENDPOINT)
+ ;
+ else if (mode == 2
+ && ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK)
+ == LIBUSB_TRANSFER_TYPE_INTERRUPT)
+ && (ep->bEndpointAddress & 0x80))
+ return ep->bEndpointAddress;
+ else if ((mode == 0 || mode == 1)
+ && ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK)
+ == LIBUSB_TRANSFER_TYPE_BULK)
+ && (ep->bEndpointAddress & 0x80) == want_bulk_in)
+ return ep->bEndpointAddress;
+ }
+
+ return -1;
+}
+
+
+/* Helper for scan_devices. This function returns true if a
+ requested device has been found or the caller should stop scanning
+ for other reasons. */
+static void
+scan_usb_device (int *count, char **rid_list, struct libusb_device *dev)
+{
+ int ifc_no;
+ int set_no;
+ const struct libusb_interface_descriptor *ifcdesc;
+ char *rid;
+ libusb_device_handle *idev = NULL;
+ int err;
+ struct libusb_config_descriptor *config;
+ struct libusb_device_descriptor desc;
+ char *p;
+
+ err = libusb_get_device_descriptor (dev, &desc);
+ if (err)
+ return;
+
+ err = libusb_get_active_config_descriptor (dev, &config);
+ if (err)
+ return;
+
+ for (ifc_no=0; ifc_no < config->bNumInterfaces; ifc_no++)
+ for (set_no=0; set_no < config->interface[ifc_no].num_altsetting; set_no++)
+ {
+ ifcdesc = (config->interface[ifc_no].altsetting + set_no);
+ /* The second condition is for older SCM SPR 532 who did
+ not know about the assigned CCID class. The third
+ condition does the same for a Cherry SmartTerminal
+ ST-2000. Instead of trying to interpret the strings
+ we simply check the product ID. */
+ if (ifcdesc && ifcdesc->extra
+ && ((ifcdesc->bInterfaceClass == 11
+ && ifcdesc->bInterfaceSubClass == 0
+ && ifcdesc->bInterfaceProtocol == 0)
+ || (ifcdesc->bInterfaceClass == 255
+ && desc.idVendor == VENDOR_SCM
+ && desc.idProduct == SCM_SPR532)
+ || (ifcdesc->bInterfaceClass == 255
+ && desc.idVendor == VENDOR_CHERRY
+ && desc.idProduct == CHERRY_ST2000)))
+ {
+ ++*count;
+
+ err = libusb_open (dev, &idev);
+ if (err)
+ {
+ DEBUGOUT_1 ("usb_open failed: %s\n", libusb_error_name (err));
+ continue; /* with next setting. */
+ }
+
+ rid = make_reader_id (idev, desc.idVendor, desc.idProduct,
+ desc.iSerialNumber);
+ if (!rid)
+ {
+ libusb_free_config_descriptor (config);
+ return;
+ }
+
+ /* We are collecting infos about all available CCID
+ readers. Store them and continue. */
+ DEBUGOUT_2 ("found CCID reader %d (ID=%s)\n", *count, rid);
+ p = malloc ((*rid_list? strlen (*rid_list):0) + 1
+ + strlen (rid) + 1);
+ if (p)
+ {
+ *p = 0;
+ if (*rid_list)
+ {
+ strcat (p, *rid_list);
+ free (*rid_list);
+ }
+ strcat (p, rid);
+ strcat (p, "\n");
+ *rid_list = p;
+ }
+ else /* Out of memory. */
+ {
+ libusb_free_config_descriptor (config);
+ free (rid);
+ return;
+ }
+
+ free (rid);
+ libusb_close (idev);
+ idev = NULL;
+ }
+ }
+
+ libusb_free_config_descriptor (config);
+}
+
+/* Scan all CCID devices.
+
+ The function returns 0 if a reader has been found or when a scan
+ returned without error.
+
+ R_RID should be the address where to store the list of reader_ids
+ we found. If on return this list is empty, no CCID device has been
+ found; otherwise it points to an allocated linked list of reader
+ IDs.
+*/
+static int
+scan_devices (char **r_rid)
+{
+ char *rid_list = NULL;
+ int count = 0;
+ libusb_device **dev_list = NULL;
+ libusb_device *dev;
+ int i;
+ ssize_t n;
+
+ /* Set return values to a default. */
+ if (r_rid)
+ *r_rid = NULL;
+
+ n = libusb_get_device_list (NULL, &dev_list);
+
+ for (i = 0; i < n; i++)
+ {
+ dev = dev_list[i];
+ scan_usb_device (&count, &rid_list, dev);
+ }
+
+ libusb_free_device_list (dev_list, 1);
+
+ *r_rid = rid_list;
+ return 0;
+}
+
+
+/* Set the level of debugging to LEVEL and return the old level. -1
+ just returns the old level. A level of 0 disables debugging, 1
+ enables debugging, 2 enables additional tracing of the T=1
+ protocol, 3 additionally enables debugging for GetSlotStatus, other
+ values are not yet defined.
+
+ Note that libusb may provide its own debugging feature which is
+ enabled by setting the envvar USB_DEBUG. */
+int
+ccid_set_debug_level (int level)
+{
+ int old = debug_level;
+ if (level != -1)
+ debug_level = level;
+ return old;
+}
+
+
+char *
+ccid_get_reader_list (void)
+{
+ char *reader_list;
+
+ if (!initialized_usb)
+ {
+ int rc;
+ if ((rc = libusb_init (NULL)))
+ {
+ DEBUGOUT_1 ("usb_init failed: %s.\n", libusb_error_name (rc));
+ return NULL;
+ }
+ initialized_usb = 1;
+ }
+
+ if (scan_devices (&reader_list))
+ return NULL; /* Error. */
+ return reader_list;
+}
+
+
+/* Vendor specific custom initialization. */
+static int
+ccid_vendor_specific_init (ccid_driver_t handle)
+{
+ int r = 0;
+
+ if (handle->id_vendor == VENDOR_VEGA && handle->id_product == VEGA_ALPHA)
+ {
+ /*
+ * Vega alpha has a feature to show retry counter on the pinpad
+ * display. But it assumes that the card returns the value of
+ * retry counter by VERIFY with empty data (return code of
+ * 63Cx). Unfortunately, existing OpenPGP cards don't support
+ * VERIFY command with empty data. This vendor specific command
+ * sequence is to disable the feature.
+ */
+ const unsigned char cmd[] = { '\xb5', '\x01', '\x00', '\x03', '\x00' };
+
+ r = send_escape_cmd (handle, cmd, sizeof (cmd), NULL, 0, NULL);
+ }
+ else if (handle->id_vendor == VENDOR_SCM && handle->id_product == SCM_SPR532)
+ {
+ /*
+ * It seems that SEQ may be out of sync between host and the card reader,
+ * and SET_INTERFACE doesn't reset it. Make sure it works at the init.
+ */
+ abort_cmd (handle, 0, 1);
+ }
+
+ if (r != 0 && r != CCID_DRIVER_ERR_CARD_INACTIVE
+ && r != CCID_DRIVER_ERR_NO_CARD)
+ return r;
+ else
+ return 0;
+}
+
+
+static int
+ccid_vendor_specific_setup (ccid_driver_t handle)
+{
+ if (handle->id_vendor == VENDOR_SCM && handle->id_product == SCM_SPR532)
+ {
+#ifdef USE_NPTH
+ npth_unprotect ();
+#endif
+ libusb_clear_halt (handle->idev, handle->ep_intr);
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+ }
+ return 0;
+}
+
+
+static int
+ccid_vendor_specific_pinpad_setup (ccid_driver_t handle)
+{
+ if (handle->id_vendor == VENDOR_SCM && handle->id_product == SCM_SPR532)
+ {
+ DEBUGOUT ("sending escape sequence to switch to a case 1 APDU\n");
+ send_escape_cmd (handle, (const unsigned char*)"\x80\x02\x00", 3,
+ NULL, 0, NULL);
+ }
+ return 0;
+}
+
+
+gpg_error_t
+ccid_dev_scan (int *idx_max_p, void **t_p)
+{
+ ssize_t n;
+ libusb_device *dev;
+ int i;
+ int ifc_no;
+ int set_no;
+ int idx = 0;
+ int err = 0;
+
+ *idx_max_p = 0;
+ *t_p = NULL;
+
+ if (!initialized_usb)
+ {
+ int rc;
+ if ((rc = libusb_init (NULL)))
+ {
+ DEBUGOUT_1 ("usb_init failed: %s.\n", libusb_error_name (rc));
+ return gpg_error (GPG_ERR_ENODEV);
+ }
+ initialized_usb = 1;
+ }
+
+ n = libusb_get_device_list (NULL, &ccid_usb_dev_list);
+ for (i = 0; i < n; i++)
+ {
+ struct libusb_config_descriptor *config;
+ struct libusb_device_descriptor desc;
+
+ dev = ccid_usb_dev_list[i];
+
+ if (libusb_get_device_descriptor (dev, &desc))
+ continue;
+
+ if (libusb_get_active_config_descriptor (dev, &config))
+ continue;
+
+ for (ifc_no=0; ifc_no < config->bNumInterfaces; ifc_no++)
+ for (set_no=0; set_no < config->interface[ifc_no].num_altsetting;
+ set_no++)
+ {
+ const struct libusb_interface_descriptor *ifcdesc;
+
+ ifcdesc = &config->interface[ifc_no].altsetting[set_no];
+ /* The second condition is for older SCM SPR 532 who did
+ not know about the assigned CCID class. The third
+ condition does the same for a Cherry SmartTerminal
+ ST-2000. Instead of trying to interpret the strings
+ we simply check the product ID. */
+ if (ifcdesc && ifcdesc->extra
+ && ((ifcdesc->bInterfaceClass == 11
+ && ifcdesc->bInterfaceSubClass == 0
+ && ifcdesc->bInterfaceProtocol == 0)
+ || (ifcdesc->bInterfaceClass == 255
+ && desc.idVendor == VENDOR_SCM
+ && desc.idProduct == SCM_SPR532)
+ || (ifcdesc->bInterfaceClass == 255
+ && desc.idVendor == VENDOR_CHERRY
+ && desc.idProduct == CHERRY_ST2000)))
+ {
+ /* Found a reader. */
+ unsigned char *ifcdesc_extra;
+
+ ifcdesc_extra = malloc (ifcdesc->extra_length);
+ if (!ifcdesc_extra)
+ {
+ err = gpg_error_from_syserror ();
+ libusb_free_config_descriptor (config);
+ goto scan_finish;
+ }
+ memcpy (ifcdesc_extra, ifcdesc->extra, ifcdesc->extra_length);
+
+ ccid_dev_table[idx].n = i;
+ ccid_dev_table[idx].interface_number = ifc_no;
+ ccid_dev_table[idx].setting_number = set_no;
+ ccid_dev_table[idx].ifcdesc_extra = ifcdesc_extra;
+ ccid_dev_table[idx].ifcdesc_extra_len = ifcdesc->extra_length;
+ ccid_dev_table[idx].ep_bulk_out = find_endpoint (ifcdesc, 0);
+ ccid_dev_table[idx].ep_bulk_in = find_endpoint (ifcdesc, 1);
+ ccid_dev_table[idx].ep_intr = find_endpoint (ifcdesc, 2);
+
+ idx++;
+ if (idx >= CCID_MAX_DEVICE)
+ {
+ libusb_free_config_descriptor (config);
+ err = 0;
+ goto scan_finish;
+ }
+ }
+ }
+
+ libusb_free_config_descriptor (config);
+ }
+
+ scan_finish:
+
+ if (err)
+ {
+ for (i = 0; i < idx; i++)
+ {
+ free (ccid_dev_table[i].ifcdesc_extra);
+ ccid_dev_table[i].n = 0;
+ ccid_dev_table[i].interface_number = 0;
+ ccid_dev_table[i].setting_number = 0;
+ ccid_dev_table[i].ifcdesc_extra = NULL;
+ ccid_dev_table[i].ifcdesc_extra_len = 0;
+ ccid_dev_table[i].ep_bulk_out = 0;
+ ccid_dev_table[i].ep_bulk_in = 0;
+ ccid_dev_table[i].ep_intr = 0;
+ }
+ libusb_free_device_list (ccid_usb_dev_list, 1);
+ ccid_usb_dev_list = NULL;
+ }
+ else
+ {
+ *idx_max_p = idx;
+ if (idx)
+ *t_p = ccid_dev_table;
+ else
+ *t_p = NULL;
+ }
+
+ return err;
+}
+
+void
+ccid_dev_scan_finish (void *tbl0, int max)
+{
+ int i;
+ struct ccid_dev_table *tbl = tbl0;
+
+ for (i = 0; i < max; i++)
+ {
+ free (tbl[i].ifcdesc_extra);
+ tbl[i].n = 0;
+ tbl[i].interface_number = 0;
+ tbl[i].setting_number = 0;
+ tbl[i].ifcdesc_extra = NULL;
+ tbl[i].ifcdesc_extra_len = 0;
+ tbl[i].ep_bulk_out = 0;
+ tbl[i].ep_bulk_in = 0;
+ tbl[i].ep_intr = 0;
+ }
+ libusb_free_device_list (ccid_usb_dev_list, 1);
+ ccid_usb_dev_list = NULL;
+}
+
+unsigned int
+ccid_get_BAI (int idx, void *tbl0)
+{
+ int n;
+ int bus, addr, intf;
+ unsigned int bai;
+ libusb_device *dev;
+ struct ccid_dev_table *tbl = tbl0;
+
+ n = tbl[idx].n;
+ dev = ccid_usb_dev_list[n];
+
+ bus = libusb_get_bus_number (dev);
+ addr = libusb_get_device_address (dev);
+ intf = tbl[idx].interface_number;
+ bai = (bus << 16) | (addr << 8) | intf;
+
+ return bai;
+}
+
+int
+ccid_compare_BAI (ccid_driver_t handle, unsigned int bai)
+{
+ return handle->bai == bai;
+}
+
+
+static void
+intr_cb (struct libusb_transfer *transfer)
+{
+ ccid_driver_t handle = transfer->user_data;
+
+ DEBUGOUT_2 ("CCID: interrupt callback %d (%d)\n",
+ transfer->status, transfer->actual_length);
+
+ if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT)
+ {
+ int err;
+
+ submit_again:
+ /* Submit the URB again to keep watching the INTERRUPT transfer. */
+ err = libusb_submit_transfer (transfer);
+ if (err == LIBUSB_ERROR_NO_DEVICE)
+ goto device_removed;
+
+ DEBUGOUT_1 ("CCID submit transfer again %d\n", err);
+ }
+ else if (transfer->status == LIBUSB_TRANSFER_COMPLETED)
+ {
+ size_t len = transfer->actual_length;
+ unsigned char *p = transfer->buffer;
+ int card_removed = 0;
+
+ while (len)
+ {
+ if (*p == RDR_to_PC_NotifySlotChange)
+ {
+ if (len < 2)
+ break;
+
+ DEBUGOUT_1 ("CCID: NotifySlotChange: %02x\n", p[1]);
+
+ if ((p[1] & 1))
+ card_removed = 0;
+ else
+ card_removed = 1;
+
+ p += 2;
+ len -= 2;
+ }
+ else if (*p == RDR_to_PC_HardwareError)
+ {
+ if (len < 4)
+ break;
+
+ DEBUGOUT_1 ("CCID: hardware error detected: %02x\n", p[3]);
+ p += 4;
+ len -= 4;
+ }
+ else
+ {
+ DEBUGOUT_1 ("CCID: unknown intr: %02x\n", p[0]);
+ break;
+ }
+ }
+
+ if (card_removed)
+ {
+ DEBUGOUT ("CCID: card removed\n");
+ handle->powered_off = 1;
+#if defined(GNUPG_MAJOR_VERSION)
+ scd_kick_the_loop ();
+#endif
+ }
+ else
+ {
+ /* Event other than card removal. */
+ goto submit_again;
+ }
+ }
+ else if (transfer->status == LIBUSB_TRANSFER_CANCELLED)
+ handle->powered_off = 1;
+ else if (transfer->status == LIBUSB_TRANSFER_OVERFLOW)
+ {
+ /* Something goes wrong. Ignore. */
+ DEBUGOUT ("CCID: interrupt transfer overflow\n");
+ }
+ else
+ {
+ device_removed:
+ DEBUGOUT ("CCID: device removed\n");
+ handle->powered_off = 1;
+#if defined(GNUPG_MAJOR_VERSION)
+ scd_kick_the_loop ();
+#endif
+ }
+}
+
+static void
+ccid_setup_intr (ccid_driver_t handle)
+{
+ struct libusb_transfer *transfer;
+ int err;
+
+ transfer = libusb_alloc_transfer (0);
+ handle->transfer = transfer;
+ libusb_fill_interrupt_transfer (transfer, handle->idev, handle->ep_intr,
+ handle->intr_buf, sizeof (handle->intr_buf),
+ intr_cb, handle, 0);
+ err = libusb_submit_transfer (transfer);
+ DEBUGOUT_2 ("CCID submit transfer (%x): %d", handle->ep_intr, err);
+}
+
+
+static void *
+ccid_usb_thread (void *arg)
+{
+ libusb_context *ctx = arg;
+
+ while (ccid_usb_thread_is_alive)
+ {
+#ifdef USE_NPTH
+ npth_unprotect ();
+#endif
+ libusb_handle_events_completed (ctx, NULL);
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+ }
+
+ return NULL;
+}
+
+
+static int
+ccid_open_usb_reader (const char *spec_reader_name,
+ int idx, void *ccid_table0,
+ ccid_driver_t *handle, char **rdrname_p)
+{
+ libusb_device *dev;
+ libusb_device_handle *idev = NULL;
+ char *rid = NULL;
+ int rc = 0;
+ int ifc_no, set_no;
+ struct libusb_device_descriptor desc;
+ int n;
+ int bus, addr;
+ unsigned int bai;
+ struct ccid_dev_table *ccid_table = ccid_table0;
+
+ n = ccid_table[idx].n;
+ ifc_no = ccid_table[idx].interface_number;
+ set_no = ccid_table[idx].setting_number;
+
+ dev = ccid_usb_dev_list[n];
+ bus = libusb_get_bus_number (dev);
+ addr = libusb_get_device_address (dev);
+ bai = (bus << 16) | (addr << 8) | ifc_no;
+
+ rc = libusb_open (dev, &idev);
+ if (rc)
+ {
+ DEBUGOUT_1 ("usb_open failed: %s\n", libusb_error_name (rc));
+ free (*handle);
+ *handle = NULL;
+ return map_libusb_error (rc);
+ }
+
+ if (ccid_usb_thread_is_alive++ == 0)
+ {
+ npth_t thread;
+ npth_attr_t tattr;
+ int err;
+
+ err = npth_attr_init (&tattr);
+ if (err)
+ {
+ DEBUGOUT_1 ("npth_attr_init failed: %s\n", strerror (err));
+ free (*handle);
+ *handle = NULL;
+ return err;
+ }
+
+ npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
+ err = npth_create (&thread, &tattr, ccid_usb_thread, NULL);
+ if (err)
+ {
+ DEBUGOUT_1 ("npth_create failed: %s\n", strerror (err));
+ free (*handle);
+ *handle = NULL;
+ return err;
+ }
+ npth_setname_np (thread, "ccid_usb_thread");
+
+ npth_attr_destroy (&tattr);
+ }
+
+ rc = libusb_get_device_descriptor (dev, &desc);
+ if (rc)
+ {
+ DEBUGOUT ("get_device_descripor failed\n");
+ rc = map_libusb_error (rc);
+ goto leave;
+ }
+
+ rid = make_reader_id (idev, desc.idVendor, desc.idProduct,
+ desc.iSerialNumber);
+
+ /* Check to see if reader name matches the spec. */
+ if (spec_reader_name
+ && strncmp (rid, spec_reader_name, strlen (spec_reader_name)))
+ {
+ DEBUGOUT ("device not matched\n");
+ rc = CCID_DRIVER_ERR_NO_READER;
+ goto leave;
+ }
+
+ (*handle)->id_vendor = desc.idVendor;
+ (*handle)->id_product = desc.idProduct;
+ (*handle)->idev = idev;
+ (*handle)->bai = bai;
+ (*handle)->ifc_no = ifc_no;
+ (*handle)->ep_bulk_out = ccid_table[idx].ep_bulk_out;
+ (*handle)->ep_bulk_in = ccid_table[idx].ep_bulk_in;
+ (*handle)->ep_intr = ccid_table[idx].ep_intr;
+
+ DEBUGOUT_2 ("using CCID reader %d (ID=%s)\n", idx, rid);
+
+ if (parse_ccid_descriptor (*handle, desc.bcdDevice,
+ ccid_table[idx].ifcdesc_extra,
+ ccid_table[idx].ifcdesc_extra_len))
+ {
+ DEBUGOUT ("device not supported\n");
+ rc = CCID_DRIVER_ERR_NO_READER;
+ goto leave;
+ }
+
+#ifdef USE_NPTH
+ npth_unprotect ();
+#endif
+ rc = libusb_claim_interface (idev, ifc_no);
+ if (rc)
+ {
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+ DEBUGOUT_1 ("usb_claim_interface failed: %d\n", rc);
+ rc = map_libusb_error (rc);
+ goto leave;
+ }
+
+ /* Submit SET_INTERFACE control transfer which can reset the device. */
+ rc = libusb_set_interface_alt_setting (idev, ifc_no, set_no);
+ if (rc)
+ {
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+ DEBUGOUT_1 ("usb_set_interface_alt_setting failed: %d\n", rc);
+ rc = map_libusb_error (rc);
+ goto leave;
+ }
+
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+
+ rc = ccid_vendor_specific_init (*handle);
+
+ leave:
+ if (rc)
+ {
+ --ccid_usb_thread_is_alive;
+ free (rid);
+ libusb_release_interface (idev, ifc_no);
+ libusb_close (idev);
+ free (*handle);
+ *handle = NULL;
+ }
+ else
+ {
+ if (rdrname_p)
+ *rdrname_p = rid;
+ else
+ free (rid);
+ }
+
+ return rc;
+}
+
+/* Open the reader with the internal number READERNO and return a
+ pointer to be used as handle in HANDLE. Returns 0 on success. */
+int
+ccid_open_reader (const char *spec_reader_name, int idx,
+ void *ccid_table0,
+ ccid_driver_t *handle, char **rdrname_p)
+{
+ struct ccid_dev_table *ccid_table = ccid_table0;
+
+ *handle = calloc (1, sizeof **handle);
+ if (!*handle)
+ {
+ DEBUGOUT ("out of memory\n");
+ return CCID_DRIVER_ERR_OUT_OF_CORE;
+ }
+
+ return ccid_open_usb_reader (spec_reader_name, idx, ccid_table,
+ handle, rdrname_p);
+}
+
+
+int
+ccid_require_get_status (ccid_driver_t handle)
+{
+ /* When a card reader supports interrupt transfer to check the
+ status of card, it is possible to submit only an interrupt
+ transfer, and no check is required by application layer. USB can
+ detect removal of a card and can detect removal of a reader.
+ */
+ if (handle->ep_intr >= 0)
+ {
+ if (handle->id_vendor != VENDOR_SCM)
+ return 0;
+
+ /*
+ * For card reader with interrupt transfer support, ideally,
+ * removal is detected by intr_cb, but some card reader
+ * (e.g. SPR532) has a possible case of missing report to
+ * intr_cb, and another case of valid report to intr_cb.
+ *
+ * For such a reader, the removal should be able to be detected
+ * by PC_to_RDR_GetSlotStatus, too. Thus, calls to
+ * ccid_slot_status should go on wire even if "on_wire" is not
+ * requested.
+ *
+ */
+ if (handle->transfer == NULL)
+ return 0;
+ }
+
+ /* Libusb actually detects the removal of USB device in use.
+ However, there is no good API to handle the removal (yet),
+ cleanly and with good portability.
+
+ There is libusb_set_pollfd_notifiers function, but it doesn't
+ offer libusb_device_handle* data to its callback. So, when it
+ watches multiple devices, there is no way to know which device is
+ removed.
+
+ Once, we will have a good programming interface of libusb, we can
+ list tokens (with no interrupt transfer support, but always with
+ card inserted) here to return 0, so that scdaemon can submit
+ minimum packet on wire.
+ */
+ return 1;
+}
+
+static int
+send_power_off (ccid_driver_t handle)
+{
+ int rc;
+ unsigned char msg[100];
+ size_t msglen;
+ unsigned char seqno;
+
+ msg[0] = PC_to_RDR_IccPowerOff;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = 0; /* RFU */
+ msg[8] = 0; /* RFU */
+ msg[9] = 0; /* RFU */
+ set_msg_len (msg, 0);
+ msglen = 10;
+
+ rc = bulk_out (handle, msg, msglen, 0);
+ if (!rc)
+ bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus,
+ seqno, 2000, 0);
+ return rc;
+}
+
+static void
+do_close_reader (ccid_driver_t handle)
+{
+ int rc;
+
+ if (!handle->powered_off)
+ send_power_off (handle);
+
+ if (handle->transfer)
+ {
+ if (!handle->powered_off)
+ {
+ DEBUGOUT ("libusb_cancel_transfer\n");
+
+ rc = libusb_cancel_transfer (handle->transfer);
+ if (rc != LIBUSB_ERROR_NOT_FOUND)
+ while (!handle->powered_off)
+ {
+ DEBUGOUT ("libusb_handle_events_completed\n");
+#ifdef USE_NPTH
+ npth_unprotect ();
+#endif
+ libusb_handle_events_completed (NULL, &handle->powered_off);
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+ }
+ }
+
+ libusb_free_transfer (handle->transfer);
+ handle->transfer = NULL;
+ }
+
+ DEBUGOUT ("libusb_release_interface and libusb_close\n");
+ libusb_release_interface (handle->idev, handle->ifc_no);
+ --ccid_usb_thread_is_alive;
+ libusb_close (handle->idev);
+ handle->idev = NULL;
+}
+
+
+int
+ccid_set_progress_cb (ccid_driver_t handle,
+ void (*cb)(void *, const char *, int, int, int),
+ void *cb_arg)
+{
+ if (!handle)
+ return CCID_DRIVER_ERR_INV_VALUE;
+
+ handle->progress_cb = cb;
+ handle->progress_cb_arg = cb_arg;
+ return 0;
+}
+
+
+int
+ccid_set_prompt_cb (ccid_driver_t handle,
+ void (*cb)(void *, int), void *cb_arg)
+{
+ if (!handle)
+ return CCID_DRIVER_ERR_INV_VALUE;
+
+ handle->prompt_cb = cb;
+ handle->prompt_cb_arg = cb_arg;
+ return 0;
+}
+
+
+/* Close the reader HANDLE. */
+int
+ccid_close_reader (ccid_driver_t handle)
+{
+ if (!handle)
+ return 0;
+
+ do_close_reader (handle);
+ free (handle);
+ return 0;
+}
+
+
+/* Return False if a card is present and powered. */
+int
+ccid_check_card_presence (ccid_driver_t handle)
+{
+ (void)handle; /* Not yet implemented. */
+ return -1;
+}
+
+
+/* Write a MSG of length MSGLEN to the designated bulk out endpoint.
+ Returns 0 on success. */
+static int
+bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen,
+ int no_debug)
+{
+ int rc;
+ int transferred;
+
+ /* No need to continue and clutter the log with USB write error
+ messages after we got the first ENODEV. */
+ if (handle->enodev_seen)
+ return CCID_DRIVER_ERR_NO_READER;
+
+ if (debug_level && (!no_debug || debug_level >= 3))
+ {
+ switch (msglen? msg[0]:0)
+ {
+ case PC_to_RDR_IccPowerOn:
+ print_p2r_iccpoweron (msg, msglen);
+ break;
+ case PC_to_RDR_IccPowerOff:
+ print_p2r_iccpoweroff (msg, msglen);
+ break;
+ case PC_to_RDR_GetSlotStatus:
+ print_p2r_getslotstatus (msg, msglen);
+ break;
+ case PC_to_RDR_XfrBlock:
+ print_p2r_xfrblock (msg, msglen);
+ break;
+ case PC_to_RDR_GetParameters:
+ print_p2r_getparameters (msg, msglen);
+ break;
+ case PC_to_RDR_ResetParameters:
+ print_p2r_resetparameters (msg, msglen);
+ break;
+ case PC_to_RDR_SetParameters:
+ print_p2r_setparameters (msg, msglen);
+ break;
+ case PC_to_RDR_Escape:
+ print_p2r_escape (msg, msglen);
+ break;
+ case PC_to_RDR_IccClock:
+ print_p2r_iccclock (msg, msglen);
+ break;
+ case PC_to_RDR_T0APDU:
+ print_p2r_to0apdu (msg, msglen);
+ break;
+ case PC_to_RDR_Secure:
+ print_p2r_secure (msg, msglen);
+ break;
+ case PC_to_RDR_Mechanical:
+ print_p2r_mechanical (msg, msglen);
+ break;
+ case PC_to_RDR_Abort:
+ print_p2r_abort (msg, msglen);
+ break;
+ case PC_to_RDR_SetDataRate:
+ print_p2r_setdatarate (msg, msglen);
+ break;
+ default:
+ print_p2r_unknown (msg, msglen);
+ break;
+ }
+ }
+
+#ifdef USE_NPTH
+ npth_unprotect ();
+#endif
+ rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_out,
+ msg, msglen, &transferred,
+ 5000 /* ms timeout */);
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+ if (rc == 0 && transferred == msglen)
+ return 0;
+
+ if (rc)
+ {
+ DEBUGOUT_1 ("usb_bulk_write error: %s\n", libusb_error_name (rc));
+ if (rc == LIBUSB_ERROR_NO_DEVICE)
+ {
+ handle->enodev_seen = 1;
+ return CCID_DRIVER_ERR_NO_READER;
+ }
+ }
+
+ return 0;
+}
+
+
+/* Read a maximum of LENGTH bytes from the bulk in endpoint into
+ BUFFER and return the actual read number if bytes in NREAD. SEQNO
+ is the sequence number used to send the request and EXPECTED_TYPE
+ the type of message we expect. Does checks on the ccid
+ header. TIMEOUT is the timeout value in ms. NO_DEBUG may be set to
+ avoid debug messages in case of no error; this can be overriden
+ with a glibal debug level of at least 3. Returns 0 on success. */
+static int
+bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length,
+ size_t *nread, int expected_type, int seqno, int timeout,
+ int no_debug)
+{
+ int rc;
+ int msglen;
+ int notified = 0;
+ int bwi = 1;
+
+ /* Fixme: The next line for the current Valgrind without support
+ for USB IOCTLs. */
+ memset (buffer, 0, length);
+ retry:
+
+#ifdef USE_NPTH
+ npth_unprotect ();
+#endif
+ rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_in,
+ buffer, length, &msglen, bwi*timeout);
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+ if (rc)
+ {
+ DEBUGOUT_1 ("usb_bulk_read error: %s\n", libusb_error_name (rc));
+ if (rc == LIBUSB_ERROR_NO_DEVICE)
+ handle->enodev_seen = 1;
+
+ return map_libusb_error (rc);
+ }
+ if (msglen < 0)
+ return CCID_DRIVER_ERR_INV_VALUE; /* Faulty libusb. */
+ *nread = msglen;
+
+ if (msglen < 10)
+ {
+ DEBUGOUT_1 ("bulk-in msg too short (%u)\n", (unsigned int)msglen);
+ abort_cmd (handle, seqno, 0);
+ return CCID_DRIVER_ERR_INV_VALUE;
+ }
+ if (buffer[5] != 0)
+ {
+ DEBUGOUT_1 ("unexpected bulk-in slot (%d)\n", buffer[5]);
+ return CCID_DRIVER_ERR_INV_VALUE;
+ }
+ if (buffer[6] != seqno)
+ {
+ DEBUGOUT_2 ("bulk-in seqno does not match (%d/%d)\n",
+ seqno, buffer[6]);
+ /* Retry until we are synced again. */
+ goto retry;
+ }
+
+ /* We need to handle the time extension request before we check that
+ we got the expected message type. This is in particular required
+ for the Cherry keyboard which sends a time extension request for
+ each key hit. */
+ if (!(buffer[7] & 0x03) && (buffer[7] & 0xC0) == 0x80)
+ {
+ /* Card present and active, time extension requested. */
+ DEBUGOUT_2 ("time extension requested (%02X,%02X)\n",
+ buffer[7], buffer[8]);
+
+ bwi = 1;
+ if (buffer[8] != 0 && buffer[8] != 0xff)
+ bwi = buffer[8];
+
+ /* Gnuk enhancement to prompt user input by ack button */
+ if (buffer[8] == 0xff && !notified)
+ {
+ notified = 1;
+ handle->prompt_cb (handle->prompt_cb_arg, 1);
+ }
+
+ goto retry;
+ }
+
+ if (notified)
+ handle->prompt_cb (handle->prompt_cb_arg, 0);
+
+ if (buffer[0] != expected_type && buffer[0] != RDR_to_PC_SlotStatus)
+ {
+ DEBUGOUT_1 ("unexpected bulk-in msg type (%02x)\n", buffer[0]);
+ abort_cmd (handle, seqno, 0);
+ return CCID_DRIVER_ERR_INV_VALUE;
+ }
+
+ if (debug_level && (!no_debug || debug_level >= 3))
+ {
+ switch (buffer[0])
+ {
+ case RDR_to_PC_DataBlock:
+ print_r2p_datablock (buffer, msglen);
+ break;
+ case RDR_to_PC_SlotStatus:
+ print_r2p_slotstatus (buffer, msglen);
+ break;
+ case RDR_to_PC_Parameters:
+ print_r2p_parameters (buffer, msglen);
+ break;
+ case RDR_to_PC_Escape:
+ print_r2p_escape (buffer, msglen);
+ break;
+ case RDR_to_PC_DataRate:
+ print_r2p_datarate (buffer, msglen);
+ break;
+ default:
+ print_r2p_unknown (buffer, msglen);
+ break;
+ }
+ }
+ if (CCID_COMMAND_FAILED (buffer))
+ print_command_failed (buffer);
+
+ /* Check whether a card is at all available. Note: If you add new
+ error codes here, check whether they need to be ignored in
+ send_escape_cmd. */
+ switch ((buffer[7] & 0x03))
+ {
+ case 0: /* no error */ break;
+ case 1: rc = CCID_DRIVER_ERR_CARD_INACTIVE; break;
+ case 2: rc = CCID_DRIVER_ERR_NO_CARD; break;
+ case 3: /* RFU */ break;
+ }
+
+ if (rc)
+ {
+ /*
+ * Communication failure by device side.
+ * Possibly, it was forcibly suspended and resumed.
+ */
+ if (handle->ep_intr < 0)
+ {
+ DEBUGOUT ("CCID: card inactive/removed\n");
+ handle->powered_off = 1;
+ }
+
+#if defined(GNUPG_MAJOR_VERSION)
+ scd_kick_the_loop ();
+#endif
+ }
+
+ return rc;
+}
+
+
+
+/* Send an abort sequence and wait until everything settled. */
+static int
+abort_cmd (ccid_driver_t handle, int seqno, int init)
+{
+ int rc;
+ unsigned char dummybuf[8];
+ unsigned char msg[100];
+ int msglen;
+
+ seqno &= 0xff;
+ DEBUGOUT_1 ("sending abort sequence for seqno %d\n", seqno);
+ /* Send the abort command to the control pipe. Note that we don't
+ need to keep track of sent abort commands because there should
+ never be another thread using the same slot concurrently. */
+#ifdef USE_NPTH
+ npth_unprotect ();
+#endif
+ rc = libusb_control_transfer (handle->idev,
+ 0x21,/* bmRequestType: host-to-device,
+ class specific, to interface. */
+ 1, /* ABORT */
+ (seqno << 8 | 0 /* slot */),
+ handle->ifc_no,
+ dummybuf, 0,
+ 1000 /* ms timeout */);
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+ if (rc)
+ {
+ DEBUGOUT_1 ("usb_control_msg error: %s\n", libusb_error_name (rc));
+ if (!init)
+ return map_libusb_error (rc);
+ }
+
+ /* Now send the abort command to the bulk out pipe using the same
+ SEQNO and SLOT. Do this in a loop to so that all seqno are
+ tried. */
+ seqno--; /* Adjust for next increment. */
+ do
+ {
+ int transferred;
+
+ seqno++;
+ msg[0] = PC_to_RDR_Abort;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno;
+ msg[7] = 0; /* RFU */
+ msg[8] = 0; /* RFU */
+ msg[9] = 0; /* RFU */
+ msglen = 10;
+ set_msg_len (msg, 0);
+
+#ifdef USE_NPTH
+ npth_unprotect ();
+#endif
+ rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_out,
+ msg, msglen, &transferred,
+ init? 100: 5000 /* ms timeout */);
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+ if (rc == 0 && transferred == msglen)
+ rc = 0;
+ else if (rc)
+ DEBUGOUT_1 ("usb_bulk_write error in abort_cmd: %s\n",
+ libusb_error_name (rc));
+
+ if (rc)
+ return map_libusb_error (rc);
+
+#ifdef USE_NPTH
+ npth_unprotect ();
+#endif
+ rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_in,
+ msg, sizeof msg, &msglen,
+ init? 100: 5000 /*ms timeout*/);
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+ if (rc)
+ {
+ DEBUGOUT_1 ("usb_bulk_read error in abort_cmd: %s\n",
+ libusb_error_name (rc));
+ if (init && rc == LIBUSB_ERROR_TIMEOUT)
+ continue;
+ else
+ return map_libusb_error (rc);
+ }
+
+ if (msglen < 10)
+ {
+ DEBUGOUT_1 ("bulk-in msg in abort_cmd too short (%u)\n",
+ (unsigned int)msglen);
+ return CCID_DRIVER_ERR_INV_VALUE;
+ }
+ if (msg[5] != 0)
+ {
+ DEBUGOUT_1 ("unexpected bulk-in slot (%d) in abort_cmd\n", msg[5]);
+ return CCID_DRIVER_ERR_INV_VALUE;
+ }
+
+ DEBUGOUT_3 ("status: %02X error: %02X octet[9]: %02X\n",
+ msg[7], msg[8], msg[9]);
+ if (CCID_COMMAND_FAILED (msg))
+ print_command_failed (msg);
+ }
+ while (rc == LIBUSB_ERROR_TIMEOUT
+ || (msg[0] != RDR_to_PC_SlotStatus && msg[5] != 0 && msg[6] != seqno));
+
+ handle->seqno = ((seqno + 1) & 0xff);
+ DEBUGOUT ("sending abort sequence succeeded\n");
+
+ return 0;
+}
+
+
+/* Note that this function won't return the error codes NO_CARD or
+ CARD_INACTIVE. IF RESULT is not NULL, the result from the
+ operation will get returned in RESULT and its length in RESULTLEN.
+ If the response is larger than RESULTMAX, an error is returned and
+ the required buffer length returned in RESULTLEN. */
+static int
+send_escape_cmd (ccid_driver_t handle,
+ const unsigned char *data, size_t datalen,
+ unsigned char *result, size_t resultmax, size_t *resultlen)
+{
+ int rc;
+ unsigned char msg[100];
+ size_t msglen;
+ unsigned char seqno;
+
+ if (resultlen)
+ *resultlen = 0;
+
+ if (datalen > sizeof msg - 10)
+ return CCID_DRIVER_ERR_INV_VALUE; /* Escape data too large. */
+
+ msg[0] = PC_to_RDR_Escape;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = 0; /* RFU */
+ msg[8] = 0; /* RFU */
+ msg[9] = 0; /* RFU */
+ memcpy (msg+10, data, datalen);
+ msglen = 10 + datalen;
+ set_msg_len (msg, datalen);
+
+ rc = bulk_out (handle, msg, msglen, 0);
+ if (rc)
+ return rc;
+ rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Escape,
+ seqno, 5000, 0);
+ if (result)
+ switch (rc)
+ {
+ /* We need to ignore certain errorcode here. */
+ case 0:
+ case CCID_DRIVER_ERR_CARD_INACTIVE:
+ case CCID_DRIVER_ERR_NO_CARD:
+ {
+ if (msglen > resultmax)
+ rc = CCID_DRIVER_ERR_INV_VALUE; /* Response too large. */
+ else
+ {
+ memcpy (result, msg, msglen);
+ if (resultlen)
+ *resultlen = msglen;
+ rc = 0;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+
+ return rc;
+}
+
+
+int
+ccid_transceive_escape (ccid_driver_t handle,
+ const unsigned char *data, size_t datalen,
+ unsigned char *resp, size_t maxresplen, size_t *nresp)
+{
+ return send_escape_cmd (handle, data, datalen, resp, maxresplen, nresp);
+}
+
+
+
+/* experimental */
+int
+ccid_poll (ccid_driver_t handle)
+{
+ int rc;
+ unsigned char msg[10];
+ int msglen;
+ int i, j;
+
+ rc = libusb_interrupt_transfer (handle->idev, handle->ep_intr,
+ msg, sizeof msg, &msglen,
+ 0 /* ms timeout */ );
+ if (rc == LIBUSB_ERROR_TIMEOUT)
+ return 0;
+
+ if (rc)
+ {
+ DEBUGOUT_1 ("usb_intr_read error: %s\n", libusb_error_name (rc));
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+ }
+
+ if (msglen < 1)
+ {
+ DEBUGOUT ("intr-in msg too short\n");
+ return CCID_DRIVER_ERR_INV_VALUE;
+ }
+
+ if (msg[0] == RDR_to_PC_NotifySlotChange)
+ {
+ DEBUGOUT ("notify slot change:");
+ for (i=1; i < msglen; i++)
+ for (j=0; j < 4; j++)
+ DEBUGOUT_CONT_3 (" %d:%c%c",
+ (i-1)*4+j,
+ (msg[i] & (1<<(j*2)))? 'p':'-',
+ (msg[i] & (2<<(j*2)))? '*':' ');
+ DEBUGOUT_LF ();
+ }
+ else if (msg[0] == RDR_to_PC_HardwareError)
+ {
+ DEBUGOUT ("hardware error occurred\n");
+ }
+ else
+ {
+ DEBUGOUT_1 ("unknown intr-in msg of type %02X\n", msg[0]);
+ }
+
+ return 0;
+}
+
+
+/* Note that this function won't return the error codes NO_CARD or
+ CARD_INACTIVE */
+int
+ccid_slot_status (ccid_driver_t handle, int *statusbits, int on_wire)
+{
+ int rc;
+ unsigned char msg[100];
+ size_t msglen;
+ unsigned char seqno;
+ int retries = 0;
+
+ if (handle->powered_off)
+ return CCID_DRIVER_ERR_NO_READER;
+
+ /* If the card (with its lower-level driver) doesn't require
+ GET_STATUS on wire (because it supports INTERRUPT transfer for
+ status change, or it's a token which has a card always inserted),
+ no need to send on wire. */
+ if (!on_wire && !ccid_require_get_status (handle))
+ {
+ /* Setup interrupt transfer at the initial call of slot_status
+ with ON_WIRE == 0 */
+ if (handle->transfer == NULL)
+ ccid_setup_intr (handle);
+
+ *statusbits = 0;
+ return 0;
+ }
+
+ retry:
+ msg[0] = PC_to_RDR_GetSlotStatus;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = 0; /* RFU */
+ msg[8] = 0; /* RFU */
+ msg[9] = 0; /* RFU */
+ set_msg_len (msg, 0);
+
+ rc = bulk_out (handle, msg, 10, 1);
+ if (rc)
+ return rc;
+ /* Note that we set the NO_DEBUG flag here, so that the logs won't
+ get cluttered up by a ticker function checking for the slot
+ status and debugging enabled. */
+ rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus,
+ seqno, retries? 1000 : 200, 1);
+ if ((rc == CCID_DRIVER_ERR_CARD_IO_ERROR || rc == CCID_DRIVER_ERR_USB_TIMEOUT)
+ && retries < 3)
+ {
+ if (!retries)
+ {
+ DEBUGOUT ("USB: CALLING USB_CLEAR_HALT\n");
+#ifdef USE_NPTH
+ npth_unprotect ();
+#endif
+ libusb_clear_halt (handle->idev, handle->ep_bulk_in);
+ libusb_clear_halt (handle->idev, handle->ep_bulk_out);
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+ }
+ else
+ DEBUGOUT ("USB: RETRYING bulk_in AGAIN\n");
+ retries++;
+ goto retry;
+ }
+ if (rc && rc != CCID_DRIVER_ERR_NO_CARD && rc != CCID_DRIVER_ERR_CARD_INACTIVE)
+ return rc;
+ *statusbits = (msg[7] & 3);
+
+ return 0;
+}
+
+
+/* Parse ATR string (of ATRLEN) and update parameters at PARAM.
+ Calling this routine, it should prepare default values at PARAM
+ beforehand. This routine assumes that card is accessed by T=1
+ protocol. It doesn't analyze historical bytes at all.
+
+ Returns < 0 value on error:
+ -1 for parse error or integrity check error
+ -2 for card doesn't support T=1 protocol
+ -3 for parameters are nod explicitly defined by ATR
+ -4 for this driver doesn't support CRC
+
+ Returns >= 0 on success:
+ 0 for card is negotiable mode
+ 1 for card is specific mode (and not negotiable)
+ */
+static int
+update_param_by_atr (unsigned char *param, unsigned char *atr, size_t atrlen)
+{
+ int i = -1;
+ int t, y, chk;
+ int historical_bytes_num, negotiable = 1;
+
+#define NEXTBYTE() do { i++; if (atrlen <= i) return -1; } while (0)
+
+ NEXTBYTE ();
+
+ if (atr[i] == 0x3F)
+ param[1] |= 0x02; /* Convention is inverse. */
+ NEXTBYTE ();
+
+ y = (atr[i] >> 4);
+ historical_bytes_num = atr[i] & 0x0f;
+ NEXTBYTE ();
+
+ if ((y & 1))
+ {
+ param[0] = atr[i]; /* TA1 - Fi & Di */
+ NEXTBYTE ();
+ }
+
+ if ((y & 2))
+ NEXTBYTE (); /* TB1 - ignore */
+
+ if ((y & 4))
+ {
+ param[2] = atr[i]; /* TC1 - Guard Time */
+ NEXTBYTE ();
+ }
+
+ if ((y & 8))
+ {
+ y = (atr[i] >> 4); /* TD1 */
+ t = atr[i] & 0x0f;
+ NEXTBYTE ();
+
+ if ((y & 1))
+ { /* TA2 - PPS mode */
+ if ((atr[i] & 0x0f) != 1)
+ return -2; /* Wrong card protocol (!= 1). */
+
+ if ((atr[i] & 0x10) != 0x10)
+ return -3; /* Transmission parameters are implicitly defined. */
+
+ negotiable = 0; /* TA2 means specific mode. */
+ NEXTBYTE ();
+ }
+
+ if ((y & 2))
+ NEXTBYTE (); /* TB2 - ignore */
+
+ if ((y & 4))
+ NEXTBYTE (); /* TC2 - ignore */
+
+ if ((y & 8))
+ {
+ y = (atr[i] >> 4); /* TD2 */
+ t = atr[i] & 0x0f;
+ NEXTBYTE ();
+ }
+ else
+ y = 0;
+
+ while (y)
+ {
+ if ((y & 1))
+ { /* TAx */
+ if (t == 1)
+ param[5] = atr[i]; /* IFSC */
+ else if (t == 15)
+ /* XXX: check voltage? */
+ param[4] = (atr[i] >> 6); /* ClockStop */
+
+ NEXTBYTE ();
+ }
+
+ if ((y & 2))
+ {
+ if (t == 1)
+ param[3] = atr[i]; /* TBx - BWI & CWI */
+ NEXTBYTE ();
+ }
+
+ if ((y & 4))
+ {
+ if (t == 1)
+ param[1] |= (atr[i] & 0x01); /* TCx - LRC/CRC */
+ NEXTBYTE ();
+
+ if (param[1] & 0x01)
+ return -4; /* CRC not supported yet. */
+ }
+
+ if ((y & 8))
+ {
+ y = (atr[i] >> 4); /* TDx */
+ t = atr[i] & 0x0f;
+ NEXTBYTE ();
+ }
+ else
+ y = 0;
+ }
+ }
+
+ i += historical_bytes_num - 1;
+ NEXTBYTE ();
+ if (atrlen != i+1)
+ return -1;
+
+#undef NEXTBYTE
+
+ chk = 0;
+ do
+ {
+ chk ^= atr[i];
+ i--;
+ }
+ while (i > 0);
+
+ if (chk != 0)
+ return -1;
+
+ return negotiable;
+}
+
+
+/* Return the ATR of the card. This is not a cached value and thus an
+ actual reset is done. */
+int
+ccid_get_atr (ccid_driver_t handle,
+ unsigned char *atr, size_t maxatrlen, size_t *atrlen)
+{
+ int rc;
+ int statusbits;
+ unsigned char msg[100];
+ unsigned char *tpdu;
+ size_t msglen, tpdulen;
+ unsigned char seqno;
+ int use_crc = 0;
+ unsigned int edc;
+ int tried_iso = 0;
+ int got_param;
+ unsigned char param[7] = { /* For Protocol T=1 */
+ 0x11, /* bmFindexDindex */
+ 0x10, /* bmTCCKST1 */
+ 0x00, /* bGuardTimeT1 */
+ 0x4d, /* bmWaitingIntegersT1 */
+ 0x00, /* bClockStop */
+ 0x20, /* bIFSC */
+ 0x00 /* bNadValue */
+ };
+
+ /* First check whether a card is available. */
+ rc = ccid_slot_status (handle, &statusbits, 1);
+ if (rc)
+ return rc;
+ if (statusbits == 2)
+ return CCID_DRIVER_ERR_NO_CARD;
+
+ /*
+ * In the first invocation of ccid_slot_status, card reader may
+ * return CCID_DRIVER_ERR_CARD_INACTIVE and handle->powered_off may
+ * become 1. Because inactive card is no problem (we are turning it
+ * ON here), clear the flag.
+ */
+ handle->powered_off = 0;
+
+ /* For an inactive and also for an active card, issue the PowerOn
+ command to get the ATR. */
+ again:
+ msg[0] = PC_to_RDR_IccPowerOn;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ /* power select (0=auto, 1=5V, 2=3V, 3=1.8V) */
+ msg[7] = handle->auto_voltage ? 0 : 1;
+ msg[8] = 0; /* RFU */
+ msg[9] = 0; /* RFU */
+ set_msg_len (msg, 0);
+ msglen = 10;
+
+ rc = bulk_out (handle, msg, msglen, 0);
+ if (rc)
+ return rc;
+ rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock,
+ seqno, 5000, 0);
+ if (rc)
+ return rc;
+ if (!tried_iso && CCID_COMMAND_FAILED (msg) && CCID_ERROR_CODE (msg) == 0xbb
+ && ((handle->id_vendor == VENDOR_CHERRY
+ && handle->id_product == 0x0005)
+ || (handle->id_vendor == VENDOR_GEMPC
+ && handle->id_product == 0x4433)
+ ))
+ {
+ tried_iso = 1;
+ /* Try switching to ISO mode. */
+ if (!send_escape_cmd (handle, (const unsigned char*)"\xF1\x01", 2,
+ NULL, 0, NULL))
+ goto again;
+ }
+ else if (statusbits == 0 && CCID_COMMAND_FAILED (msg))
+ {
+ /* Card was active already, and something went wrong with
+ PC_to_RDR_IccPowerOn command. It may be baud-rate mismatch
+ between the card and the reader. To recover from this state,
+ send PC_to_RDR_IccPowerOff command to reset the card and try
+ again.
+ */
+ rc = send_power_off (handle);
+ if (rc)
+ return rc;
+
+ statusbits = 1;
+ goto again;
+ }
+ else if (CCID_COMMAND_FAILED (msg))
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+
+
+ handle->powered_off = 0;
+
+ if (atr)
+ {
+ size_t n = msglen - 10;
+
+ if (n > maxatrlen)
+ n = maxatrlen;
+ memcpy (atr, msg+10, n);
+ *atrlen = n;
+ }
+
+ param[6] = handle->nonnull_nad? ((1 << 4) | 0): 0;
+ rc = update_param_by_atr (param, msg+10, msglen - 10);
+ if (rc < 0)
+ {
+ DEBUGOUT_1 ("update_param_by_atr failed: %d\n", rc);
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+ }
+
+ got_param = 0;
+
+ if (handle->auto_param)
+ {
+ msg[0] = PC_to_RDR_GetParameters;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = 0; /* RFU */
+ msg[8] = 0; /* RFU */
+ msg[9] = 0; /* RFU */
+ set_msg_len (msg, 0);
+ msglen = 10;
+ rc = bulk_out (handle, msg, msglen, 0);
+ if (!rc)
+ rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters,
+ seqno, 2000, 0);
+ if (rc)
+ DEBUGOUT ("GetParameters failed\n");
+ else if (msglen == 17 && msg[9] == 1)
+ got_param = 1;
+ }
+ else if (handle->auto_pps)
+ ;
+ else if (rc == 1) /* It's negotiable, send PPS. */
+ {
+ msg[0] = PC_to_RDR_XfrBlock;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = 0;
+ msg[8] = 0;
+ msg[9] = 0;
+ msg[10] = 0xff; /* PPSS */
+ msg[11] = 0x11; /* PPS0: PPS1, Protocol T=1 */
+ msg[12] = param[0]; /* PPS1: Fi / Di */
+ msg[13] = 0xff ^ 0x11 ^ param[0]; /* PCK */
+ set_msg_len (msg, 4);
+ msglen = 10 + 4;
+
+ rc = bulk_out (handle, msg, msglen, 0);
+ if (rc)
+ return rc;
+
+ rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock,
+ seqno, 5000, 0);
+ if (rc)
+ return rc;
+
+ if (msglen != 10 + 4)
+ {
+ DEBUGOUT_1 ("Setting PPS failed: %zu\n", msglen);
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+ }
+
+ if (msg[10] != 0xff || msg[11] != 0x11 || msg[12] != param[0])
+ {
+ DEBUGOUT_1 ("Setting PPS failed: 0x%02x\n", param[0]);
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+ }
+ }
+
+ /* Setup parameters to select T=1. */
+ msg[0] = PC_to_RDR_SetParameters;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = 1; /* Select T=1. */
+ msg[8] = 0; /* RFU */
+ msg[9] = 0; /* RFU */
+
+ if (!got_param)
+ memcpy (&msg[10], param, 7);
+ set_msg_len (msg, 7);
+ msglen = 10 + 7;
+
+ rc = bulk_out (handle, msg, msglen, 0);
+ if (rc)
+ return rc;
+ rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters,
+ seqno, 5000, 0);
+ if (rc)
+ DEBUGOUT ("SetParameters failed (ignored)\n");
+
+ if (!rc && msglen > 15 && msg[15] >= 16 && msg[15] <= 254 )
+ handle->ifsc = msg[15];
+ else
+ handle->ifsc = 128; /* Something went wrong, assume 128 bytes. */
+
+ if (handle->nonnull_nad && msglen > 16 && msg[16] == 0)
+ {
+ DEBUGOUT ("Use Null-NAD, clearing handle->nonnull_nad.\n");
+ handle->nonnull_nad = 0;
+ }
+
+ handle->t1_ns = 0;
+ handle->t1_nr = 0;
+
+ /* Send an S-Block with our maximum IFSD to the CCID. */
+ if (!handle->apdu_level && !handle->auto_ifsd)
+ {
+ tpdu = msg+10;
+ /* NAD: DAD=1, SAD=0 */
+ tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0;
+ tpdu[1] = (0xc0 | 0 | 1); /* S-block request: change IFSD */
+ tpdu[2] = 1;
+ tpdu[3] = handle->max_ifsd? handle->max_ifsd : 32;
+ tpdulen = 4;
+ edc = compute_edc (tpdu, tpdulen, use_crc);
+ if (use_crc)
+ tpdu[tpdulen++] = (edc >> 8);
+ tpdu[tpdulen++] = edc;
+
+ msg[0] = PC_to_RDR_XfrBlock;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = 0;
+ msg[8] = 0; /* RFU */
+ msg[9] = 0; /* RFU */
+ set_msg_len (msg, tpdulen);
+ msglen = 10 + tpdulen;
+
+ if (debug_level > 1)
+ DEBUGOUT_3 ("T=1: put %c-block seq=%d%s\n",
+ ((msg[11] & 0xc0) == 0x80)? 'R' :
+ (msg[11] & 0x80)? 'S' : 'I',
+ ((msg[11] & 0x80)? !!(msg[11]& 0x10)
+ : !!(msg[11] & 0x40)),
+ (!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":""));
+
+ rc = bulk_out (handle, msg, msglen, 0);
+ if (rc)
+ return rc;
+
+
+ rc = bulk_in (handle, msg, sizeof msg, &msglen,
+ RDR_to_PC_DataBlock, seqno, 5000, 0);
+ if (rc)
+ return rc;
+
+ tpdu = msg + 10;
+ tpdulen = msglen - 10;
+
+ if (tpdulen < 4)
+ return CCID_DRIVER_ERR_ABORTED;
+
+ if (debug_level > 1)
+ DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n",
+ ((msg[11] & 0xc0) == 0x80)? 'R' :
+ (msg[11] & 0x80)? 'S' : 'I',
+ ((msg[11] & 0x80)? !!(msg[11]& 0x10)
+ : !!(msg[11] & 0x40)),
+ ((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0,
+ (!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":""));
+
+ if ((tpdu[1] & 0xe0) != 0xe0 || tpdu[2] != 1)
+ {
+ DEBUGOUT ("invalid response for S-block (Change-IFSD)\n");
+ return -1;
+ }
+ DEBUGOUT_1 ("IFSD has been set to %d\n", tpdu[3]);
+ }
+
+ ccid_vendor_specific_setup (handle);
+ return 0;
+}
+
+
+
+
+static unsigned int
+compute_edc (const unsigned char *data, size_t datalen, int use_crc)
+{
+ if (use_crc)
+ {
+ return 0x42; /* Not yet implemented. */
+ }
+ else
+ {
+ unsigned char crc = 0;
+
+ for (; datalen; datalen--)
+ crc ^= *data++;
+ return crc;
+ }
+}
+
+
+/* Return true if APDU is an extended length one. */
+static int
+is_exlen_apdu (const unsigned char *apdu, size_t apdulen)
+{
+ if (apdulen < 7 || apdu[4])
+ return 0; /* Too short or no Z byte. */
+ return 1;
+}
+
+
+/* Helper for ccid_transceive used for APDU level exchanges. */
+static int
+ccid_transceive_apdu_level (ccid_driver_t handle,
+ const unsigned char *apdu_buf, size_t apdu_len,
+ unsigned char *resp, size_t maxresplen,
+ size_t *nresp)
+{
+ int rc;
+ unsigned char msg[CCID_MAX_BUF];
+ const unsigned char *apdu_p;
+ size_t apdu_part_len;
+ size_t msglen;
+ unsigned char seqno;
+ int bwi = 0;
+ unsigned char chain = 0;
+
+ if (apdu_len == 0 || apdu_len > sizeof (msg) - 10)
+ return CCID_DRIVER_ERR_INV_VALUE; /* Invalid length. */
+
+ apdu_p = apdu_buf;
+ while (1)
+ {
+ apdu_part_len = apdu_len;
+ if (apdu_part_len > handle->max_ccid_msglen - 10)
+ {
+ apdu_part_len = handle->max_ccid_msglen - 10;
+ chain |= 0x01;
+ }
+
+ msg[0] = PC_to_RDR_XfrBlock;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = bwi;
+ msg[8] = chain;
+ msg[9] = 0;
+ memcpy (msg+10, apdu_p, apdu_part_len);
+ set_msg_len (msg, apdu_part_len);
+ msglen = 10 + apdu_part_len;
+
+ rc = bulk_out (handle, msg, msglen, 0);
+ if (rc)
+ return rc;
+
+ apdu_p += apdu_part_len;
+ apdu_len -= apdu_part_len;
+
+ rc = bulk_in (handle, msg, sizeof msg, &msglen,
+ RDR_to_PC_DataBlock, seqno, CCID_CMD_TIMEOUT, 0);
+ if (rc)
+ return rc;
+
+ if (!(chain & 0x01))
+ break;
+
+ chain = 0x02;
+ }
+
+ apdu_len = 0;
+ while (1)
+ {
+ apdu_part_len = msglen - 10;
+ if (resp && apdu_len + apdu_part_len <= maxresplen)
+ memcpy (resp + apdu_len, msg+10, apdu_part_len);
+ apdu_len += apdu_part_len;
+
+ if (!(msg[9] & 0x01))
+ break;
+
+ msg[0] = PC_to_RDR_XfrBlock;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = bwi;
+ msg[8] = 0x10; /* Request next data block */
+ msg[9] = 0;
+ set_msg_len (msg, 0);
+ msglen = 10;
+
+ rc = bulk_out (handle, msg, msglen, 0);
+ if (rc)
+ return rc;
+
+ rc = bulk_in (handle, msg, sizeof msg, &msglen,
+ RDR_to_PC_DataBlock, seqno, CCID_CMD_TIMEOUT, 0);
+ if (rc)
+ return rc;
+ }
+
+ if (resp)
+ {
+ if (apdu_len > maxresplen)
+ {
+ DEBUGOUT_2 ("provided buffer too short for received data "
+ "(%u/%u)\n",
+ (unsigned int)apdu_len, (unsigned int)maxresplen);
+ return CCID_DRIVER_ERR_INV_VALUE;
+ }
+
+ *nresp = apdu_len;
+ }
+
+ return 0;
+}
+
+
+
+/*
+ Protocol T=1 overview
+
+ Block Structure:
+ Prologue Field:
+ 1 byte Node Address (NAD)
+ 1 byte Protocol Control Byte (PCB)
+ 1 byte Length (LEN)
+ Information Field:
+ 0-254 byte APDU or Control Information (INF)
+ Epilogue Field:
+ 1 byte Error Detection Code (EDC)
+
+ NAD:
+ bit 7 unused
+ bit 4..6 Destination Node Address (DAD)
+ bit 3 unused
+ bit 2..0 Source Node Address (SAD)
+
+ If node adresses are not used, SAD and DAD should be set to 0 on
+ the first block sent to the card. If they are used they should
+ have different values (0 for one is okay); that first block sets up
+ the addresses of the nodes.
+
+ PCB:
+ Information Block (I-Block):
+ bit 7 0
+ bit 6 Sequence number (yep, that is modulo 2)
+ bit 5 Chaining flag
+ bit 4..0 reserved
+ Received-Ready Block (R-Block):
+ bit 7 1
+ bit 6 0
+ bit 5 0
+ bit 4 Sequence number
+ bit 3..0 0 = no error
+ 1 = EDC or parity error
+ 2 = other error
+ other values are reserved
+ Supervisory Block (S-Block):
+ bit 7 1
+ bit 6 1
+ bit 5 clear=request,set=response
+ bit 4..0 0 = resynchronization request
+ 1 = information field size request
+ 2 = abort request
+ 3 = extension of BWT request
+ 4 = VPP error
+ other values are reserved
+
+*/
+
+int
+ccid_transceive (ccid_driver_t handle,
+ const unsigned char *apdu_buf, size_t apdu_buflen,
+ unsigned char *resp, size_t maxresplen, size_t *nresp)
+{
+ int rc;
+ /* The size of the buffer used to be 10+259. For the via_escape
+ hack we need one extra byte, thus 11+259. */
+ unsigned char send_buffer[11+259], recv_buffer[11+259];
+ const unsigned char *apdu;
+ size_t apdulen;
+ unsigned char *msg, *tpdu, *p;
+ size_t msglen, tpdulen, last_tpdulen, n;
+ unsigned char seqno;
+ unsigned int edc;
+ int use_crc = 0;
+ int hdrlen, pcboff;
+ size_t dummy_nresp;
+ int via_escape = 0;
+ int next_chunk = 1;
+ int sending = 1;
+ int retries = 0;
+ int resyncing = 0;
+ int nad_byte;
+ int wait_more = 0;
+
+ if (!nresp)
+ nresp = &dummy_nresp;
+ *nresp = 0;
+
+ /* Smarter readers allow sending APDUs directly; divert here. */
+ if (handle->apdu_level)
+ {
+ /* We employ a hack for Omnikey readers which are able to send
+ TPDUs using an escape sequence. There is no documentation
+ but the Windows driver does it this way. Tested using a
+ CM6121. This method works also for the Cherry XX44
+ keyboards; however there are problems with the
+ ccid_transceive_secure which leads to a loss of sync on the
+ CCID level. If Cherry wants to make their keyboard work
+ again, they should hand over some docs. */
+ if ((handle->id_vendor == VENDOR_OMNIKEY)
+ && handle->apdu_level < 2
+ && is_exlen_apdu (apdu_buf, apdu_buflen))
+ via_escape = 1;
+ else
+ return ccid_transceive_apdu_level (handle, apdu_buf, apdu_buflen,
+ resp, maxresplen, nresp);
+ }
+
+ /* The other readers we support require sending TPDUs. */
+
+ tpdulen = 0; /* Avoid compiler warning about no initialization. */
+ msg = send_buffer;
+ hdrlen = via_escape? 11 : 10;
+
+ /* NAD: DAD=1, SAD=0 */
+ nad_byte = handle->nonnull_nad? ((1 << 4) | 0): 0;
+ if (via_escape)
+ nad_byte = 0;
+
+ last_tpdulen = 0; /* Avoid gcc warning (controlled by RESYNCING). */
+ for (;;)
+ {
+ if (next_chunk)
+ {
+ next_chunk = 0;
+
+ apdu = apdu_buf;
+ apdulen = apdu_buflen;
+ assert (apdulen);
+
+ /* Construct an I-Block. */
+ tpdu = msg + hdrlen;
+ tpdu[0] = nad_byte;
+ tpdu[1] = ((handle->t1_ns & 1) << 6); /* I-block */
+ if (apdulen > handle->ifsc )
+ {
+ apdulen = handle->ifsc;
+ apdu_buf += handle->ifsc;
+ apdu_buflen -= handle->ifsc;
+ tpdu[1] |= (1 << 5); /* Set more bit. */
+ }
+ tpdu[2] = apdulen;
+ memcpy (tpdu+3, apdu, apdulen);
+ tpdulen = 3 + apdulen;
+ edc = compute_edc (tpdu, tpdulen, use_crc);
+ if (use_crc)
+ tpdu[tpdulen++] = (edc >> 8);
+ tpdu[tpdulen++] = edc;
+ }
+
+ if (via_escape)
+ {
+ msg[0] = PC_to_RDR_Escape;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = 0; /* RFU */
+ msg[8] = 0; /* RFU */
+ msg[9] = 0; /* RFU */
+ msg[10] = 0x1a; /* Omnikey command to send a TPDU. */
+ set_msg_len (msg, 1 + tpdulen);
+ }
+ else
+ {
+ msg[0] = PC_to_RDR_XfrBlock;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = (wait_more ? wait_more : 1); /* bBWI */
+ msg[8] = 0; /* RFU */
+ msg[9] = 0; /* RFU */
+ set_msg_len (msg, tpdulen);
+ }
+ msglen = hdrlen + tpdulen;
+ if (!resyncing)
+ last_tpdulen = tpdulen;
+ pcboff = hdrlen+1;
+
+ if (debug_level > 1)
+ DEBUGOUT_3 ("T=1: put %c-block seq=%d%s\n",
+ ((msg[pcboff] & 0xc0) == 0x80)? 'R' :
+ (msg[pcboff] & 0x80)? 'S' : 'I',
+ ((msg[pcboff] & 0x80)? !!(msg[pcboff]& 0x10)
+ : !!(msg[pcboff] & 0x40)),
+ (!(msg[pcboff] & 0x80) && (msg[pcboff] & 0x20)?
+ " [more]":""));
+
+ rc = bulk_out (handle, msg, msglen, 0);
+ if (rc)
+ return rc;
+
+ msg = recv_buffer;
+ rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen,
+ via_escape? RDR_to_PC_Escape : RDR_to_PC_DataBlock, seqno,
+ (wait_more ? wait_more : 1) * CCID_CMD_TIMEOUT, 0);
+ if (rc)
+ return rc;
+
+ tpdu = msg + hdrlen;
+ tpdulen = msglen - hdrlen;
+ resyncing = 0;
+
+ if (tpdulen < 4)
+ {
+#ifdef USE_NPTH
+ npth_unprotect ();
+#endif
+ libusb_clear_halt (handle->idev, handle->ep_bulk_in);
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+ return CCID_DRIVER_ERR_ABORTED;
+ }
+
+ if (debug_level > 1)
+ DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n",
+ ((msg[pcboff] & 0xc0) == 0x80)? 'R' :
+ (msg[pcboff] & 0x80)? 'S' : 'I',
+ ((msg[pcboff] & 0x80)? !!(msg[pcboff]& 0x10)
+ : !!(msg[pcboff] & 0x40)),
+ ((msg[pcboff] & 0xc0) == 0x80)? (msg[pcboff] & 0x0f) : 0,
+ (!(msg[pcboff] & 0x80) && (msg[pcboff] & 0x20)?
+ " [more]":""));
+
+ wait_more = 0;
+ if (!(tpdu[1] & 0x80))
+ { /* This is an I-block. */
+ retries = 0;
+ if (sending)
+ { /* last block sent was successful. */
+ handle->t1_ns ^= 1;
+ sending = 0;
+ }
+
+ if (!!(tpdu[1] & 0x40) != handle->t1_nr)
+ { /* Response does not match our sequence number. */
+ msg = send_buffer;
+ tpdu = msg + hdrlen;
+ tpdu[0] = nad_byte;
+ tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4 | 2); /* R-block */
+ tpdu[2] = 0;
+ tpdulen = 3;
+ edc = compute_edc (tpdu, tpdulen, use_crc);
+ if (use_crc)
+ tpdu[tpdulen++] = (edc >> 8);
+ tpdu[tpdulen++] = edc;
+
+ continue;
+ }
+
+ handle->t1_nr ^= 1;
+
+ p = tpdu + 3; /* Skip the prologue field. */
+ n = tpdulen - 3 - 1; /* Strip the epilogue field. */
+ /* fixme: verify the checksum. */
+ if (resp)
+ {
+ if (n > maxresplen)
+ {
+ DEBUGOUT_2 ("provided buffer too short for received data "
+ "(%u/%u)\n",
+ (unsigned int)n, (unsigned int)maxresplen);
+ return CCID_DRIVER_ERR_INV_VALUE;
+ }
+
+ memcpy (resp, p, n);
+ resp += n;
+ *nresp += n;
+ maxresplen -= n;
+ }
+
+ if (!(tpdu[1] & 0x20))
+ return 0; /* No chaining requested - ready. */
+
+ msg = send_buffer;
+ tpdu = msg + hdrlen;
+ tpdu[0] = nad_byte;
+ tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4); /* R-block */
+ tpdu[2] = 0;
+ tpdulen = 3;
+ edc = compute_edc (tpdu, tpdulen, use_crc);
+ if (use_crc)
+ tpdu[tpdulen++] = (edc >> 8);
+ tpdu[tpdulen++] = edc;
+ }
+ else if ((tpdu[1] & 0xc0) == 0x80)
+ { /* This is a R-block. */
+ if ( (tpdu[1] & 0x0f))
+ {
+ retries++;
+ if (via_escape && retries == 1 && (msg[pcboff] & 0x0f))
+ {
+ /* Error probably due to switching to TPDU. Send a
+ resync request. We use the recv_buffer so that
+ we don't corrupt the send_buffer. */
+ msg = recv_buffer;
+ tpdu = msg + hdrlen;
+ tpdu[0] = nad_byte;
+ tpdu[1] = 0xc0; /* S-block resync request. */
+ tpdu[2] = 0;
+ tpdulen = 3;
+ edc = compute_edc (tpdu, tpdulen, use_crc);
+ if (use_crc)
+ tpdu[tpdulen++] = (edc >> 8);
+ tpdu[tpdulen++] = edc;
+ resyncing = 1;
+ DEBUGOUT ("T=1: requesting resync\n");
+ }
+ else if (retries > 3)
+ {
+ DEBUGOUT ("T=1: 3 failed retries\n");
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+ }
+ else
+ {
+ /* Error: repeat last block */
+ msg = send_buffer;
+ tpdulen = last_tpdulen;
+ }
+ }
+ else if (sending && !!(tpdu[1] & 0x10) == handle->t1_ns)
+ { /* Response does not match our sequence number. */
+ DEBUGOUT ("R-block with wrong seqno received on more bit\n");
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+ }
+ else if (sending)
+ { /* Send next chunk. */
+ retries = 0;
+ msg = send_buffer;
+ next_chunk = 1;
+ handle->t1_ns ^= 1;
+ }
+ else
+ {
+ DEBUGOUT ("unexpected ACK R-block received\n");
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+ }
+ }
+ else
+ { /* This is a S-block. */
+ retries = 0;
+ DEBUGOUT_2 ("T=1: S-block %s received cmd=%d\n",
+ (tpdu[1] & 0x20)? "response": "request",
+ (tpdu[1] & 0x1f));
+ if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 1 && tpdu[2] == 1)
+ {
+ /* Information field size request. */
+ unsigned char ifsc = tpdu[3];
+
+ if (ifsc < 16 || ifsc > 254)
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+
+ msg = send_buffer;
+ tpdu = msg + hdrlen;
+ tpdu[0] = nad_byte;
+ tpdu[1] = (0xc0 | 0x20 | 1); /* S-block response */
+ tpdu[2] = 1;
+ tpdu[3] = ifsc;
+ tpdulen = 4;
+ edc = compute_edc (tpdu, tpdulen, use_crc);
+ if (use_crc)
+ tpdu[tpdulen++] = (edc >> 8);
+ tpdu[tpdulen++] = edc;
+ DEBUGOUT_1 ("T=1: requesting an ifsc=%d\n", ifsc);
+ }
+ else if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 3 && tpdu[2])
+ {
+ /* Wait time extension request. */
+ unsigned char bwi = tpdu[3];
+
+ wait_more = bwi;
+
+ msg = send_buffer;
+ tpdu = msg + hdrlen;
+ tpdu[0] = nad_byte;
+ tpdu[1] = (0xc0 | 0x20 | 3); /* S-block response */
+ tpdu[2] = 1;
+ tpdu[3] = bwi;
+ tpdulen = 4;
+ edc = compute_edc (tpdu, tpdulen, use_crc);
+ if (use_crc)
+ tpdu[tpdulen++] = (edc >> 8);
+ tpdu[tpdulen++] = edc;
+ DEBUGOUT_1 ("T=1: waittime extension of bwi=%d\n", bwi);
+ print_progress (handle);
+ }
+ else if ( (tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 0 && !tpdu[2])
+ {
+ DEBUGOUT ("T=1: resync ack from reader\n");
+ /* Repeat previous block. */
+ msg = send_buffer;
+ tpdulen = last_tpdulen;
+ }
+ else
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+ }
+ } /* end T=1 protocol loop. */
+
+ return 0;
+}
+
+
+/* Send the CCID Secure command to the reader. APDU_BUF should
+ contain the APDU template. PIN_MODE defines how the pin gets
+ formatted:
+
+ 1 := The PIN is ASCII encoded and of variable length. The
+ length of the PIN entered will be put into Lc by the reader.
+ The APDU should me made up of 4 bytes without Lc.
+
+ PINLEN_MIN and PINLEN_MAX define the limits for the pin length. 0
+ may be used t enable reasonable defaults.
+
+ When called with RESP and NRESP set to NULL, the function will
+ merely check whether the reader supports the secure command for the
+ given APDU and PIN_MODE. */
+int
+ccid_transceive_secure (ccid_driver_t handle,
+ const unsigned char *apdu_buf, size_t apdu_buflen,
+ pininfo_t *pininfo,
+ unsigned char *resp, size_t maxresplen, size_t *nresp)
+{
+ int rc;
+ unsigned char send_buffer[10+259], recv_buffer[10+259];
+ unsigned char *msg, *tpdu, *p;
+ size_t msglen, tpdulen, n;
+ unsigned char seqno;
+ size_t dummy_nresp;
+ int testmode;
+ int cherry_mode = 0;
+ int add_zero = 0;
+ int enable_varlen = 0;
+
+ testmode = !resp && !nresp;
+
+ if (!nresp)
+ nresp = &dummy_nresp;
+ *nresp = 0;
+
+ if (apdu_buflen >= 4 && apdu_buf[1] == 0x20 && (handle->has_pinpad & 1))
+ ;
+ else if (apdu_buflen >= 4 && apdu_buf[1] == 0x24 && (handle->has_pinpad & 2))
+ ;
+ else
+ return CCID_DRIVER_ERR_NO_PINPAD;
+
+ if (!pininfo->minlen)
+ pininfo->minlen = 1;
+ if (!pininfo->maxlen)
+ pininfo->maxlen = 15;
+
+ /* Note that the 25 is the maximum value the SPR532 allows. */
+ if (pininfo->minlen < 1 || pininfo->minlen > 25
+ || pininfo->maxlen < 1 || pininfo->maxlen > 25
+ || pininfo->minlen > pininfo->maxlen)
+ return CCID_DRIVER_ERR_INV_VALUE;
+
+ /* We have only tested a few readers so better don't risk anything
+ and do not allow the use with other readers. */
+ switch (handle->id_vendor)
+ {
+ case VENDOR_SCM: /* Tested with SPR 532. */
+ case VENDOR_KAAN: /* Tested with KAAN Advanced (1.02). */
+ case VENDOR_FSIJ: /* Tested with Gnuk (0.21). */
+ pininfo->maxlen = 25;
+ enable_varlen = 1;
+ break;
+ case VENDOR_REINER:/* Tested with cyberJack go */
+ case VENDOR_VASCO: /* Tested with DIGIPASS 920 */
+ enable_varlen = 1;
+ break;
+ case VENDOR_CHERRY:
+ pininfo->maxlen = 15;
+ enable_varlen = 1;
+ /* The CHERRY XX44 keyboard echos an asterisk for each entered
+ character on the keyboard channel. We use a special variant
+ of PC_to_RDR_Secure which directs these characters to the
+ smart card's bulk-in channel. We also need to append a zero
+ Lc byte to the APDU. It seems that it will be replaced with
+ the actual length instead of being appended before the APDU
+ is send to the card. */
+ add_zero = 1;
+ if (handle->id_product != CHERRY_ST2000)
+ cherry_mode = 1;
+ break;
+ case VENDOR_NXP:
+ if (handle->id_product == CRYPTOUCAN){
+ pininfo->maxlen = 25;
+ enable_varlen = 1;
+ break;
+ }
+ return CCID_DRIVER_ERR_NOT_SUPPORTED;
+ case VENDOR_GEMPC:
+ if (handle->id_product == GEMPC_PINPAD)
+ {
+ enable_varlen = 0;
+ pininfo->minlen = 4;
+ pininfo->maxlen = 8;
+ break;
+ }
+ else if (handle->id_product == GEMPC_EZIO)
+ {
+ pininfo->maxlen = 25;
+ enable_varlen = 1;
+ break;
+ }
+ return CCID_DRIVER_ERR_NOT_SUPPORTED;
+ default:
+ if ((handle->id_vendor == VENDOR_VEGA &&
+ handle->id_product == VEGA_ALPHA))
+ {
+ enable_varlen = 0;
+ pininfo->minlen = 4;
+ pininfo->maxlen = 8;
+ break;
+ }
+ return CCID_DRIVER_ERR_NOT_SUPPORTED;
+ }
+
+ if (enable_varlen)
+ pininfo->fixedlen = 0;
+
+ if (testmode)
+ return 0; /* Success */
+
+ if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16)
+ return CCID_DRIVER_ERR_NOT_SUPPORTED;
+
+ ccid_vendor_specific_pinpad_setup (handle);
+
+ msg = send_buffer;
+ msg[0] = cherry_mode? 0x89 : PC_to_RDR_Secure;
+ msg[5] = 0; /* slot */
+ msg[6] = seqno = handle->seqno++;
+ msg[7] = 0; /* bBWI */
+ msg[8] = 0; /* RFU */
+ msg[9] = 0; /* RFU */
+ msg[10] = apdu_buf[1] == 0x20 ? 0 : 1;
+ /* Perform PIN verification or PIN modification. */
+ msg[11] = 0; /* Timeout in seconds. */
+ msg[12] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
+ if (handle->id_vendor == VENDOR_SCM)
+ {
+ /* For the SPR532 the next 2 bytes need to be zero. We do this
+ for all SCM products. Kudos to Martin Paljak for this
+ hint. */
+ msg[13] = msg[14] = 0;
+ }
+ else
+ {
+ msg[13] = pininfo->fixedlen; /* bmPINBlockString:
+ 0 bits of pin length to insert.
+ PIN block size by fixedlen. */
+ msg[14] = 0x00; /* bmPINLengthFormat:
+ Units are bytes, position is 0. */
+ }
+
+ msglen = 15;
+ if (apdu_buf[1] == 0x24)
+ {
+ msg[msglen++] = 0; /* bInsertionOffsetOld */
+ msg[msglen++] = pininfo->fixedlen; /* bInsertionOffsetNew */
+ }
+
+ /* The following is a little endian word. */
+ msg[msglen++] = pininfo->maxlen; /* wPINMaxExtraDigit-Maximum. */
+ msg[msglen++] = pininfo->minlen; /* wPINMaxExtraDigit-Minimum. */
+
+ if (apdu_buf[1] == 0x24)
+ msg[msglen++] = apdu_buf[2] == 0 ? 0x03 : 0x01;
+ /* bConfirmPIN
+ * 0x00: new PIN once
+ * 0x01: new PIN twice (confirmation)
+ * 0x02: old PIN and new PIN once
+ * 0x03: old PIN and new PIN twice (confirmation)
+ */
+
+ msg[msglen] = 0x02; /* bEntryValidationCondition:
+ Validation key pressed */
+ if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
+ msg[msglen] |= 0x01; /* Max size reached. */
+ msglen++;
+
+ if (apdu_buf[1] == 0x20)
+ msg[msglen++] = 0x01; /* bNumberMessage. */
+ else
+ msg[msglen++] = 0x03; /* bNumberMessage. */
+
+ msg[msglen++] = 0x09; /* wLangId-Low: English FIXME: use the first entry. */
+ msg[msglen++] = 0x04; /* wLangId-High. */
+
+ if (apdu_buf[1] == 0x20)
+ msg[msglen++] = 0; /* bMsgIndex. */
+ else
+ {
+ msg[msglen++] = 0; /* bMsgIndex1. */
+ msg[msglen++] = 1; /* bMsgIndex2. */
+ msg[msglen++] = 2; /* bMsgIndex3. */
+ }
+
+ /* Calculate Lc. */
+ n = pininfo->fixedlen;
+ if (apdu_buf[1] == 0x24)
+ n += pininfo->fixedlen;
+
+ /* bTeoProlog follows: */
+ msg[msglen++] = handle->nonnull_nad? ((1 << 4) | 0): 0;
+ msg[msglen++] = ((handle->t1_ns & 1) << 6); /* I-block */
+ if (n)
+ msg[msglen++] = n + 5; /* apdulen should be filled for fixed length. */
+ else
+ msg[msglen++] = 0; /* The apdulen will be filled in by the reader. */
+ /* APDU follows: */
+ msg[msglen++] = apdu_buf[0]; /* CLA */
+ msg[msglen++] = apdu_buf[1]; /* INS */
+ msg[msglen++] = apdu_buf[2]; /* P1 */
+ msg[msglen++] = apdu_buf[3]; /* P2 */
+ if (add_zero)
+ msg[msglen++] = 0;
+ else if (pininfo->fixedlen != 0)
+ {
+ msg[msglen++] = n;
+ memset (&msg[msglen], 0xff, n);
+ msglen += n;
+ }
+ /* An EDC is not required. */
+ set_msg_len (msg, msglen - 10);
+
+ rc = bulk_out (handle, msg, msglen, 0);
+ if (rc)
+ return rc;
+
+ msg = recv_buffer;
+ rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen,
+ RDR_to_PC_DataBlock, seqno, 30000, 0);
+ if (rc)
+ return rc;
+
+ tpdu = msg + 10;
+ tpdulen = msglen - 10;
+
+ if (handle->apdu_level)
+ {
+ if (resp)
+ {
+ if (tpdulen > maxresplen)
+ {
+ DEBUGOUT_2 ("provided buffer too short for received data "
+ "(%u/%u)\n",
+ (unsigned int)tpdulen, (unsigned int)maxresplen);
+ return CCID_DRIVER_ERR_INV_VALUE;
+ }
+
+ memcpy (resp, tpdu, tpdulen);
+ *nresp = tpdulen;
+ }
+ return 0;
+ }
+
+ if (tpdulen < 4)
+ {
+#ifdef USE_NPTH
+ npth_unprotect ();
+#endif
+ libusb_clear_halt (handle->idev, handle->ep_bulk_in);
+#ifdef USE_NPTH
+ npth_protect ();
+#endif
+ return CCID_DRIVER_ERR_ABORTED;
+ }
+ if (debug_level > 1)
+ DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n",
+ ((msg[11] & 0xc0) == 0x80)? 'R' :
+ (msg[11] & 0x80)? 'S' : 'I',
+ ((msg[11] & 0x80)? !!(msg[11]& 0x10) : !!(msg[11] & 0x40)),
+ ((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0,
+ (!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":""));
+
+ if (!(tpdu[1] & 0x80))
+ { /* This is an I-block. */
+ /* Last block sent was successful. */
+ handle->t1_ns ^= 1;
+
+ if (!!(tpdu[1] & 0x40) != handle->t1_nr)
+ { /* Response does not match our sequence number. */
+ DEBUGOUT ("I-block with wrong seqno received\n");
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+ }
+
+ handle->t1_nr ^= 1;
+
+ p = tpdu + 3; /* Skip the prologue field. */
+ n = tpdulen - 3 - 1; /* Strip the epilogue field. */
+ /* fixme: verify the checksum. */
+ if (resp)
+ {
+ if (n > maxresplen)
+ {
+ DEBUGOUT_2 ("provided buffer too short for received data "
+ "(%u/%u)\n",
+ (unsigned int)n, (unsigned int)maxresplen);
+ return CCID_DRIVER_ERR_INV_VALUE;
+ }
+
+ memcpy (resp, p, n);
+ *nresp += n;
+ }
+
+ if (!(tpdu[1] & 0x20))
+ return 0; /* No chaining requested - ready. */
+
+ DEBUGOUT ("chaining requested but not supported for Secure operation\n");
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+ }
+ else if ((tpdu[1] & 0xc0) == 0x80)
+ { /* This is a R-block. */
+ if ( (tpdu[1] & 0x0f))
+ { /* Error: repeat last block */
+ DEBUGOUT ("No retries supported for Secure operation\n");
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+ }
+ else if (!!(tpdu[1] & 0x10) == handle->t1_ns)
+ { /* Response does not match our sequence number. */
+ DEBUGOUT ("R-block with wrong seqno received on more bit\n");
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+ }
+ else
+ { /* Send next chunk. */
+ DEBUGOUT ("chaining not supported on Secure operation\n");
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+ }
+ }
+ else
+ { /* This is a S-block. */
+ DEBUGOUT_2 ("T=1: S-block %s received cmd=%d for Secure operation\n",
+ (tpdu[1] & 0x20)? "response": "request",
+ (tpdu[1] & 0x1f));
+ return CCID_DRIVER_ERR_CARD_IO_ERROR;
+ }
+
+ return 0;
+}
+
+
+
+
+#ifdef TEST
+
+
+static void
+print_error (int err)
+{
+ const char *p;
+ char buf[50];
+
+ switch (err)
+ {
+ case 0: p = "success"; break;
+ case CCID_DRIVER_ERR_OUT_OF_CORE: p = "out of core"; break;
+ case CCID_DRIVER_ERR_INV_VALUE: p = "invalid value"; break;
+ case CCID_DRIVER_ERR_NO_DRIVER: p = "no driver"; break;
+ case CCID_DRIVER_ERR_NOT_SUPPORTED: p = "not supported"; break;
+ case CCID_DRIVER_ERR_LOCKING_FAILED: p = "locking failed"; break;
+ case CCID_DRIVER_ERR_BUSY: p = "busy"; break;
+ case CCID_DRIVER_ERR_NO_CARD: p = "no card"; break;
+ case CCID_DRIVER_ERR_CARD_INACTIVE: p = "card inactive"; break;
+ case CCID_DRIVER_ERR_CARD_IO_ERROR: p = "card I/O error"; break;
+ case CCID_DRIVER_ERR_GENERAL_ERROR: p = "general error"; break;
+ case CCID_DRIVER_ERR_NO_READER: p = "no reader"; break;
+ case CCID_DRIVER_ERR_ABORTED: p = "aborted"; break;
+ default: sprintf (buf, "0x%05x", err); p = buf; break;
+ }
+ fprintf (stderr, "operation failed: %s\n", p);
+}
+
+
+static void
+print_data (const unsigned char *data, size_t length)
+{
+ if (length >= 2)
+ {
+ fprintf (stderr, "operation status: %02X%02X\n",
+ data[length-2], data[length-1]);
+ length -= 2;
+ }
+ if (length)
+ {
+ fputs (" returned data:", stderr);
+ for (; length; length--, data++)
+ fprintf (stderr, " %02X", *data);
+ putc ('\n', stderr);
+ }
+}
+
+static void
+print_result (int rc, const unsigned char *data, size_t length)
+{
+ if (rc)
+ print_error (rc);
+ else if (data)
+ print_data (data, length);
+}
+
+int
+main (int argc, char **argv)
+{
+ gpg_error_t err;
+ ccid_driver_t ccid;
+ int slotstat;
+ unsigned char result[512];
+ size_t resultlen;
+ int no_pinpad = 0;
+ int verify_123456 = 0;
+ int did_verify = 0;
+ int no_poll = 0;
+ int idx_max;
+ struct ccid_dev_table *ccid_table;
+
+ if (argc)
+ {
+ argc--;
+ argv++;
+ }
+
+ while (argc)
+ {
+ if ( !strcmp (*argv, "--list"))
+ {
+ char *p;
+ p = ccid_get_reader_list ();
+ if (!p)
+ return 1;
+ fputs (p, stderr);
+ free (p);
+ return 0;
+ }
+ else if ( !strcmp (*argv, "--debug"))
+ {
+ ccid_set_debug_level (ccid_set_debug_level (-1)+1);
+ argc--; argv++;
+ }
+ else if ( !strcmp (*argv, "--no-poll"))
+ {
+ no_poll = 1;
+ argc--; argv++;
+ }
+ else if ( !strcmp (*argv, "--no-pinpad"))
+ {
+ no_pinpad = 1;
+ argc--; argv++;
+ }
+ else if ( !strcmp (*argv, "--verify-123456"))
+ {
+ verify_123456 = 1;
+ argc--; argv++;
+ }
+ else
+ break;
+ }
+
+ err = ccid_dev_scan (&idx_max, &ccid_table);
+ if (err)
+ return 1;
+
+ if (idx_max == 0)
+ return 1;
+
+ err = ccid_open_reader (argc? *argv:NULL, 0, ccid_table, &ccid, NULL);
+ if (err)
+ return 1;
+
+ ccid_dev_scan_finish (ccid_table, idx_max);
+
+ if (!no_poll)
+ ccid_poll (ccid);
+ fputs ("getting ATR ...\n", stderr);
+ err = ccid_get_atr (ccid, NULL, 0, NULL);
+ if (err)
+ {
+ print_error (err);
+ return 1;
+ }
+
+ if (!no_poll)
+ ccid_poll (ccid);
+ fputs ("getting slot status ...\n", stderr);
+ err = ccid_slot_status (ccid, &slotstat, 1);
+ if (err)
+ {
+ print_error (err);
+ return 1;
+ }
+
+ if (!no_poll)
+ ccid_poll (ccid);
+
+ fputs ("selecting application OpenPGP ....\n", stderr);
+ {
+ static unsigned char apdu[] = {
+ 0, 0xA4, 4, 0, 6, 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01};
+ err = ccid_transceive (ccid,
+ apdu, sizeof apdu,
+ result, sizeof result, &resultlen);
+ print_result (err, result, resultlen);
+ }
+
+
+ if (!no_poll)
+ ccid_poll (ccid);
+
+ fputs ("getting OpenPGP DO 0x65 ....\n", stderr);
+ {
+ static unsigned char apdu[] = { 0, 0xCA, 0, 0x65, 254 };
+ err = ccid_transceive (ccid, apdu, sizeof apdu,
+ result, sizeof result, &resultlen);
+ print_result (err, result, resultlen);
+ }
+
+ if (!no_pinpad)
+ {
+ }
+
+ if (!no_pinpad)
+ {
+ static unsigned char apdu[] = { 0, 0x20, 0, 0x81 };
+ pininfo_t pininfo = { 0, 0, 0 };
+
+ if (ccid_transceive_secure (ccid, apdu, sizeof apdu, &pininfo,
+ NULL, 0, NULL))
+ fputs ("can't verify using a PIN-Pad reader\n", stderr);
+ else
+ {
+ fputs ("verifying CHV1 using the PINPad ....\n", stderr);
+
+ err = ccid_transceive_secure (ccid, apdu, sizeof apdu, &pininfo,
+ result, sizeof result, &resultlen);
+ print_result (err, result, resultlen);
+ did_verify = 1;
+ }
+ }
+
+ if (verify_123456 && !did_verify)
+ {
+ fputs ("verifying that CHV1 is 123456....\n", stderr);
+ {
+ static unsigned char apdu[] = {0, 0x20, 0, 0x81,
+ 6, '1','2','3','4','5','6'};
+ err = ccid_transceive (ccid, apdu, sizeof apdu,
+ result, sizeof result, &resultlen);
+ print_result (err, result, resultlen);
+ }
+ }
+
+ if (!err)
+ {
+ fputs ("getting OpenPGP DO 0x5E ....\n", stderr);
+ {
+ static unsigned char apdu[] = { 0, 0xCA, 0, 0x5E, 254 };
+ err = ccid_transceive (ccid, apdu, sizeof apdu,
+ result, sizeof result, &resultlen);
+ print_result (err, result, resultlen);
+ }
+ }
+
+ ccid_close_reader (ccid);
+
+ return 0;
+}
+
+/*
+ * Local Variables:
+ * compile-command: "gcc -DTEST -DGPGRT_ENABLE_ES_MACROS -DHAVE_NPTH -DUSE_NPTH -Wall -I/usr/include/libusb-1.0 -I/usr/local/include -lusb-1.0 -g ccid-driver.c -lnpth -lgpg-error"
+ * End:
+ */
+#endif /*TEST*/
+#endif /*HAVE_LIBUSB*/