[PATCH] pcsc pinpad support (part 1)

NIIBE Yutaka gniibe at fsij.org
Thu Jan 13 05:59:39 CET 2011


This is the replacement of the (part 1) patch.

2011-01-13 11:06, NIIBE Yutaka wrote:
> I am submitting a series of patches for pcsc pinpad support.
[...]
> The first patch is adding pcsc_control handle and two fields to
> reader_table_s structure; verify_ioctl and modify_ioctl which
> correspond to the operations of PIN Verification and PIN Modification.
> 
> (In the forthcoming another patch,) the value of verify_ioctl and
> modify_ioctl will be filled using pcsc_control.  Since the value is in
> the format of big-endian TLV (Tag-Length-Value), convert_be_u32 is
> added.

This patch includes filling of verify_ioctl and modify_ioctl too.
I don't use convert_be_u32, this time.

Perhaps, check_pcsc_keypad should be more restrictive like
check_ccid_keypad.  This implementation just returns "Yes", when the
reader says so.


2011-01-13  NIIBE Yutaka  <gniibe at fsij.org>

	* pcsc-wrapper.c (load_pcsc_driver): Initialize pcsc_control.
	(handle_control): New.
	(main): Handle the case 6 of handle_control.

	* apdu.c: Include "iso7816.h".
	(struct reader_table_s): Add fields verify_ioctl and
	modify_ioctl in pcsc.
	(CM_IOCTL_GET_FEATURE_REQUEST, FEATURE_VERIFY_PIN_DIRECT)
	(FEATURE_MODIFY_PIN_DIRECT): New.
	(pcsc_control): New.
	(check_pcsc_keypad): New.
	(control_pcsc_direct, control_pcsc_wrapped, control_pcsc): New.
	(new_reader_slot): Initialize with check_pcsc_keypad,
	verify_ioctl and modify_ioctl.
	(apdu_open_reader): Initialize pcsc_control.

---
 scd/apdu.c         |  232 +++++++++++++++++++++++++++++++++++++++++++++++++++-
 scd/pcsc-wrapper.c |   50 +++++++++++-
 2 files changed, 277 insertions(+), 5 deletions(-)

diff --git a/scd/apdu.c b/scd/apdu.c
index 80c933e..4942d05 100644
--- a/scd/apdu.c
+++ b/scd/apdu.c
@@ -61,6 +61,7 @@

 #include "apdu.h"
 #include "ccid-driver.h"
+#include "iso7816.h"


 /* Due to conflicting use of threading libraries we usually can't link
@@ -117,6 +118,8 @@ struct reader_table_s {
     unsigned long context;
     unsigned long card;
     unsigned long protocol;
+    unsigned long verify_ioctl;
+    unsigned long modify_ioctl;
 #ifdef NEED_PCSC_WRAPPER
     int req_fd;
     int rsp_fd;
@@ -235,6 +238,11 @@ static char (* DLSTDCALL CT_close) (unsigned short ctn);
 #define PCSC_E_READER_UNAVAILABLE      0x80100017
 #define PCSC_W_REMOVED_CARD            0x80100069

+#define CM_IOCTL_GET_FEATURE_REQUEST (0x42000000 + 3400)
+#define FEATURE_VERIFY_PIN_DIRECT        0x06
+#define FEATURE_MODIFY_PIN_DIRECT        0x07
+
+
 /* The PC/SC error is defined as a long as per specs.  Due to left
    shifts bit 31 will get sign extended.  We use this mask to fix
    it. */
@@ -303,6 +311,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 +326,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 check_pcsc_keypad (int slot, int command, int pin_mode,
+                              int pinlen_min, int pinlen_max, int pin_padlen);


 
@@ -354,7 +371,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 = check_pcsc_keypad;
   reader_table[reader].dump_status_reader = NULL;
   reader_table[reader].set_progress_cb = NULL;

@@ -367,6 +384,8 @@ new_reader_slot (void)
   reader_table[reader].pcsc.rsp_fd = -1;
   reader_table[reader].pcsc.pid = (pid_t)(-1);
 #endif
+  reader_table[reader].pcsc.verify_ioctl = 0;
+  reader_table[reader].pcsc.modify_ioctl = 0;

   return reader;
 }
@@ -1168,6 +1187,150 @@ pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen,

 #ifndef NEED_PCSC_WRAPPER
 static int
+control_pcsc_direct (int slot, unsigned long ioctl_code,
+                     const unsigned char *cntlbuf, size_t len,
+                     unsigned char *buffer, size_t *buflen)
+{
+  long err;
+
+  err = pcsc_control (reader_table[slot].pcsc.card, ioctl_code,
+                      cntlbuf, len, buffer, *buflen, buflen);
+  if (err)
+    {
+      log_error ("pcsc_control failed: %s (0x%lx)\n",
+                 pcsc_error_string (err), err);
+      return pcsc_error_to_sw (err);
+    }
+
+  return 0;
+}
+#endif /*!NEED_PCSC_WRAPPER*/
+
+
+#ifdef NEED_PCSC_WRAPPER
+static int
+control_pcsc_wrapped (int slot, unsigned long ioctl_code,
+                      const unsigned char *cntlbuf, 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 + 4) >> 24);
+  msgbuf[2] = ((len + 4) >> 16);
+  msgbuf[3] = ((len + 4) >>  8);
+  msgbuf[4] = ((len + 4)      );
+  msgbuf[5] = (ioctl_code >> 24);
+  msgbuf[6] = (ioctl_code >> 16);
+  msgbuf[7] = (ioctl_code >>  8);
+  msgbuf[8] = (ioctl_code      );
+  if ( writen (slotp->pcsc.req_fd, msgbuf, 9)
+       || writen (slotp->pcsc.req_fd, cntlbuf, 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 pcsc_error_to_sw (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 pcsc_error_to_sw (err);
+}
+#endif /*NEED_PCSC_WRAPPER*/
+
+
+
+/* Do some control with the value of IOCTL_CODE to the card inserted
+   to SLOT.  Input buffer is specified by CNTLBUF of length LEN.
+   Output buffer is specified by BUFFER of length *BUFLEN, and the
+   actual output size will be stored at BUFLEN.  Returns: A status word.
+   This routine is used for PIN pad input support.  */
+static int
+control_pcsc (int slot, unsigned long ioctl_code,
+              const unsigned char *cntlbuf, size_t len,
+              unsigned char *buffer, size_t *buflen)
+{
+#ifdef NEED_PCSC_WRAPPER
+  return control_pcsc_wrapped (slot, ioctl_code, cntlbuf, len, buffer, buflen);
+#else
+  return control_pcsc_direct (slot, ioctl_code, cntlbuf, len, buffer, buflen);
+#endif
+}
+
+
+#ifndef NEED_PCSC_WRAPPER
+static int
 close_pcsc_reader_direct (int slot)
 {
   pcsc_release_context (reader_table[slot].pcsc.context);
@@ -1802,6 +1965,66 @@ open_pcsc_reader (const char *portstr)
 }


+/* Check whether the reader supports the ISO command code COMMAND
+   on the keypad.  Return 0 on success.  */
+static int
+check_pcsc_keypad (int slot, int command, int pin_mode,
+                   int pinlen_min, int pinlen_max, int pin_padlen)
+{
+  unsigned char buf[256];
+  size_t len = 256;
+  int sw;
+
+  (void)pin_mode;
+  (void)pinlen_min;
+  (void)pinlen_max;
+  (void)pin_padlen;
+
+ check_again:
+  if (command == ISO7816_VERIFY)
+    {
+      if (reader_table[slot].pcsc.verify_ioctl == (unsigned long)-1)
+        return SW_NOT_SUPPORTED;
+      else if (reader_table[slot].pcsc.verify_ioctl != 0)
+        return 0;                       /* Success */
+    }
+  else if (command == ISO7816_CHANGE_REFERENCE_DATA)
+    {
+      if (reader_table[slot].pcsc.modify_ioctl == (unsigned long)-1)
+        return SW_NOT_SUPPORTED;
+      else if (reader_table[slot].pcsc.modify_ioctl != 0)
+        return 0;                       /* Success */
+    }
+  else
+    return SW_NOT_SUPPORTED;
+
+  reader_table[slot].pcsc.verify_ioctl = (unsigned long)-1;
+  reader_table[slot].pcsc.modify_ioctl = (unsigned long)-1;
+
+  sw = control_pcsc (slot, CM_IOCTL_GET_FEATURE_REQUEST, NULL, 0, buf, &len);
+  if (sw)
+    return SW_NOT_SUPPORTED;
+  else
+    {
+      unsigned char *p = buf;
+
+      while (p < buf + len)
+        {
+          unsigned char code = *p++;
+
+          p++;                  /* Skip length */
+          if (code == FEATURE_VERIFY_PIN_DIRECT)
+            reader_table[slot].pcsc.verify_ioctl
+              = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
+          else if (code == FEATURE_MODIFY_PIN_DIRECT)
+            reader_table[slot].pcsc.modify_ioctl
+              = (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
+          p += 4;
+        }
+    }
+
+  goto check_again;
+}
 
 #ifdef HAVE_LIBUSB
 /*
@@ -2438,6 +2661,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 +2674,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 +2692,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;
         }
diff --git a/scd/pcsc-wrapper.c b/scd/pcsc-wrapper.c
index a7b2198..9379655 100644
--- a/scd/pcsc-wrapper.c
+++ b/scd/pcsc-wrapper.c
@@ -178,6 +178,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 +342,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 +355,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 +374,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);
     }
@@ -721,6 +731,38 @@ handle_transmit (unsigned char *argbuf, size_t arglen)
 }


+/* Handle a control request.  The argument is expected to be a buffer
+   which contains CONTROL_CODE (4-byte) and INPUT_BYTES.
+ */
+static void
+handle_control (unsigned char *argbuf, size_t arglen)
+{
+  long err;
+  unsigned long ioctl_code;
+  unsigned long recv_len = 1024;
+  unsigned char buffer[1024];
+
+  if (arglen < 4)
+    bad_request ("CONTROL");
+
+  ioctl_code = (argbuf[0] << 24) | (argbuf[1] << 16) | (argbuf[2] << 8) | argbuf[3];
+  argbuf += 4;
+  arglen -= 4;
+
+  recv_len = sizeof (buffer);
+  err = pcsc_control (pcsc_card, ioctl_code, 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)
@@ -832,6 +874,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);
-- 
1.7.2.3




More information about the Gnupg-devel mailing list