Supporting not-so-smart card readers

NIIBE Yutaka gniibe at fsij.org
Tue Jan 8 07:59:02 CET 2013


Hello,

Last year, I bought a card reader with pinpad.  It's:

   http://pcsclite.alioth.debian.org/ccid/supported.html#0x08E60x3478

lsusb says:

	08e6:3478 Gemplus PinPad Smart Card Reader

Unfortunately, this reader is not yet supported by the internal driver
of GnuPG.  PC/SC is needed for this card reader to use GnuPG.

The internal driver requires CCID to have the feature of 'Auto
configuration based on ATR', which is not available for the card
reader.

I am currently testing the patch below (against master branch), so
that I can use the card reader with the internal driver of GnuPG.
Basically, it is to add setting parameters based on ATR string.

It works for me.  No, pinpad is not yet supported, and some more work
is required to support fixed length keypad input.

I think that many card readers will work with this change (with no
PC/SC service).  Your comments, testing are welcome.

Some notes:

* The card reader claims that it supports the feature of nonnull NAD,
  but it doesn't work when NAD != 0.

* It is needed to specify power voltage.  Auto (=0) doesn't work.

The patch:

* scd/ccid-driver.c (struct ccid_driver_s): Add auto_voltage,
auto_param, and auto_pps.
(parse_ccid_descriptor): Set auto_voltage, auto_param, and auto_pps.
Support non-autoconf readers.
(update_param_by_atr): New.
(ccid_get_atr): Use 5V for PowerOn when auto_voltage is not supported.
Use 0x10 when nonnull_nad for SetParameters.
Call update_param_by_atr for parsing ATR, and use param for
SetParameters.
Send PPS if reader requires it and card is negotiable.
When bNadValue in the return values of SetParameters == 0,
clear handle->nonnull_nad flag.
--
This change is to support more card readers by the internal driver.
Tested with 08e6:3478 Gemplus PinPad Smart Card Reader.
---
 scd/ccid-driver.c |  290 ++++++++++++++++++++++++++++++++++++++++++++++-------
 1 file changed, 253 insertions(+), 37 deletions(-)

diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c
index 3fe932c..5b3bcaf 100644
--- a/scd/ccid-driver.c
+++ b/scd/ccid-driver.c
@@ -264,6 +264,9 @@ struct ccid_driver_s
   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 powered_off:1;
   unsigned int has_pinpad:2;
@@ -758,7 +761,7 @@ parse_ccid_descriptor (ccid_driver_t handle,
 {
   unsigned int i;
   unsigned int us;
-  int have_t1 = 0, have_tpdu=0, have_auto_conf = 0;
+  int have_t1 = 0, have_tpdu=0;
 

   handle->nonnull_nad = 0;
@@ -767,6 +770,9 @@ parse_ccid_descriptor (ccid_driver_t handle,
   handle->ifsd = 0;
   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, handle->bcd_device);
   if (buflen < 54 || buf[0] < 54)
@@ -842,22 +848,31 @@ parse_ccid_descriptor (ccid_driver_t handle,
   DEBUGOUT_1 ("  dwFeatures       %08X\n", us);
   if ((us & 0x0002))
     {
-      DEBUGOUT ("    Auto configuration based on ATR\n");
-      have_auto_conf = 1;
+      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");
+    {
+      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");
+    {
+      DEBUGOUT ("    Auto parameter negotiation made by CCID\n");
+      handle->auto_param = 1;
+    }
   else if ((us & 0x0080))
-    DEBUGOUT ("    Auto PPS made by CCID\n");
-  else if ((us & (0x0040 | 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))
@@ -935,11 +950,10 @@ parse_ccid_descriptor (ccid_driver_t handle,
       DEBUGOUT_LF ();
     }
 
-  if (!have_t1 || !(have_tpdu  || handle->apdu_level) || !have_auto_conf)
+  if (!have_t1 || !(have_tpdu  || handle->apdu_level))
     {
       DEBUGOUT ("this drivers requires that the reader supports T=1, "
-                "TPDU or APDU level exchange and auto configuration - "
-                "this is not available\n");
+                "TPDU or APDU level exchange - this is not available\n");
       return -1;
     }
 
@@ -2338,6 +2352,151 @@ ccid_slot_status (ccid_driver_t handle, int *statusbits)
 }
 

+/* 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
@@ -2354,6 +2513,15 @@ ccid_get_atr (ccid_driver_t handle,
   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);
@@ -2368,7 +2536,8 @@ ccid_get_atr (ccid_driver_t handle,
   msg[0] = PC_to_RDR_IccPowerOn;
   msg[5] = 0; /* slot */
   msg[6] = seqno = handle->seqno++;
-  msg[7] = 0; /* power select (0=auto, 1=5V, 2=3V, 3=1.8V) */
+  /* 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);
@@ -2410,23 +2579,73 @@ ccid_get_atr (ccid_driver_t handle,
       *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;
-  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;
+
+  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: %d\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;
@@ -2437,16 +2656,7 @@ ccid_get_atr (ccid_driver_t handle,
   msg[9] = 0; /* RFU */
 
   if (!got_param)
-    {
-      /* FIXME: Get those values from the ATR. */
-      msg[10]= 0x01; /* Fi/Di */
-      msg[11]= 0x10; /* LRC, direct convention. */
-      msg[12]= 0;    /* Extra guardtime. */
-      msg[13]= 0x41; /* BWI/CWI */
-      msg[14]= 0;    /* No clock stoppping. */
-      msg[15]= 254;  /* IFSC */
-      msg[16]= 0;    /* Does not support non default NAD values. */
-    }
+    memcpy (&msg[10], param, 7);
   set_msg_len (msg, 7);
   msglen = 10 + 7;
 
@@ -2463,6 +2673,12 @@ ccid_get_atr (ccid_driver_t handle,
   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;
 
-- 
1.7.10.4





More information about the Gnupg-devel mailing list