smartcard: PIN pad support

NIIBE Yutaka gniibe at fsij.org
Wed Jan 12 06:40:35 CET 2011


2011-01-05 17:10, Werner Koch wrote:
> On Wed,  5 Jan 2011 05:10, gniibe at fsij.org said:
> 
>>   * It is only used for VERIFY command.
>>
>>     Yes, we have the functions iso7816_change_reference_data_kp and
>>     iso7816_reset_retry_counter_kp, but callers are not yet
>>     implemented to support PIN pad.
> 
> Right.  My fear is that a little bug in the code or one of the readers
> turns the card into a brick (v1 cards) or renders the keys unusable (v2).
> Thus this support would need extensive testing.

Thanks for your reply.  I understand the current situation.

I think that iso7816_reset_retry_counter_kp is not needed
(iso7816_verify_kp and iso7816_change_reference_data_kp only) because
CCID protocol only defines PIN Verification and PIN Modification
operations.

I am testing GnuPG version 2 on Debian with pcsc-lite.  Attached is a
patch to enable pcsc-lite backend to support PINPAD input.  Note that
I don't think this is ready to merge (yet), because it's not that
clean and there are code duplicates and interface mismatches, etc.
This is to show it's not that far for pcsc-lite.

For pcsc-lite (I guess that it's same for PCSC on Windows), the CCID
message of PC_to_RDR_Secure (= 0x69) can be composed by SCardControl
API.


8<-------------------------------------------------

diff --git a/scd/Makefile.am b/scd/Makefile.am
index 923ebfe..b2aab01 100644
--- a/scd/Makefile.am
+++ b/scd/Makefile.am
@@ -22,7 +22,7 @@ if ! HAVE_W32_SYSTEM
 libexec_PROGRAMS = gnupg-pcsc-wrapper
 endif

-AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/intl -I$(top_srcdir)/common
+AM_CPPFLAGS = -I$(top_srcdir)/gl -I$(top_srcdir)/intl -I$(top_srcdir)/common -I/usr/include/PCSC

 include $(top_srcdir)/am/cmacros.am

diff --git a/scd/apdu.c b/scd/apdu.c
index 80c933e..656801d 100644
--- a/scd/apdu.c
+++ b/scd/apdu.c
@@ -33,6 +33,8 @@
 # include <fcntl.h>
 # include <pth.h>
 #endif
+#include <winscard.h>
+#include <reader.h>


 /* If requested include the definitions for the remote APDU protocol
@@ -121,6 +123,9 @@ struct reader_table_s {
     int req_fd;
     int rsp_fd;
     pid_t pid;
+#else
+    unsigned long verify_ioctl;
+    unsigned long modify_ioctl;
 #endif /*NEED_PCSC_WRAPPER*/
   } pcsc;
 #ifdef USE_G10CODE_RAPDU
@@ -303,6 +308,13 @@ long (* DLSTDCALL pcsc_transmit) (unsigned long card,
                                   unsigned long *recv_len);
 long (* DLSTDCALL pcsc_set_timeout) (unsigned long context,
                                      unsigned long timeout);
+long (* DLSTDCALL pcsc_control) (unsigned long card,
+				 unsigned long control_code,
+				 const void *send_buffer,
+				 unsigned long send_len,
+				 void *recv_buffer,
+				 unsigned long recv_len,
+				 unsigned long *bytes_returned);


 /*  Prototypes.  */
@@ -311,6 +323,8 @@ static int reset_pcsc_reader (int slot);
 static int apdu_get_status_internal (int slot, int hang, int no_atr_reset,
                                      unsigned int *status,
                                      unsigned int *changed);
+static int pcsc_check_keypad (int slot, int command, int pin_mode,
+			      int pinlen_min, int pinlen_max, int pin_padlen);


 
@@ -354,7 +368,7 @@ new_reader_slot (void)
   reader_table[reader].reset_reader = NULL;
   reader_table[reader].get_status_reader = NULL;
   reader_table[reader].send_apdu_reader = NULL;
-  reader_table[reader].check_keypad = NULL;
+  reader_table[reader].check_keypad = pcsc_check_keypad;
   reader_table[reader].dump_status_reader = NULL;
   reader_table[reader].set_progress_cb = NULL;

@@ -1165,6 +1179,170 @@ pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen,
 #endif
 }

+#ifdef NEED_PCSC_WRAPPER
+static int
+pcsc_control_wrapped (int slot, const unsigned char *controlbuf, size_t len,
+		      unsigned char *buffer, size_t *buflen)
+{
+  long err = PCSC_E_NOT_TRANSACTED;
+  reader_table_t slotp;
+  unsigned char msgbuf[9];
+  int i, n;
+  size_t full_len;
+
+  slotp = reader_table + slot;
+
+  msgbuf[0] = 0x06; /* CONTROL command. */
+  msgbuf[1] = (len >> 24);
+  msgbuf[2] = (len >> 16);
+  msgbuf[3] = (len >>  8);
+  msgbuf[4] = (len      );
+  if ( writen (slotp->pcsc.req_fd, msgbuf, 5)
+       || writen (slotp->pcsc.req_fd, controlbuf, len))
+    {
+      log_error ("error sending PC/SC CONTROL request: %s\n",
+		 strerror (errno));
+      goto command_failed;
+    }
+
+  /* Read the response. */
+  if ((i=readn (slotp->pcsc.rsp_fd, msgbuf, 9, &len)) || len != 9)
+    {
+      log_error ("error receiving PC/SC CONTROL response: %s\n",
+		 i? strerror (errno) : "premature EOF");
+      goto command_failed;
+    }
+  len = (msgbuf[1] << 24) | (msgbuf[2] << 16) | (msgbuf[3] << 8 ) | msgbuf[4];
+  if (msgbuf[0] != 0x81 || len < 4)
+    {
+      log_error ("invalid response header from PC/SC received\n");
+      goto command_failed;
+    }
+  len -= 4; /* Already read the error code. */
+  err = PCSC_ERR_MASK ((msgbuf[5] << 24) | (msgbuf[6] << 16)
+		       | (msgbuf[7] << 8 ) | msgbuf[8]);
+  if (err)
+    {
+      log_error ("pcsc_control failed: %s (0x%lx)\n",
+		 pcsc_error_string (err), err);
+      return err;
+    }
+
+  full_len = len;
+
+  n = *buflen < len ? *buflen : len;
+  if ((i=readn (slotp->pcsc.rsp_fd, buffer, n, &len)) || len != n)
+    {
+      log_error ("error receiving PC/SC CONTROL response: %s\n",
+		 i? strerror (errno) : "premature EOF");
+      goto command_failed;
+    }
+  *buflen = n;
+
+  full_len -= len;
+  if (full_len)
+    {
+      log_error ("pcsc_send_apdu: provided buffer too short - truncated\n");
+      err = PCSC_E_INVALID_VALUE;
+    }
+  /* We need to read any rest of the response, to keep the
+     protocol running.  */
+  while (full_len)
+    {
+      unsigned char dummybuf[128];
+
+      n = full_len < DIM (dummybuf) ? full_len : DIM (dummybuf);
+      if ((i=readn (slotp->pcsc.rsp_fd, dummybuf, n, &len)) || len != n)
+	{
+	  log_error ("error receiving PC/SC CONTROL response: %s\n",
+		     i? strerror (errno) : "premature EOF");
+	  goto command_failed;
+	}
+      full_len -= n;
+    }
+
+  if (!err)
+    return 0;
+
+ command_failed:
+  close (slotp->pcsc.req_fd);
+  close (slotp->pcsc.rsp_fd);
+  slotp->pcsc.req_fd = -1;
+  slotp->pcsc.rsp_fd = -1;
+  kill (slotp->pcsc.pid, SIGTERM);
+  slotp->pcsc.pid = (pid_t)(-1);
+  slotp->used = 0;
+  return err;
+}
+#endif /*NEED_PCSC_WRAPPER*/
+
+static int
+pcsc_pinpad_verify (int slot, unsigned char *apdu, size_t apdulen,
+		    unsigned char *buffer, size_t *buflen,
+		    struct pininfo_s *pininfo)
+{
+  long err;
+  PIN_VERIFY_STRUCTURE *pin_verify;
+  unsigned long len = sizeof (PIN_VERIFY_STRUCTURE) + apdulen - 1;
+
+  if (!reader_table[slot].atrlen
+      && (err = reset_pcsc_reader (slot)))
+    return err;
+
+  pin_verify = xtrymalloc (len);
+  if (!pin_verify)
+    return SW_HOST_OUT_OF_CORE;
+
+  pin_verify->bTimerOut = 0x00;
+  pin_verify->bTimerOut2 = 0x00;
+  pin_verify->bmFormatString = 0x82; /* Byte, pos=0, left, ASCII. */
+  pin_verify->bmPINBlockString = 0x00;
+  pin_verify->bmPINLengthFormat = 0x00;
+  ((unsigned char *)&pin_verify->wPINMaxExtraDigit)[0] = pininfo->maxlen;
+  ((unsigned char *)&pin_verify->wPINMaxExtraDigit)[1] = pininfo->minlen;
+  pin_verify->bEntryValidationCondition = 0x02;	/* Validation key pressed */
+  pin_verify->bNumberMessage = 0xff;		/* Default */
+  /* LangId = 0x0409: US English */
+  ((unsigned char *)&pin_verify->wLangId)[0] = 0x09;
+  ((unsigned char *)&pin_verify->wLangId)[1] = 0x04;
+  pin_verify->bMsgIndex = 0x00;
+  pin_verify->bTeoPrologue[0] = 0x00;
+  pin_verify->bTeoPrologue[1] = 0x00;
+  pin_verify->bTeoPrologue[2] = 0x00;
+  ((unsigned char *)&pin_verify->ulDataLength)[0] = apdulen & 0xff;
+  ((unsigned char *)&pin_verify->ulDataLength)[1] = (apdulen >> 8) & 0xff;
+  ((unsigned char *)&pin_verify->ulDataLength)[2] = (apdulen >> 16) & 0xff;
+  ((unsigned char *)&pin_verify->ulDataLength)[3] = apdulen >> 24;
+  memcpy (pin_verify->abData, apdu, apdulen);
+
+#ifdef NEED_PCSC_WRAPPER
+  err = pcsc_control_wrapped (slot, (const unsigned char *)pin_verify,
+			      len, buffer, buflen);
+#else
+  err = pcsc_control (reader_table[slot].pcsc.card,
+		      reader_table[slot].pcsc.verify_ioctl,
+		      pin_verify, len, buffer, *buflen, buflen);
+#endif
+
+  if (err)
+    log_error ("pcsc_control failed: %s (0x%lx)\n",
+               pcsc_error_string (err), err);
+  xfree (pin_verify);
+  return pcsc_error_to_sw (err);
+}
+
+static int
+pcsc_check_keypad (int slot, int command, int pin_mode,
+                   int pinlen_min, int pinlen_max, int pin_padlen)
+{
+  (void)slot;
+  (void)command;
+  (void)pin_mode;
+  (void)pinlen_min;
+  (void)pinlen_max;
+  (void)pin_padlen;
+  return 0;			/* Success */
+}

 #ifndef NEED_PCSC_WRAPPER
 static int
@@ -1261,6 +1439,14 @@ close_pcsc_reader (int slot)

 /* Connect a PC/SC card.  */
 #ifndef NEED_PCSC_WRAPPER
+/* Convert a big endian stored 4 byte value into an unsigned
+   integer. */
+static unsigned int
+convert_be_u32 (const unsigned char *buf)
+{
+  return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
+}
+
 static int
 connect_pcsc_card (int slot)
 {
@@ -1314,6 +1500,28 @@ connect_pcsc_card (int slot)
                                              | APDU_CARD_PRESENT
                                              | APDU_CARD_ACTIVE);
           reader_table[slot].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0);
+	  reader_table[slot].pcsc.verify_ioctl = 0;
+	  reader_table[slot].pcsc.modify_ioctl = 0;
+	  err = pcsc_control (reader_table[slot].pcsc.card,
+			      CM_IOCTL_GET_FEATURE_REQUEST, NULL, 0,
+			      reader, sizeof reader, &readerlen);
+	  if (err)
+	    log_error ("pcsc_control failed: %s (0x%lx)\n",
+		       pcsc_error_string (err), err);
+	  else
+	    {
+	      PCSC_TLV_STRUCTURE *pcsc_tlv;
+	      int i;
+
+	      pcsc_tlv = (PCSC_TLV_STRUCTURE *)reader;
+	      for (i = 0; i < readerlen / sizeof (PCSC_TLV_STRUCTURE); i++)
+		if (pcsc_tlv[i].tag == FEATURE_VERIFY_PIN_DIRECT)
+		  reader_table[slot].pcsc.verify_ioctl
+		    = convert_be_u32 ((const unsigned char *)&pcsc_tlv[i].value);
+		else if (pcsc_tlv[i].tag == FEATURE_MODIFY_PIN_DIRECT)
+		  reader_table[slot].pcsc.modify_ioctl
+		    = convert_be_u32 ((const unsigned char *)&pcsc_tlv[i].value);
+	    }
         }
     }

@@ -2438,6 +2646,7 @@ apdu_open_reader (const char *portstr)
       pcsc_end_transaction   = dlsym (handle, "SCardEndTransaction");
       pcsc_transmit          = dlsym (handle, "SCardTransmit");
       pcsc_set_timeout       = dlsym (handle, "SCardSetTimeout");
+      pcsc_control           = dlsym (handle, "SCardControl");

       if (!pcsc_establish_context
           || !pcsc_release_context
@@ -2450,12 +2659,13 @@ apdu_open_reader (const char *portstr)
           || !pcsc_begin_transaction
           || !pcsc_end_transaction
           || !pcsc_transmit
+          || !pcsc_control
           /* || !pcsc_set_timeout */)
         {
           /* Note that set_timeout is currently not used and also not
              available under Windows. */
           log_error ("apdu_open_reader: invalid PC/SC driver "
-                     "(%d%d%d%d%d%d%d%d%d%d%d%d)\n",
+                     "(%d%d%d%d%d%d%d%d%d%d%d%d%d)\n",
                      !!pcsc_establish_context,
                      !!pcsc_release_context,
                      !!pcsc_list_readers,
@@ -2467,7 +2677,8 @@ apdu_open_reader (const char *portstr)
                      !!pcsc_begin_transaction,
                      !!pcsc_end_transaction,
                      !!pcsc_transmit,
-                     !!pcsc_set_timeout );
+                     !!pcsc_set_timeout,
+		     !!pcsc_control );
           dlclose (handle);
           return -1;
         }
@@ -3301,6 +3512,7 @@ apdu_send_simple (int slot, int extended_mode,


 /* Same as apdu_send_simple but uses the keypad of the reader. */
+#if 0
 int
 apdu_send_simple_kp (int slot, int class, int ins, int p0, int p1,
                      int lc, const char *data,
@@ -3316,7 +3528,49 @@ apdu_send_simple_kp (int slot, int class, int ins, int p0, int p1,
   return send_le (slot, class, ins, p0, p1, lc, data, -1,
                   NULL, NULL, &pininfo, 0);
 }
+#else
+int
+apdu_send_simple_kp (int slot, int class, int ins, int p0, int p1,
+                     int lc, const char *data,
+                     int pin_mode,
+                     int pinlen_min, int pinlen_max, int pin_padlen)
+{
+  unsigned char apdu[4];
+  size_t apdulen;
+  struct pininfo_s pininfo;
+  unsigned char result[2];
+  size_t resultlen = 2;
+  long rc;
+  int sw;
+
+  (void)data;
+  (void)lc;
+  apdulen = 0;
+  apdu[apdulen++] = class;
+  apdu[apdulen++] = ins;
+  apdu[apdulen++] = p0;
+  apdu[apdulen++] = p1;
+  pininfo.mode = pin_mode;
+  pininfo.minlen = pinlen_min;
+  pininfo.maxlen = pinlen_max;
+  pininfo.padlen = pin_padlen;
+
+  if ((sw = lock_slot (slot)))
+    return sw;

+  rc = pcsc_pinpad_verify (slot, apdu, apdulen, result, &resultlen, &pininfo);
+  if (rc || resultlen < 2)
+    {
+      log_info ("apdu_send_simple_kp(%d) failed: %s\n",
+		slot, apdu_strerror (rc));
+      unlock_slot (slot);
+      return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
+    }
+  sw = (result[resultlen-2] << 8) | result[resultlen-1];
+  unlock_slot (slot);
+  return sw;
+}
+#endif

 /* This is a more generic version of the apdu sending routine.  It
    takes an already formatted APDU in APDUDATA or length APDUDATALEN
diff --git a/scd/pcsc-wrapper.c b/scd/pcsc-wrapper.c
index a7b2198..53ecb4e 100644
--- a/scd/pcsc-wrapper.c
+++ b/scd/pcsc-wrapper.c
@@ -47,6 +47,8 @@
 #include <stdarg.h>
 #include <assert.h>
 #include <dlfcn.h>
+#include <winscard.h>
+#include <reader.h>


 #define PGM "pcsc-wrapper"
@@ -133,6 +135,8 @@ static unsigned long pcsc_context;  /* The current PC/CS context. */
 static char *current_rdrname;
 static unsigned long pcsc_card;
 static unsigned long pcsc_protocol;
+static unsigned long verify_ioctl;
+static unsigned long modify_ioctl;
 static unsigned char current_atr[33];
 static size_t current_atrlen;

@@ -178,6 +182,13 @@ long (* pcsc_transmit) (unsigned long card,
                         unsigned long *recv_len);
 long (* pcsc_set_timeout) (unsigned long context,
                            unsigned long timeout);
+long (* pcsc_control) (unsigned long card,
+		       unsigned long control_code,
+		       const void *send_buffer,
+		       unsigned long send_len,
+		       void *recv_buffer,
+		       unsigned long recv_len,
+		       unsigned long *bytes_returned);



@@ -335,6 +346,7 @@ load_pcsc_driver (const char *libname)
   pcsc_end_transaction   = dlsym (handle, "SCardEndTransaction");
   pcsc_transmit          = dlsym (handle, "SCardTransmit");
   pcsc_set_timeout       = dlsym (handle, "SCardSetTimeout");
+  pcsc_control           = dlsym (handle, "SCardControl");

   if (!pcsc_establish_context
       || !pcsc_release_context
@@ -347,13 +359,14 @@ load_pcsc_driver (const char *libname)
       || !pcsc_begin_transaction
       || !pcsc_end_transaction
       || !pcsc_transmit
+      || !pcsc_control
       /* || !pcsc_set_timeout */)
     {
       /* Note that set_timeout is currently not used and also not
          available under Windows. */
       fprintf (stderr,
                "apdu_open_reader: invalid PC/SC driver "
-               "(%d%d%d%d%d%d%d%d%d%d%d%d)\n",
+               "(%d%d%d%d%d%d%d%d%d%d%d%d%d)\n",
                !!pcsc_establish_context,
                !!pcsc_release_context,
                !!pcsc_list_readers,
@@ -365,7 +378,8 @@ load_pcsc_driver (const char *libname)
                !!pcsc_begin_transaction,
                !!pcsc_end_transaction,
                !!pcsc_transmit,
-               !!pcsc_set_timeout );
+               !!pcsc_set_timeout,
+	       !!pcsc_control);
       dlclose (handle);
       exit (1);
     }
@@ -373,6 +387,14 @@ load_pcsc_driver (const char *libname)



+/* Convert a big endian stored 4 byte value into an unsigned
+   integer. */
+static unsigned int
+convert_be_u32 (const unsigned char *buf)
+{
+  return (buf[0] << 24) | (buf[1] << 16) | (buf[2] << 8) | buf[3];
+}
+

 /* Handle a open request.  The argument is expected to be a string
    with the port identification.  ARGBUF is always guaranteed to be
@@ -504,6 +526,30 @@ handle_open (unsigned char *argbuf, size_t arglen)
             }
           memcpy (current_atr, atr, atrlen);
           current_atrlen = atrlen;
+
+	  verify_ioctl = 0;
+	  modify_ioctl = 0;
+	  err = pcsc_control (pcsc_card,
+			      CM_IOCTL_GET_FEATURE_REQUEST, NULL, 0,
+			      reader, sizeof reader, &readerlen);
+	  if (err)
+	    {
+	      if (verbose)
+		fprintf (stderr, PGM": pcsc_control failed: %s (0x%lx)\n",
+			 pcsc_error_string (err), err);
+	    }
+	  else
+	    {
+	      PCSC_TLV_STRUCTURE *pcsc_tlv;
+	      int i;
+
+	      pcsc_tlv = (PCSC_TLV_STRUCTURE *)reader;
+	      for (i = 0; i < readerlen / sizeof (PCSC_TLV_STRUCTURE); i++)
+		if (pcsc_tlv[i].tag == FEATURE_VERIFY_PIN_DIRECT)
+		  verify_ioctl = convert_be_u32 ((const unsigned char *)&pcsc_tlv[i].value);
+		else if (pcsc_tlv[i].tag == FEATURE_MODIFY_PIN_DIRECT)
+		  modify_ioctl = convert_be_u32 ((const unsigned char *)&pcsc_tlv[i].value);
+	    }
         }
     }

@@ -723,6 +769,29 @@ handle_transmit (unsigned char *argbuf, size_t arglen)


 static void
+handle_control (unsigned char *argbuf, size_t arglen)
+{
+  long err;
+  unsigned long recv_len = 1024;
+  unsigned char buffer[1024];
+
+  recv_len = sizeof (buffer);
+  err = pcsc_control (pcsc_card, verify_ioctl, argbuf, arglen,
+		      buffer, recv_len, &recv_len);
+  if (err)
+    {
+      if (verbose)
+        fprintf (stderr, PGM": pcsc_control failed: %s (0x%lx)\n",
+                 pcsc_error_string (err), err);
+      request_failed (err);
+      return;
+    }
+  request_succeeded (buffer, recv_len);
+}
+
+
+
+static void
 print_version (int with_help)
 {
   fputs (MYVERSION_LINE "\n"
@@ -832,6 +901,10 @@ main (int argc, char **argv)
           handle_reset (argbuffer, arglen);
           break;

+	case 6:
+	  handle_control (argbuffer, arglen);
+	  break;
+
         default:
           fprintf (stderr, PGM ": invalid request 0x%02X\n", c);
           exit (1);



More information about the Gnupg-devel mailing list