More support for Netkey cards.
[gnupg.git] / scd / app-nks.c
index 9af3918..7e6c7f9 100644 (file)
 
 /* Notes:
 
-  - This is still work in progress.  We are now targeting TCOS 3 cards
-    but try to keep compatibility to TCOS 2.  Both are not fully
-    working as of now.  TCOS 3 PIN management seems to work.  Use GPA
-    from SVN trunk to test it.
+  - We are now targeting TCOS 3 cards and it may happen that there is
+    a regression towards TCOS 2 cards.  Please report.
+
+  - The TKS3 AUT key is not used.  It seems that it is only useful for
+    the internal authentication command and not accessible by other
+    applications.  The key itself is in the encryption class but the
+    corresponding certificate has only the digitalSignature
+    capability.
 
   - If required, we automagically switch between the NKS application
     and the SigG application.  This avoids to use the DINSIG
@@ -63,26 +67,29 @@ static struct
   int fid;       /* File ID. */
   int nks_ver;   /* 0 for NKS version 2, 3 for version 3. */
   int certtype;  /* Type of certificate or 0 if it is not a certificate. */
-  int iskeypair; /* If true has the FID of the correspoding certificate. */
+  int iskeypair; /* If true has the FID of the corresponding certificate. */
   int issignkey; /* True if file is a key usable for signing. */
   int isenckey;  /* True if file is a key usable for decryption. */
+  unsigned char kid;  /* Corresponding key references.  */
 } filelist[] = {
-  { 0, 0x4531, 0, 0,  0xC000, 1, 0 }, /* EF_PK.NKS.SIG */
-  { 1, 0x4531, 3, 0,  0x0000, 1, 1 }, /* EF_PK.CH.SIG  */
-  { 0, 0xC000, 0, 101 },              /* EF_C.NKS.SIG  */
-  { 1, 0xC000, 0, 101 },              /* EF_C.CH.SIG  */
+  { 0, 0x4531, 0, 0,  0xC000, 1, 0, 0x80 }, /* EF_PK.NKS.SIG */
+  { 0, 0xC000, 0, 101 },                    /* EF_C.NKS.SIG  */
   { 0, 0x4331, 0, 100 },
   { 0, 0x4332, 0, 100 },
-  { 0, 0xB000, 0, 110 },              /* EF_PK.RCA.NKS */
-  { 0, 0x45B1, 0, 0,  0xC200, 0, 1 }, /* EF_PK.NKS.ENC */
-  { 0, 0xC200, 0, 101 },              /* EF_C.NKS.ENC  */
+  { 0, 0xB000, 0, 110 },                    /* EF_PK.RCA.NKS */
+  { 0, 0x45B1, 0, 0,  0xC200, 0, 1, 0x81 }, /* EF_PK.NKS.ENC */
+  { 0, 0xC200, 0, 101 },                    /* EF_C.NKS.ENC  */
   { 0, 0x43B1, 0, 100 },
   { 0, 0x43B2, 0, 100 },
-  { 0, 0x4571, 3, 0,  0xc500, 0, 0 }, /* EF_PK.NKS.AUT */
-  { 0, 0xC500, 3, 101 },              /* EF_C.NKS.AUT  */
-  { 0, 0x45B2, 3, 0,  0xC201, 0, 1 }, /* EF_PK.NKS.ENC1024 */
-  { 0, 0xC201, 3, 101 },              /* EF_C.NKS.ENC1024  */
-/*   { 1, 0xB000, 3, ...  */
+/* The authentication key is not used.  */
+/*   { 0, 0x4571, 3, 0,  0xC500, 0, 0, 0x82 }, /\* EF_PK.NKS.AUT *\/ */
+/*   { 0, 0xC500, 3, 101 },                    /\* EF_C.NKS.AUT  *\/ */
+  { 0, 0x45B2, 3, 0,  0xC201, 0, 1, 0x83 }, /* EF_PK.NKS.ENC1024 */
+  { 0, 0xC201, 3, 101 },                    /* EF_C.NKS.ENC1024  */
+  { 1, 0x4531, 3, 0,  0xC000, 1, 1, 0x84 }, /* EF_PK.CH.SIG  */
+  { 1, 0xC000, 0, 101 },                    /* EF_C.CH.SIG  */
+  { 1, 0xC008, 3, 101 },                    /* EF_C.CA.SIG  */
+  { 1, 0xC00E, 3, 111 },                    /* EF_C.RCA.SIG  */
   { 0, 0 }
 };
 
@@ -93,6 +100,11 @@ struct app_local_s {
   int nks_version;  /* NKS version.  */
 
   int sigg_active;  /* True if switched to the SigG application.  */
+  int sigg_msig_checked;/*  True if we checked for a mass signature card.  */
+  int sigg_is_msig; /* True if this is a mass signature card.  */
+
+  int need_app_select; /* Need to re-select the application.  */
+
 };
 
 
@@ -113,6 +125,18 @@ do_deinit (app_t app)
 }
 
 
+static int
+all_zero_p (void *buffer, size_t length)
+{
+  char *p;
+
+  for (p=buffer; length; length--, p++)
+    if (*p)
+      return 0;
+  return 1;
+}
+
+
 /* Read the file with FID, assume it contains a public key and return
    its keygrip in the caller provided 41 byte buffer R_GRIPSTR. */
 static gpg_error_t
@@ -124,7 +148,8 @@ keygripstr_from_pk_file (app_t app, int fid, char *r_gripstr)
   size_t buflen[2];
   gcry_sexp_t sexp;
   int i;
-  
+  int offset[2] = { 0, 0 };
+
   err = iso7816_select_file (app->slot, fid, 0, NULL, NULL);
   if (err)
     return err;
@@ -137,7 +162,7 @@ keygripstr_from_pk_file (app_t app, int fid, char *r_gripstr)
       xfree (buffer[0]);
       return err;
     }
-  
+
   if (app->app_local->nks_version < 3)
     {
       /* Old versions of NKS store the values in a TLV encoded format.
@@ -152,14 +177,55 @@ keygripstr_from_pk_file (app_t app, int fid, char *r_gripstr)
             err = gpg_error (GPG_ERR_TOO_SHORT);
           else if (buffer[i][1] != buflen[i]-2 )
             err = gpg_error (GPG_ERR_INV_OBJ);
+          else
+            offset[i] = 2;
+        }
+    }
+  else
+    {
+      /* Remove leading zeroes to get a correct keygrip.  Take care of
+         negative numbers.  We should also fix it the same way in
+         libgcrypt but we can't yet rely on it yet.  */
+      for (i=0; i < 2; i++)
+        {
+          while (buflen[i]-offset[i] > 1 
+                 && !buffer[i][offset[i]] 
+                 && !(buffer[i][offset[i]+1] & 0x80))
+            offset[i]++;
+        }
+    }
+
+  /* Check whether negative values are not prefixed with a zero and
+     fix that.  */
+  for (i=0; i < 2; i++)
+    {
+      if ((buflen[i]-offset[i]) && (buffer[i][offset[i]] & 0x80))
+        {
+          unsigned char *newbuf;          
+          size_t newlen;
+          
+          newlen = 1 + buflen[i] - offset[i];
+          newbuf = xtrymalloc (newlen);
+          if (!newlen)
+            {
+              xfree (buffer[0]);
+              xfree (buffer[1]);
+              return gpg_error_from_syserror ();
+            }
+          newbuf[0] = 0;
+          memcpy (newbuf+1, buffer[i]+offset[i], buflen[i] - offset[i]);
+          xfree (buffer[i]);
+          buffer[i] = newbuf;
+          buflen[i] = newlen;
+          offset[i] = 0;
         }
     }
 
   if (!err)
     err = gcry_sexp_build (&sexp, NULL,
                            "(public-key (rsa (n %b) (e %b)))",
-                           (int)buflen[0]-2, buffer[0]+2,
-                           (int)buflen[1]-2, buffer[1]+2);
+                           (int)buflen[0]-offset[0], buffer[0]+offset[0],
+                           (int)buflen[1]-offset[1], buffer[1]+offset[1]);
 
   xfree (buffer[0]);
   xfree (buffer[1]);
@@ -205,7 +271,7 @@ get_chv_status (app_t app, int sigg, int pwid)
   command[2] = 0x00;
   command[3] = pwid;
 
-  if (apdu_send_direct (app->slot, command, 4, 0, &result, &resultlen))
+  if (apdu_send_direct (app->slot, 0, command, 4, 0, &result, &resultlen))
     rc = -1; /* Error. */
   else if (resultlen < 2)
     rc = -1; /* Error. */
@@ -261,10 +327,12 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name)
     {
     case 1: /* $AUTHKEYID */
       {
-        /* NetKey 3.0 cards define this key for authentication.
-           FIXME: We don't have the readkey command, so this
-           information is pretty useless.  */
-        char const tmp[] = "NKS-NKS3.4571";
+        /* NetKey 3.0 cards define an authentication key but according
+           to the specs this key is only usable for encryption and not
+           signing.  it might work anyway but it has not yet been
+           tested - fixme.  Thus for now we use the NKS signature key
+           for authentication.  */
+        char const tmp[] = "NKS-NKS3.4531";
         send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
       }
       break;
@@ -539,6 +607,65 @@ do_readcert (app_t app, const char *certid,
 }
 
 
+/* Handle the READKEY command. On success a canonical encoded
+   S-expression with the public key will get stored at PK and its
+   length at PKLEN; the caller must release that buffer.  On error PK
+   and PKLEN are not changed and an error code is returned.  As of now
+   this function is only useful for the internal authentication key.
+   Other keys are automagically retrieved via by means of the
+   certificate parsing code in commands.c:cmd_readkey.  For internal
+   use PK and PKLEN may be NULL to just check for an existing key.  */
+static gpg_error_t
+do_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen)
+{
+  gpg_error_t err;
+  unsigned char *buffer[2];
+  size_t buflen[2];
+  unsigned short path[1] = { 0x4500 };
+
+  /* We use a generic name to retrieve PK.AUT.IFD-SPK.  */
+  if (!strcmp (keyid, "$IFDAUTHKEY") && app->app_local->nks_version >= 3)
+    ;
+  else /* Return the error code expected by cmd_readkey.  */
+    return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION); 
+
+  /* Access the KEYD file which is always in the master directory.  */
+  err = iso7816_select_path (app->slot, path, DIM (path), NULL, NULL);
+  if (err)
+    return err;
+  /* Due to the above select we need to re-select our application.  */
+  app->app_local->need_app_select = 1;
+  /* Get the two records.  */
+  err = iso7816_read_record (app->slot, 5, 1, 0, &buffer[0], &buflen[0]);
+  if (err)
+    return err;
+  if (all_zero_p (buffer[0], buflen[0]))
+    {
+      xfree (buffer[0]);
+      return gpg_error (GPG_ERR_NOT_FOUND);
+    }
+  err = iso7816_read_record (app->slot, 6, 1, 0, &buffer[1], &buflen[1]);
+  if (err)
+    {
+      xfree (buffer[0]);
+      return err;
+    }
+
+  if (pk && pklen)
+    {
+      *pk = make_canon_sexp_from_rsa_pk (buffer[0], buflen[0],
+                                         buffer[1], buflen[1],
+                                         pklen);
+      if (!*pk)
+        err = gpg_error_from_syserror ();
+    }
+
+  xfree (buffer[0]);
+  xfree (buffer[1]);
+  return err;
+}
+
+
 static gpg_error_t
 basic_pin_checks (const char *pinvalue, int minlen, int maxlen)
 {
@@ -622,7 +749,6 @@ verify_pin (app_t app, int pwid, const char *desc,
 }
 
 
-
 /* Create the signature and return the allocated result in OUTDATA.
    If a PIN is required the PINCB will be used to ask for the PIN;
    that callback should return the PIN in an allocated buffer and
@@ -643,13 +769,18 @@ do_sign (app_t app, const char *keyidstr, int hashalgo,
   int rc, i;
   int is_sigg = 0;
   int fid;
-  unsigned char data[35];   /* Must be large enough for a SHA-1 digest
-                               + the largest OID _prefix above. */
+  unsigned char kid;
+  unsigned char data[83];   /* Must be large enough for a SHA-1 digest
+                               + the largest OID prefix. */
+  size_t datalen;
 
   if (!keyidstr || !*keyidstr)
     return gpg_error (GPG_ERR_INV_VALUE);
-  if (indatalen != 20 && indatalen != 16 && indatalen != 35)
-    return gpg_error (GPG_ERR_INV_VALUE);
+  switch (indatalen)
+    {
+    case 16: case 20: case 35: case 47: case 51: case 67: case 83: break;
+    default: return gpg_error (GPG_ERR_INV_VALUE);
+    }
 
   /* Check that the provided ID is valid.  This is not really needed
      but we do it to enforce correct usage by the caller. */
@@ -667,6 +798,12 @@ do_sign (app_t app, const char *keyidstr, int hashalgo,
   if (rc)
     return rc;
 
+  if (is_sigg && app->app_local->sigg_is_msig)
+    {
+      log_info ("mass signature cards are not allowed\n");
+      return gpg_error (GPG_ERR_NOT_SUPPORTED);
+    }
+
   if (!hexdigitp (keyidstr) || !hexdigitp (keyidstr+1)
       || !hexdigitp (keyidstr+2) || !hexdigitp (keyidstr+3) 
       || keyidstr[4])
@@ -679,22 +816,35 @@ do_sign (app_t app, const char *keyidstr, int hashalgo,
     return gpg_error (GPG_ERR_NOT_FOUND);
   if (!filelist[i].issignkey)
     return gpg_error (GPG_ERR_INV_ID);
-
-  /* Prepare the DER object from INDATA. */
-  if (indatalen == 35)
+  kid = filelist[i].kid;
+
+  /* Prepare the DER object from INDATA.  */
+  if (app->app_local->nks_version > 2 && (indatalen == 35
+                                          || indatalen == 47
+                                          || indatalen == 51
+                                          || indatalen == 67 
+                                          || indatalen == 83))
+    {
+      /* The caller send data matching the length of the ASN.1 encoded
+         hash for SHA-{1,224,256,384,512}.  Assume that is okay.  */
+      assert (indatalen <= sizeof data);
+      memcpy (data, indata, indatalen);
+      datalen = indatalen;
+    }
+  else if (indatalen == 35)
     {
       /* Alright, the caller was so kind to send us an already
-         prepared DER object.  Check that it is waht we want and that
-         it matches the hash algorithm. */
+         prepared DER object.  This is for TCOS 2. */
       if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15))
         ;
-      else if (hashalgo == GCRY_MD_RMD160 && !memcmp (indata, rmd160_prefix,15))
+      else if (hashalgo == GCRY_MD_RMD160 && !memcmp (indata,rmd160_prefix,15))
         ;
       else 
         return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
       memcpy (data, indata, indatalen);
+      datalen = 35;
     }
-  else
+  else if (indatalen == 20)
     {
       if (hashalgo == GCRY_MD_SHA1)
         memcpy (data, sha1_prefix, 15);
@@ -703,11 +853,32 @@ do_sign (app_t app, const char *keyidstr, int hashalgo,
       else 
         return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
       memcpy (data+15, indata, indatalen);
+      datalen = 35;
     }
+  else
+    return gpg_error (GPG_ERR_INV_VALUE);
+
 
-  rc = verify_pin (app, 0, NULL, pincb, pincb_arg);
+  /* Send an MSE for PSO:Computer_Signature.  */
+  if (app->app_local->nks_version > 2)
+    {
+      unsigned char mse[6];
+      
+      mse[0] = 0x80; /* Algorithm reference.  */
+      mse[1] = 1;
+      mse[2] = 2;    /* RSA, card does pkcs#1 v1.5 padding, no ASN.1 check.  */
+      mse[3] = 0x84; /* Private key reference.  */
+      mse[4] = 1;
+      mse[5] = kid;
+      rc = iso7816_manage_security_env (app->slot, 0x41, 0xB6,
+                                        mse, sizeof mse);
+    }
+  /* Verify using PW1.CH.  */
+  if (!rc)
+    rc = verify_pin (app, 0, NULL, pincb, pincb_arg);
+  /* Compute the signature.  */
   if (!rc)
-    rc = iso7816_compute_ds (app->slot, data, 35, outdata, outdatalen);
+    rc = iso7816_compute_ds (app->slot, data, datalen, outdata, outdatalen);
   return rc;
 }
 
@@ -723,13 +894,10 @@ do_decipher (app_t app, const char *keyidstr,
              const void *indata, size_t indatalen,
              unsigned char **outdata, size_t *outdatalen )
 {
-  static const unsigned char mse_parm[] = {
-    0x80, 1, 0x10, /* Select algorithm RSA. */
-    0x84, 1, 0x81  /* Select local secret key 1 for decryption. */
-  };
   int rc, i;
   int is_sigg = 0;
   int fid;
+  int kid;
 
   if (!keyidstr || !*keyidstr || !indatalen)
     return gpg_error (GPG_ERR_INV_VALUE);
@@ -762,15 +930,40 @@ do_decipher (app_t app, const char *keyidstr,
     return gpg_error (GPG_ERR_NOT_FOUND);
   if (!filelist[i].isenckey)
     return gpg_error (GPG_ERR_INV_ID);
+  kid = filelist[i].kid;
+
+  if (app->app_local->nks_version > 2)
+    {
+      unsigned char mse[6];
+      mse[0] = 0x80; /* Algorithm reference.  */
+      mse[1] = 1;
+      mse[2] = 0x0a; /* RSA no padding.  (0x1A is pkcs#1.5 padding.)  */
+      mse[3] = 0x84; /* Private key reference.  */
+      mse[4] = 1;
+      mse[5] = kid;
+      rc = iso7816_manage_security_env (app->slot, 0x41, 0xB8,
+                                        mse, sizeof mse);
+    }
+  else
+    {
+      static const unsigned char mse[] = 
+        {
+          0x80, 1, 0x10, /* Select algorithm RSA. */
+          0x84, 1, 0x81  /* Select local secret key 1 for decryption. */
+        };
+      rc = iso7816_manage_security_env (app->slot, 0xC1, 0xB8,
+                                        mse, sizeof mse);
+
+    }
 
-  /* Do the TCOS specific MSE. */
-  rc = iso7816_manage_security_env (app->slot, 
-                                    0xC1, 0xB8,
-                                    mse_parm, sizeof mse_parm);
   if (!rc)
     rc = verify_pin (app, 0, NULL, pincb, pincb_arg);
+
+  /* Note that we need to use extended length APDUs for TCOS 3 cards.
+     Command chaining does not work.  */
   if (!rc)
-    rc = iso7816_decipher (app->slot, indata, indatalen, 0x81,
+    rc = iso7816_decipher (app->slot, app->app_local->nks_version > 2? 1:0,
+                           indata, indatalen, 0x81,
                            outdata, outdatalen);
   return rc;
 }
@@ -1035,8 +1228,9 @@ switch_application (app_t app, int enable_sigg)
 {
   gpg_error_t err;
 
-  if ((app->app_local->sigg_active && enable_sigg)
-      || (!app->app_local->sigg_active && !enable_sigg) )
+  if (((app->app_local->sigg_active && enable_sigg)
+       || (!app->app_local->sigg_active && !enable_sigg))
+      && !app->app_local->need_app_select)
     return 0;  /* Already switched.  */
 
   log_info ("app-nks: switching to %s\n", enable_sigg? "SigG":"NKS");
@@ -1044,9 +1238,40 @@ switch_application (app_t app, int enable_sigg)
     err = iso7816_select_application (app->slot, aid_sigg, sizeof aid_sigg, 0);
   else
     err = iso7816_select_application (app->slot, aid_nks, sizeof aid_nks, 0);
+
+  if (!err && enable_sigg && app->app_local->nks_version >= 3 
+      && !app->app_local->sigg_msig_checked)
+    {
+      /* Check whether this card is a mass signature card.  */
+      unsigned char *buffer;
+      size_t buflen;
+      const unsigned char *tmpl;
+      size_t tmpllen;
+      
+      app->app_local->sigg_msig_checked = 1;
+      app->app_local->sigg_is_msig = 1;
+      err = iso7816_select_file (app->slot, 0x5349, 0, NULL, NULL);
+      if (!err)
+        err = iso7816_read_record (app->slot, 1, 1, 0, &buffer, &buflen);
+      if (!err)
+        {
+          tmpl = find_tlv (buffer, buflen, 0x7a, &tmpllen);
+          if (tmpl && tmpllen == 12 
+              && !memcmp (tmpl,
+                          "\x93\x02\x00\x01\xA4\x06\x83\x01\x81\x83\x01\x83",
+                          12))
+            app->app_local->sigg_is_msig = 0;
+          xfree (buffer);
+        }
+      if (app->app_local->sigg_is_msig)
+        log_info ("This is a mass signature card\n");
+    }
   
   if (!err)
-    app->app_local->sigg_active = enable_sigg;
+    {
+      app->app_local->need_app_select = 0;
+      app->app_local->sigg_active = enable_sigg;
+    }
   else
     log_error ("app-nks: error switching to %s: %s\n",
                enable_sigg? "SigG":"NKS", gpg_strerror (err));
@@ -1081,8 +1306,10 @@ app_select_nks (app_t app)
       app->fnc.deinit = do_deinit;
       app->fnc.learn_status = do_learn_status;
       app->fnc.readcert = do_readcert;
+      app->fnc.readkey = do_readkey;
       app->fnc.getattr = do_getattr;
       app->fnc.setattr = NULL;
+      app->fnc.writekey = NULL;
       app->fnc.genkey = NULL;
       app->fnc.sign = do_sign;
       app->fnc.auth = NULL;