scd: better handling of extended APDU.
authorNIIBE Yutaka <gniibe@fsij.org>
Tue, 14 Apr 2015 05:17:03 +0000 (14:17 +0900)
committerNIIBE Yutaka <gniibe@fsij.org>
Wed, 15 Apr 2015 07:09:08 +0000 (16:09 +0900)
* scd/apdu.c (send_le): Bug fix for not append Z when lc<0&&le<0.
* scd/app-common.h (struct app_ctx_s): Use bit fields for flags.
* scd/ccid-driver.c (CCID_MAX_BUF): New.  Only for OpenPGPcard.
(struct ccid_driver_s): New field of max_ccid_msglen.
 Remove ifsd field.
(parse_ccid_descriptor): Initialize max_ccid_msglen.
(ccid_transceive_apdu_level): Implement sending extended APDU in
chain of CCID message.

--

With this patch, we won't need PC/SC library/service any more.
GnuPG-bug-id: 1947
(backported from 2.1 commit 971d558e862db878a7310e06ed7116dbe36886ab)

scd/apdu.c
scd/app-common.h
scd/ccid-driver.c

index 5fa66a8..6fc1148 100644 (file)
@@ -3678,9 +3678,9 @@ send_le (int slot, int class, int ins, int p0, int p1,
           apdu[apdulen++] = ins;
           apdu[apdulen++] = p0;
           apdu[apdulen++] = p1;
-          apdu[apdulen++] = 0;  /* Z byte: Extended length marker.  */
-          if (lc >= 0)
+          if (lc > 0)
             {
+              apdu[apdulen++] = 0;  /* Z byte: Extended length marker.  */
               apdu[apdulen++] = ((lc >> 8) & 0xff);
               apdu[apdulen++] = (lc & 0xff);
               memcpy (apdu+apdulen, data, lc);
@@ -3689,6 +3689,8 @@ send_le (int slot, int class, int ins, int p0, int p1,
             }
           if (le != -1)
             {
+              if (lc <= 0)
+                apdu[apdulen++] = 0;  /* Z byte: Extended length marker.  */
               apdu[apdulen++] = ((le >> 8) & 0xff);
               apdu[apdulen++] = (le & 0xff);
             }
index 4b2e13e..e48db3c 100644 (file)
@@ -64,10 +64,10 @@ struct app_ctx_s {
   size_t serialnolen;      /* Length in octets of serialnumber. */
   const char *apptype;
   unsigned int card_version;
-  int did_chv1;
-  int force_chv1;   /* True if the card does not cache CHV1. */
-  int did_chv2;
-  int did_chv3;
+  unsigned int did_chv1:1;
+  unsigned int force_chv1:1;   /* True if the card does not cache CHV1. */
+  unsigned int did_chv2:1;
+  unsigned int did_chv3:1;
   struct app_local_s *app_local;  /* Local to the application. */
   struct {
     void (*deinit) (app_t app);
index a179aa8..490569f 100644 (file)
 
 #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)
+        commnd + lc + le = 4 + 3 + 0
+   Sending: write data object of cardholder certificate
+        APDU size = 2048
+        commnd + lc + le = 4 + 3 + 0
+   Receiving: 2048 for cardholder certificate
+*/
+#define CCID_MAX_BUF (2048+7+10)
 
 /* Depending on how this source is used we either define our error
    output to go to stderr or to the jnlib based logging functions.  We
@@ -270,7 +280,7 @@ struct ccid_driver_s
   unsigned char t1_nr;
   unsigned char nonnull_nad;
   int max_ifsd;
-  int ifsd;
+  int max_ccid_msglen;
   int ifsc;
   unsigned char apdu_level:2;     /* Reader supports short APDU level
                                      exchange.  With a value of 2 short
@@ -749,7 +759,7 @@ prepare_special_transport (ccid_driver_t handle)
   handle->nonnull_nad = 0;
   handle->auto_ifsd = 0;
   handle->max_ifsd = 32;
-  handle->ifsd = 0;
+  handle->max_ccid_msglen = CCID_MAX_BUF;
   handle->has_pinpad = 0;
   handle->apdu_level = 0;
   switch (handle->id_product)
@@ -781,7 +791,6 @@ parse_ccid_descriptor (ccid_driver_t handle,
   handle->nonnull_nad = 0;
   handle->auto_ifsd = 0;
   handle->max_ifsd = 32;
-  handle->ifsd = 0;
   handle->has_pinpad = 0;
   handle->apdu_level = 0;
   handle->auto_voltage = 0;
@@ -922,6 +931,7 @@ parse_ccid_descriptor (ccid_driver_t handle,
 
   us = convert_le_u32(buf+44);
   DEBUGOUT_1 ("  dwMaxCCIDMsgLen     %5u\n", us);
+  handle->max_ccid_msglen = us;
 
   DEBUGOUT (  "  bClassGetResponse    ");
   if (buf[48] == 0xff)
@@ -2831,109 +2841,101 @@ is_exlen_apdu (const unsigned char *apdu, size_t apdulen)
 /* 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_buflen,
+                            const unsigned char *apdu_buf, size_t apdu_len,
                             unsigned char *resp, size_t maxresplen,
                             size_t *nresp)
 {
   int rc;
-  unsigned char send_buffer[10+261+300], recv_buffer[10+261+300];
-  const unsigned char *apdu;
-  size_t apdulen;
-  unsigned char *msg;
+  unsigned char msg[CCID_MAX_BUF];
+  const unsigned char *apdu_p;
+  size_t apdu_part_len;
   size_t msglen;
   unsigned char seqno;
   int bwi = 4;
+  unsigned char chain = 0;
 
-  msg = send_buffer;
+  if (apdu_len == 0 || apdu_len > sizeof (msg) - 10)
+    return CCID_DRIVER_ERR_INV_VALUE; /* Invalid length. */
 
-  apdu = apdu_buf;
-  apdulen = apdu_buflen;
-  assert (apdulen);
+  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;
+        }
 
-  /* The maximum length for a short APDU T=1 block is 261.  For an
-     extended APDU T=1 block the maximum length 65544; however
-     extended APDU exchange level is not fully supported yet.  */
-  if (apdulen > sizeof (send_buffer) - 10)
-    return CCID_DRIVER_ERR_INV_VALUE; /* Invalid length. */
+      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;
 
-  msg[0] = PC_to_RDR_XfrBlock;
-  msg[5] = 0; /* slot */
-  msg[6] = seqno = handle->seqno++;
-  msg[7] = bwi; /* bBWI */
-  msg[8] = 0; /* RFU */
-  msg[9] = 0; /* RFU */
-  memcpy (msg+10, apdu, apdulen);
-  set_msg_len (msg, apdulen);
-  msglen = 10 + apdulen;
+      rc = bulk_out (handle, msg, msglen, 0);
+      if (rc)
+        return rc;
 
-  rc = bulk_out (handle, msg, msglen, 0);
-  if (rc)
-    return rc;
+      apdu_p += apdu_part_len;
+      apdu_len -= apdu_part_len;
 
-  msg = recv_buffer;
-  rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen,
-                RDR_to_PC_DataBlock, seqno, 5000, 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 (!(chain & 0x01))
+        break;
 
-  if (msg[9] == 1)
+      chain = 0x02;
+    }
+
+  apdu_len = 0;
+  while (1)
     {
-      size_t total_msglen = msglen;
+      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;
 
-      while (1)
-        {
-          unsigned char status;
+      if (!(msg[9] & 0x01))
+        break;
 
-          msg = recv_buffer + total_msglen;
+      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;
 
-          msg[0] = PC_to_RDR_XfrBlock;
-          msg[5] = 0; /* slot */
-          msg[6] = seqno = handle->seqno++;
-          msg[7] = bwi; /* bBWI */
-          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 recv_buffer - total_msglen, &msglen,
-                        RDR_to_PC_DataBlock, seqno, 5000, 0);
-          if (rc)
-            return rc;
-          status = msg[9];
-          memmove (msg, msg+10, msglen - 10);
-          total_msglen += msglen - 10;
-          if (total_msglen >= sizeof recv_buffer)
-            return CCID_DRIVER_ERR_OUT_OF_CORE;
-
-          if (status == 0x02)
-            break;
-        }
+      rc = bulk_out (handle, msg, msglen, 0);
+      if (rc)
+        return rc;
 
-      apdu = recv_buffer + 10;
-      apdulen = total_msglen - 10;
-    }
-  else
-    {
-      apdu = msg + 10;
-      apdulen = msglen - 10;
+      rc = bulk_in (handle, msg, sizeof msg, &msglen,
+                    RDR_to_PC_DataBlock, seqno, 5000, 0);
+      if (rc)
+        return rc;
     }
 
   if (resp)
     {
-      if (apdulen > maxresplen)
+      if (apdu_len > maxresplen)
         {
           DEBUGOUT_2 ("provided buffer too short for received data "
                       "(%u/%u)\n",
-                      (unsigned int)apdulen, (unsigned int)maxresplen);
+                      (unsigned int)apdu_len, (unsigned int)maxresplen);
           return CCID_DRIVER_ERR_INV_VALUE;
         }
 
-      memcpy (resp, apdu, apdulen);
-      *nresp = apdulen;
+      *nresp = apdu_len;
     }
 
   return 0;