Cryptoflex 32K support patch

Bob Dunlop bob at xyzzy.org.uk
Tue Sep 5 10:50:14 CEST 2006


Hi,

I've been trying to use a new Cryptoflex 32K card (sold as a Cryptoflex
for Windows card) working with GnuPG under Gentoo linux.  The resultant
patch to the scd directory below works for me.

The base I started from was 1.9.22.

All the code and changes are released under GPL2 although as I post this
I realise I've not added the statement to the new file cryptofex.h but
then again it might be better to merge this file with one of the existing
headers.

It works for me in the areas I need/have tested.  I'd strongly recommend
review and further testing.


diff -Naur scd/apdu.c scd-new/apdu.c
--- scd/apdu.c	2006-09-05 08:16:50.000000000 +0000
+++ scd-new/apdu.c	2006-09-05 08:15:18.000000000 +0000
@@ -64,6 +64,7 @@
 #include "apdu.h"
 #include "dynload.h"
 #include "ccid-driver.h"
+#include "cryptoflex.h"
 
 
 /* Due to conflicting use of threading libraries we usually can't link
@@ -152,6 +153,9 @@
 /* A global table to keep track of active readers. */
 static struct reader_table_s reader_table[MAX_READER];
 
+/* Additional info for Cryptoflex 32K cards that needs to be public */
+struct cf32k_info cryptoflex_info[ MAX_READER ];
+
 
 /* ct API function pointer. */
 static char (* DLSTDCALL CT_init) (unsigned short ctn, unsigned short Pn);
@@ -789,6 +793,8 @@
   unsigned int dummy_status;
   int sw = SW_HOST_CARD_IO_ERROR;
 
+  cryptoflex_info[slot].cf32k_mode = unknown;
+
   slotp = reader_table + slot;
 
   if (slotp->pcsc.req_fd == -1
@@ -1255,6 +1261,8 @@
       return 0;
     }
 
+  cryptoflex_info[slot].cf32k_mode = unknown;
+
   msgbuf[0] = 0x02; /* CLOSE command. */
   len = 0;
   msgbuf[1] = (len >> 24);
@@ -2638,7 +2646,8 @@
   assert (sizeof (apdu) >= apdulen);
   /* As safeguard don't pass any garbage from the stack to the driver. */
   memset (apdu+apdulen, 0, sizeof (apdu) - apdulen);
-  resultlen = RESULTLEN;
+  /* Allow for a 256 byte response + 2 byte status */
+  resultlen = RESULTLEN+2;
   rc = send_apdu (slot, apdu, apdulen, result, &resultlen, pininfo);
   if (rc || resultlen < 2)
     {
@@ -2706,7 +2715,7 @@
           apdu[apdulen++] = 0;
           apdu[apdulen++] = len;
           memset (apdu+apdulen, 0, sizeof (apdu) - apdulen);
-          resultlen = RESULTLEN;
+          resultlen = RESULTLEN+2;
           rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
           if (rc || resultlen < 2)
             {
@@ -2729,6 +2738,9 @@
               || sw == SW_SUCCESS
               || sw == SW_EOF_REACHED )
             {
+	      /* Grab what might be the file length for Cryptoflex cards */
+	      cryptoflex_info[slot].cf32k_efsize = (result[2]<<8) | result[3];
+
               if (retbuf && resultlen)
                 {
                   if (p - *retbuf + resultlen > bufsize)
@@ -2878,7 +2890,7 @@
   memcpy (apdu, apdudata, apdudatalen);
   class = apdulen? *apdu : 0;
 
-  resultlen = RESULTLEN;
+  resultlen = RESULTLEN+2;
   rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
   if (rc || resultlen < 2)
     {
@@ -2932,7 +2944,7 @@
           apdu[apdulen++] = 0;
           apdu[apdulen++] = len;
           memset (apdu+apdulen, 0, sizeof (apdu) - apdulen);
-          resultlen = RESULTLEN;
+          resultlen = RESULTLEN+2;
           rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
           if (rc || resultlen < 2)
             {
diff -Naur scd/app-p15.c scd-new/app-p15.c
--- scd/app-p15.c	2006-09-05 08:16:50.000000000 +0000
+++ scd-new/app-p15.c	2006-09-05 08:15:18.000000000 +0000
@@ -44,6 +44,7 @@
 #include "tlv.h"
 #include "apdu.h" /* fixme: we should move the card detection to a
                      separate file */
+#include "cryptoflex.h"
 
 /* Types of cards we know and which needs special treatment. */
 typedef enum
@@ -51,7 +52,8 @@
     CARD_TYPE_UNKNOWN,
     CARD_TYPE_TCOS,
     CARD_TYPE_MICARDO,
-    CARD_TYPE_BELPIC   /* Belgian eID card specs. */
+    CARD_TYPE_BELPIC,  /* Belgian eID card specs. */
+    CARD_TYPE_CRYPTOFLEX	/* Newer 32K cards */
   } 
 card_type_t;
 
@@ -3152,6 +3154,10 @@
       /* TCOS creates signatures always using the local key 0.  MSE
          may not be used. */
     }
+  else if (app->app_local->card_type == CARD_TYPE_CRYPTOFLEX)
+    {
+      /* Don't know how to handle MSE with Cryptoflex so ignore */
+    }
   else if (app->app_local->card_type == CARD_TYPE_MICARDO)
     {
       if (!prkdf->pathlen)
@@ -3358,7 +3364,10 @@
 
       /* Store the card type.  FIXME: We might want to put this into
          the common APP structure. */
-      app->app_local->card_type = card_type;
+      if (is_cryptoflex(slot))
+        app->app_local->card_type = CARD_TYPE_CRYPTOFLEX;
+      else
+        app->app_local->card_type = card_type;
 
       /* Store whether we may and should use direct path selection. */
       app->app_local->direct_path_selection = direct;
diff -Naur scd/cryptoflex.h scd-new/cryptoflex.h
--- scd/cryptoflex.h	1970-01-01 00:00:00.000000000 +0000
+++ scd-new/cryptoflex.h	2006-09-05 08:16:50.000000000 +0000
@@ -0,0 +1,35 @@
+
+/* The newer Cryptoflex 32K cards are PKCS#15 cards but have a somewhat
+   different command set when compared to the cards already supported by GnuPG.
+   In particular Class often has to be set to a non-zero value and the cards
+   only support raw RSA operations. */
+
+/* This card declares additional information and functions necessary to support
+   the card.  The structure parallels the reader_table[] in apdu.c which is
+   unfortunatly declared static. */
+
+/* This should match the setting in apdu.c  The fact that it is in two places
+   probably indicates that it should be in a global header somewhere. */
+#define MAX_READER 4	/* Number of readers we support concurrently. */
+
+
+enum cf32k_mode {
+  unknown,		/* Havn't tested card yet */
+  yes,			/* Yes it's a Cryptoflex 32K */
+  no			/* Not a Cryptoflex 32K */
+};
+
+struct cf32k_info {
+  enum cf32k_mode cf32k_mode;
+  int cf32k_efsize;	/* Size of last EF file selected */
+  int cf32k_keysize;	/* Number of bytes in RSA key */
+};
+
+extern struct cf32k_info cryptoflex_info[ MAX_READER ];
+
+#define is_cryptoflex(slot)	( cryptoflex_info[slot].cf32k_mode == yes )
+#define maybe_cryptoflex(slot)	( cryptoflex_info[slot].cf32k_mode == unknown )
+
+
+/* Reserved tag for the RSA private keys file */
+#define EF_RSA_PRI_TAG	0x0012
diff -Naur scd/iso7816.c scd-new/iso7816.c
--- scd/iso7816.c	2006-09-05 08:16:50.000000000 +0000
+++ scd-new/iso7816.c	2006-09-05 08:23:47.000000000 +0000
@@ -44,6 +44,7 @@
 
 #include "iso7816.h"
 #include "apdu.h"
+#include "cryptoflex.h"
 
 
 #define CMD_SELECT_FILE 0xA4
@@ -132,6 +133,9 @@
                             unsigned int flags)
 {
   int sw;
+
+  if (is_cryptoflex(slot))
+    flags |= 1;
   sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE, 4,
                          (flags&1)? 0 :0x0c, aidlen, aid);
   return map_sw (sw);
@@ -142,7 +146,7 @@
 iso7816_select_file (int slot, int tag, int is_dir,
                      unsigned char **result, size_t *resultlen)
 {
-  int sw, p0, p1;
+  int sw, class, p0, p1;
   unsigned char tagbuf[2];
 
   tagbuf[0] = (tag >> 8) & 0xff;
@@ -156,10 +160,59 @@
     }
   else
     {
-      p0 = (tag == 0x3F00)? 0: is_dir? 1:2;
-      p1 = 0x0c; /* No FC return. */
-      sw = apdu_send_simple (slot, 0x00, CMD_SELECT_FILE,
+      if (is_cryptoflex(slot))
+	{
+	  class = 0xC0;
+	  p0 = 0;
+	  p1 = 0;
+	}
+      else
+	{
+	  class = 0x00;
+	  p0 = (tag == 0x3F00)? 0: is_dir? 1:2;
+	  p1 = 0x0c; /* No FC return. */
+	}
+      sw = apdu_send_simple (slot, class, CMD_SELECT_FILE,
                              p0, p1, 2, (char*)tagbuf );
+      /* If we've not already tried, test if Cryptoflex style commands might
+	 work */
+      if (maybe_cryptoflex(slot))
+        {
+	  if (sw == SW_INS_NOT_SUP)
+	    {
+	      log_debug ("Trying Cryptoflex file select\n");
+	      sw = apdu_send_le (slot, 0xC0, CMD_SELECT_FILE,
+			       0, 0, 2, (char*)tagbuf,-1, result, resultlen );
+	      if ( sw == SW_SUCCESS || (sw & 0xFF00) == SW_MORE_DATA )
+		cryptoflex_info[slot].cf32k_mode = yes;
+	      else
+		cryptoflex_info[slot].cf32k_mode = no;
+	    }
+	  else
+	    {
+	      cryptoflex_info[slot].cf32k_mode = no;
+	    }
+	}
+      /* Nasty side effect for Cryptoflex cards.  The size of the private keys
+	 file indicates the size of the RSA key assuming we've only stored one
+	 key in it anyway.  With more than one key we'd have to get the key
+	 size by some other means like parsing the file */
+      if ( tag == EF_RSA_PRI_TAG && is_cryptoflex(slot))
+        {
+	  switch ( cryptoflex_info[slot].cf32k_efsize )
+	    {
+	    case 647: case 646: cryptoflex_info[slot].cf32k_keysize = 256;
+	      break;
+	    case 327: case 326: cryptoflex_info[slot].cf32k_keysize = 128;
+	      break;
+	    case 247: case 246: cryptoflex_info[slot].cf32k_keysize = 96;
+	      break;
+	    case 167: case 166: cryptoflex_info[slot].cf32k_keysize = 64;
+	      break;
+	    }
+	  log_debug ("Cryptoflex key size %d bits\n",
+		cryptoflex_info[slot].cf32k_keysize * 8 );
+        }
       return map_sw (sw);
     }
 
@@ -247,15 +300,16 @@
                    iso7816_pininfo_t *pininfo)
 {
   int sw;
+  int class = is_cryptoflex(slot)? 0xC0: 0x00;
 
   if (pininfo && pininfo->mode)
-    sw = apdu_send_simple_kp (slot, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv,
+    sw = apdu_send_simple_kp (slot, class, CMD_VERIFY, 0, chvno, chvlen, chv,
                               pininfo->mode,
                               pininfo->minlen,
                               pininfo->maxlen,
                               pininfo->padlen);
   else
-    sw = apdu_send_simple (slot, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv);
+    sw = apdu_send_simple (slot, class, CMD_VERIFY, 0, chvno, chvlen, chv);
   return map_sw (sw);
 }
 
@@ -281,6 +335,7 @@
 {
   int sw;
   char *buf;
+  int class = is_cryptoflex(slot)? 0xF0: 0x00;
 
   if ((!oldchv && oldchvlen)
       || (oldchv && !oldchvlen)
@@ -295,14 +350,14 @@
   memcpy (buf+oldchvlen, newchv, newchvlen);
 
   if (pininfo && pininfo->mode)
-    sw = apdu_send_simple_kp (slot, 0x00, CMD_CHANGE_REFERENCE_DATA,
+    sw = apdu_send_simple_kp (slot, class, CMD_CHANGE_REFERENCE_DATA,
                            oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf,
                            pininfo->mode,
                            pininfo->minlen,
                            pininfo->maxlen,
                            pininfo->padlen);
   else
-    sw = apdu_send_simple (slot, 0x00, CMD_CHANGE_REFERENCE_DATA,
+    sw = apdu_send_simple (slot, class, CMD_CHANGE_REFERENCE_DATA,
                            oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf);
   xfree (buf);
   return map_sw (sw);
@@ -330,19 +385,20 @@
                                 iso7816_pininfo_t *pininfo)
 {
   int sw;
+  int class = is_cryptoflex(slot)? 0xF0: 0x00;
 
   if (!newchv || !newchvlen )
     return gpg_error (GPG_ERR_INV_VALUE);
 
   if (pininfo && pininfo->mode)
-    sw = apdu_send_simple_kp (slot, 0x00, CMD_RESET_RETRY_COUNTER,
+    sw = apdu_send_simple_kp (slot, class, CMD_RESET_RETRY_COUNTER,
                            2, chvno, newchvlen, newchv,
                            pininfo->mode,
                            pininfo->minlen,
                            pininfo->maxlen,
                            pininfo->padlen);
   else
-    sw = apdu_send_simple (slot, 0x00, CMD_RESET_RETRY_COUNTER,
+    sw = apdu_send_simple (slot, class, CMD_RESET_RETRY_COUNTER,
                            2, chvno, newchvlen, newchv);
   return map_sw (sw);
 }
@@ -410,16 +466,33 @@
                              const unsigned char *data, size_t datalen)
 {
   int sw;
+  int class = is_cryptoflex(slot)? 0xF0: 0x00;
 
   if (p1 < 0 || p1 > 255 || p2 < 0 || p2 > 255 )
     return gpg_error (GPG_ERR_INV_VALUE);
 
-  sw = apdu_send_simple (slot, 0x00, CMD_MSE, p1, p2, 
+  sw = apdu_send_simple (slot, class, CMD_MSE, p1, p2, 
                          data? datalen : -1, (const char*)data);
   return map_sw (sw);
 }
 
 
+/* Reverse bytes in a buffer given start and length */
+static void
+reverse_buffer (unsigned char *start, size_t len)
+{
+  unsigned char *end = start + len - 1;
+  unsigned char tmp;
+
+  while ( start < end )
+    {
+      tmp = *start;
+      *start++ = *end;
+      *end-- = tmp;
+    }
+}
+
+
 /* Perform the security operation COMPUTE DIGITAL SIGANTURE.  On
    success 0 is returned and the data is availavle in a newly
    allocated buffer stored at RESULT with its length stored at
@@ -429,14 +502,53 @@
                     unsigned char **result, size_t *resultlen)
 {
   int sw;
+  unsigned char padded_buf[256];
+  int keylen;
 
   if (!data || !datalen || !result || !resultlen)
     return gpg_error (GPG_ERR_INV_VALUE);
   *result = NULL;
   *resultlen = 0;
 
-  sw = apdu_send (slot, 0x00, CMD_PSO, 0x9E, 0x9A, datalen, (const char*)data,
-                  result, resultlen);
+  if (is_cryptoflex(slot))
+    {
+      keylen = cryptoflex_info[slot].cf32k_keysize;
+
+      /* Need space for PKCS#1 RSA padding */
+      if ( datalen + 11 > keylen )
+	return gpg_error (GPG_ERR_INV_VALUE);
+
+      /* Build PKCS#1 RSA padded version of the data */
+      padded_buf[0] = 0x00;
+      padded_buf[1] = 0x01;
+      memset (padded_buf+2, 0xFF, keylen-datalen-3 );
+      padded_buf[keylen-datalen-1] = 0x00;
+      memcpy (padded_buf+keylen-datalen, data, datalen );
+
+      /* The command needs and returns the data LSB first */
+      reverse_buffer (padded_buf, keylen );
+
+      /* If the key is 2048 bits then we have to send the data in 2 chunks */
+      if ( keylen >= 256 )
+	{
+	  sw = apdu_send_simple (slot, 0x10, CMD_INTERNAL_AUTHENTICATE, 0x00,
+				0x00, 10, padded_buf );
+	  sw = apdu_send (slot, 0x00, CMD_INTERNAL_AUTHENTICATE, 0x00, 0x00,
+				keylen-10, padded_buf+10, result, resultlen );
+	}
+      else
+	{
+	  sw = apdu_send (slot, 0x00, CMD_INTERNAL_AUTHENTICATE, 0x00, 0x00,
+				keylen, padded_buf, result, resultlen );
+	}
+      if ( *result != NULL )
+        reverse_buffer (*result, *resultlen );
+    }
+  else
+    {
+      sw = apdu_send (slot, 0x00, CMD_PSO, 0x9E, 0x9A, datalen,
+					(const char*)data, result, resultlen);
+    }
   if (sw != SW_SUCCESS)
     {
       /* Make sure that pending buffers are released. */
@@ -506,13 +618,14 @@
                                unsigned char **result, size_t *resultlen)
 {
   int sw;
+  int class = is_cryptoflex(slot)? 0xC0: 0x00;
 
   if (!data || !datalen || !result || !resultlen)
     return gpg_error (GPG_ERR_INV_VALUE);
   *result = NULL;
   *resultlen = 0;
 
-  sw = apdu_send (slot, 0x00, CMD_INTERNAL_AUTHENTICATE, 0, 0,
+  sw = apdu_send (slot, class, CMD_INTERNAL_AUTHENTICATE, 0, 0,
                   datalen, (const char*)data,  result, resultlen);
   if (sw != SW_SUCCESS)
     {
@@ -579,6 +692,7 @@
   int sw;
   unsigned char *result;
   size_t resultlen, n;
+  int class = is_cryptoflex(slot)? 0xC0: 0x00;
 
   if (!buffer || length < 1)
     return gpg_error (GPG_ERR_INV_VALUE);
@@ -587,7 +701,7 @@
     {
       result = NULL;
       n = length > 254? 254 : length;
-      sw = apdu_send_le (slot, 0x00, CMD_GET_CHALLENGE, 0, 0, -1, NULL,
+      sw = apdu_send_le (slot, class, CMD_GET_CHALLENGE, 0, 0, -1, NULL,
                          n,
                          &result, &resultlen);
       if (sw != SW_SUCCESS)
@@ -621,6 +735,7 @@
   size_t bufferlen;
   int read_all = !nmax;
   size_t n;
+  int class = is_cryptoflex(slot)? 0xC0: 0x00;
 
   if (!result || !resultlen)
     return gpg_error (GPG_ERR_INV_VALUE);
@@ -632,6 +747,15 @@
   if (offset > 32767)
     return gpg_error (GPG_ERR_INV_VALUE);
 
+  /* Cryptoflex 32K cards don't like the attempts to read past the EOF caused
+     by setting N to 254 if the filesize is unknown below.  Substitute the size
+     we learnt from the last file select */
+  if (is_cryptoflex(slot) && !nmax)
+    {
+      nmax = cryptoflex_info[slot].cf32k_efsize;
+      read_all = !nmax;
+    }
+
   do
     {
       buffer = NULL;
@@ -644,13 +768,13 @@
         n = 254;
       else
         n = nmax;
-      sw = apdu_send_le (slot, 0x00, CMD_READ_BINARY,
+      sw = apdu_send_le (slot, class, CMD_READ_BINARY,
                          ((offset>>8) & 0xff), (offset & 0xff) , -1, NULL,
                          n, &buffer, &bufferlen);
       if ( SW_EXACT_LENGTH_P(sw) )
         {
           n = (sw & 0x00ff);
-          sw = apdu_send_le (slot, 0x00, CMD_READ_BINARY,
+          sw = apdu_send_le (slot, class, CMD_READ_BINARY,
                              ((offset>>8) & 0xff), (offset & 0xff) , -1, NULL,
                              n, &buffer, &bufferlen);
         }
@@ -723,6 +847,7 @@
   int sw;
   unsigned char *buffer;
   size_t bufferlen;
+  int class = is_cryptoflex(slot)? 0xC0: 0x00;
 
   if (!result || !resultlen)
     return gpg_error (GPG_ERR_INV_VALUE);
@@ -739,7 +864,7 @@
   bufferlen = 0;
   /* Fixme: Either the ccid driver or the TCOS cards have problems
      with an Le of 0. */
-  sw = apdu_send_le (slot, 0x00, CMD_READ_RECORD,
+  sw = apdu_send_le (slot, class, CMD_READ_RECORD,
                      recno, 
                      short_ef? short_ef : 0x04,
                      -1, NULL,
-- 
        Bob Dunlop



More information about the Gnupg-devel mailing list