* app-openpgp.c (app_local_s): New field PK.
authorWerner Koch <wk@gnupg.org>
Tue, 22 Feb 2005 17:29:07 +0000 (17:29 +0000)
committerWerner Koch <wk@gnupg.org>
Tue, 22 Feb 2005 17:29:07 +0000 (17:29 +0000)
(do_deinit, do_genkey, app_openpgp_storekey): Clear it.
(get_public_key, send_keypair_info): New.
(do_learn_status): Send KEYPAIR info

* app-common.h (app_ctx_t): Add function pointer READKEY.
* app.c (app_readkey): New.
* command.c (cmd_readkey): Use READKEY function if possible.

scd/ChangeLog
scd/app-common.h
scd/app-openpgp.c
scd/app.c
scd/command.c

index aba75ad..054463d 100644 (file)
@@ -1,3 +1,14 @@
+2005-02-22  Werner Koch  <wk@g10code.com>
+
+       * app-openpgp.c (app_local_s): New field PK.
+       (do_deinit, do_genkey, app_openpgp_storekey): Clear it.
+       (get_public_key, send_keypair_info): New.
+       (do_learn_status): Send KEYPAIR info
+
+       * app-common.h (app_ctx_t): Add function pointer READKEY.
+       * app.c (app_readkey): New.
+       * command.c (cmd_readkey): Use READKEY function if possible.
+
 2005-01-26  Werner Koch  <wk@g10code.com>
 
        * ccid-driver.c (parse_ccid_descriptor): Need the CSM workaround
@@ -18,7 +29,7 @@
        side effect of the retrieval of the the C4 DO from the 6E DO the
        cached fingerprint will get updated to the old value and later
        when signing the generated key the checking of the fingerprint
-       fails becuase it won't match the new one.  Thanks to Moritz for
+       fails because it won't match the new one.  Thanks to Moritz for
        analyzing this problem.
        (verify_chv3): Removed the CHV status reread logic because we
        won't cache the C4 DO anymore.
        * scdaemon.c scdaemon.h, command.c: New. Based on the code from
        the gpg-agent.
 
- Copyright 2002 Free Software Foundation, Inc.
+       
+ Copyright 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
 
  This file is free software; as a special exception the author gives
  unlimited permission to copy and/or distribute it, with or without
index 48bd349..ace57d9 100644 (file)
@@ -1,5 +1,5 @@
 /* app-common.h - Common declarations for all card applications
- *     Copyright (C) 2003 Free Software Foundation, Inc.
+ *     Copyright (C) 2003, 2005 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -49,6 +49,8 @@ struct app_ctx_s {
     int (*learn_status) (app_t app, ctrl_t ctrl);
     int (*readcert) (app_t app, const char *certid,
                      unsigned char **cert, size_t *certlen);
+    int (*readkey) (app_t app, const char *certid,
+                    unsigned char **pk, size_t *pklen);
     int (*getattr) (app_t app, ctrl_t ctrl, const char *name);
     int (*setattr) (app_t app, const char *name,
                     int (*pincb)(void*, const char *, char **),
@@ -109,6 +111,8 @@ int app_get_serial_and_stamp (app_t app, char **serial, time_t *stamp);
 int app_write_learn_status (app_t app, ctrl_t ctrl);
 int app_readcert (app_t app, const char *certid,
                   unsigned char **cert, size_t *certlen);
+int app_readkey (app_t app, const char *keyid,
+                 unsigned char **pk, size_t *pklen);
 int app_getattr (app_t app, ctrl_t ctrl, const char *name);
 int app_setattr (app_t app, const char *name,
                  int (*pincb)(void*, const char *, char **),
index fca0a98..8d146ba 100644 (file)
@@ -90,6 +90,7 @@ static struct {
 };
 
 
+/* One cache item for DOs.  */
 struct cache_s {
   struct cache_s *next;
   int tag;
@@ -97,8 +98,20 @@ struct cache_s {
   unsigned char data[1];
 };
 
+
+/* Object with application (i.e. OpenPGP card) specific data.  */
 struct app_local_s {
+  /* A linked list with cached DOs.  */
   struct cache_s *cache;
+  
+  /* Keep track of the public keys.  */
+  struct
+  {
+    int read_done;   /* True if we have at least tried to read them.  */
+    gcry_sexp_t key; /* Might be NULL if key is not available.  */
+  } pk[3];
+
+  /* Keep track of card capabilities.  */
   struct 
   {
     unsigned int get_challenge:1;
@@ -106,6 +119,8 @@ struct app_local_s {
     unsigned int change_force_chv:1;
     unsigned int private_dos:1;
   } extcap;
+
+  /* Flags used to control the application.  */
   struct
   {
     unsigned int no_sync:1;   /* Do not sync CHV1 and CHV2 */
@@ -114,10 +129,16 @@ struct app_local_s {
 };
 
 
+
+/***** Local prototypes  *****/
 static unsigned long convert_sig_counter_value (const unsigned char *value,
                                                 size_t valuelen);
-static unsigned long get_sig_counter (APP app);
+static unsigned long get_sig_counter (app_t app);
 
+
+
+
+\f
 /* Deconstructor. */
 static void
 do_deinit (app_t app)
@@ -125,12 +146,19 @@ do_deinit (app_t app)
   if (app && app->app_local)
     {
       struct cache_s *c, *c2;
+      int i;
 
       for (c = app->app_local->cache; c; c = c2)
         {
           c2 = c->next;
           xfree (c);
         }
+
+      for (i=0; i < DIM (app->app_local->pk); i++)
+        {
+          gcry_sexp_release (app->app_local->pk[i].key);
+          app->app_local->pk[i].read_done = 0;
+        }
       xfree (app->app_local);
       app->app_local = NULL;
     }
@@ -736,6 +764,156 @@ do_getattr (app_t app, ctrl_t ctrl, const char *name)
 }
 
 
+/* Get the public key for KEYNO and store it as an S-expresion with
+   the APP handle.  On error that field gets cleared.  If we already
+   know about the public key we will just return.  Note that this does
+   not mean a key is available; this is soley indicated by the
+   presence of the app->app_local->pk[KEYNO-1].key field.
+
+   Note that GnuPG 1.x does not need this and it would be too time
+   consuming to send it just for the fun of it.  */
+#if GNUPG_MAJOR_VERSION > 1
+static gpg_error_t
+get_public_key (app_t app, int keyno)
+{
+  gpg_error_t err = 0;
+  unsigned char *buffer;
+  const unsigned char *keydata, *m, *e;
+  size_t buflen, keydatalen, mlen, elen;
+  gcry_sexp_t sexp;
+
+  if (keyno < 1 || keyno > 3)
+    return gpg_error (GPG_ERR_INV_ID);
+  keyno--;
+
+  /* Already cached? */
+  if (app->app_local->pk[keyno].read_done)
+    return 0;
+
+  gcry_sexp_release (app->app_local->pk[keyno].key);
+  app->app_local->pk[keyno].key = NULL;
+
+  if (app->card_version > 0x0100)
+    {
+      /* We may simply read the public key out of these cards.  */
+      err = iso7816_read_public_key (app->slot, 
+                                    keyno == 0? "\xB6" :
+                                    keyno == 1? "\xB8" : "\xA4",
+                                    2,  
+                                    &buffer, &buflen);
+      if (err)
+        {
+          log_error (_("reading public key failed: %s\n"), gpg_strerror (err));
+          goto leave;
+        }
+
+      keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen);
+      if (!keydata)
+        {
+          err = gpg_error (GPG_ERR_CARD);
+          log_error (_("response does not contain the public key data\n"));
+          goto leave;
+        }
+      m = find_tlv (keydata, keydatalen, 0x0081, &mlen);
+      if (!m)
+        {
+          err = gpg_error (GPG_ERR_CARD);
+          log_error (_("response does not contain the RSA modulus\n"));
+          goto leave;
+        }
+
+      e = find_tlv (keydata, keydatalen, 0x0082, &elen);
+      if (!e)
+        {
+          err = gpg_error (GPG_ERR_CARD);
+          log_error (_("response does not contain the RSA public exponent\n"));
+          goto leave;
+        }
+
+      err = gcry_sexp_build (&sexp, NULL,
+                             "(public-key (rsa (n %b) (e %b)))",
+                             (int)mlen, m,(int)elen, e);
+
+      if (err)
+        {
+          log_error ("error formatting the key into an S-expression: %s\n",
+                     gpg_strerror (err));
+          goto leave;
+        }
+      app->app_local->pk[keyno].key = sexp;
+
+    }
+  else
+    {
+      /* Due to a design problem in v1.0 cards we can't get the public
+         key out of these cards without doing a verify on CHV3.
+         Clearly that is not an option and thus we try to locate the
+         key using an external helper.  */
+
+      buffer = NULL;
+      /* FIXME */
+
+    }
+
+ leave:
+  /* Set a flag to indicate that we tried to read the key.  */
+  app->app_local->pk[keyno].read_done = 1;
+
+  xfree (buffer);
+  return 0;
+}
+#endif /* GNUPG_MAJOR_VERSION > 1 */
+
+
+
+/* Send the KEYPAIRINFO back. KEYNO needs to be in the range [1,3].
+   This is used by the LEARN command. */
+static gpg_error_t
+send_keypair_info (app_t app, ctrl_t ctrl, int keyno)
+{
+  gpg_error_t err = 0;
+  /* Note that GnuPG 1.x does not need this and it would be too time
+     consuming to send it just for the fun of it. */
+#if GNUPG_MAJOR_VERSION > 1
+  gcry_sexp_t sexp;
+  unsigned char grip[20];
+  char gripstr[41];
+  char idbuf[50];
+  int i;
+
+  err = get_public_key (app, keyno);
+  if (err)
+    goto leave;
+  
+  assert (keyno >= 1 && keyno <= 3);
+  sexp = app->app_local->pk[keyno-1].key;
+  if (!sexp)
+    goto leave; /* No such key.  */
+
+  if (!gcry_pk_get_keygrip (sexp, grip))
+    {
+      err = gpg_error (GPG_ERR_INTERNAL); 
+      goto leave;  
+    }
+  
+  for (i=0; i < 20; i++)
+    sprintf (gripstr+i*2, "%02X", grip[i]);
+
+  sprintf (idbuf, "OPENPGP.%d", keyno);
+  send_status_info (ctrl, "KEYPAIRINFO", 
+                    gripstr, 40, 
+                    idbuf, strlen (idbuf), 
+                    NULL, (size_t)0);
+
+ leave:
+#endif /* GNUPG_MAJOR_VERSION > 1 */
+
+  return err; 
+}
+
+
+/* Handle the LEARN command for OpenPGP.  */
 static int
 do_learn_status (app_t app, ctrl_t ctrl)
 {
@@ -760,11 +938,63 @@ do_learn_status (app_t app, ctrl_t ctrl)
       if (app->did_chv3)
         do_getattr (app, ctrl, "PRIVATE-DO-4");
     }
+  send_keypair_info (app, ctrl, 1);
+  send_keypair_info (app, ctrl, 2);
+  send_keypair_info (app, ctrl, 3);
+  return 0;
+}
+
+
+/* Handle the READKEY command for OpenPGP.  On success a canonical
+   encoded S-expression with the public key will get stored at PK and
+   its length (for assertions) at PKLEN; the caller must release that
+   buffer. On error PK and PKLEN are not changed and an error code is
+   returned.  */
+static int
+do_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen)
+{
+  gpg_error_t err;
+  int keyno;
+  size_t n;
+  unsigned char *buf;
+  gcry_sexp_t sexp;
+
+  if (!strcmp (keyid, "OPENPGP.1"))
+    keyno = 1;
+  else if (!strcmp (keyid, "OPENPGP.2"))
+    keyno = 2;
+  else if (!strcmp (keyid, "OPENPGP.3"))
+    keyno = 3;
+  else
+    return gpg_error (GPG_ERR_INV_ID);
 
+  err = get_public_key (app, keyno);
+  if (err)
+    return err;
+
+  sexp = app->app_local->pk[keyno-1].key;
+  if (!sexp)
+    return gpg_error (GPG_ERR_NO_PUBKEY);
+
+  n = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0);
+  if (!n)
+    return gpg_error (GPG_ERR_BUG);
+  buf = xtrymalloc (n);
+  if (!buf)
+    return gpg_error_from_errno (errno);
+  n = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, buf, n);
+  if (!n)
+    {
+      xfree (buf);
+      return gpg_error (GPG_ERR_BUG);
+    }
+  *pk = buf;
+  *pklen = n;
   return 0;
 }
 
 
+
 /* Verify CHV2 if required.  Depending on the configuration of the
    card CHV1 will also be verified. */
 static int
@@ -1082,6 +1312,11 @@ do_genkey (app_t app, ctrl_t ctrl,  const char *keynostr, unsigned int flags,
      generation.  This _might_ help a card to gather more entropy. */
   flush_cache (app);
 
+  /* Obviously we need to remove the cached public key.  */
+  gcry_sexp_release (app->app_local->pk[keyno].key);
+  app->app_local->pk[keyno].read_done = 0;
+
+  /* Check whether a key already exists.  */
   rc = iso7816_get_data (app->slot, 0x006E, &buffer, &buflen);
   if (rc)
     {
@@ -1109,11 +1344,12 @@ do_genkey (app_t app, ctrl_t ctrl,  const char *keynostr, unsigned int flags,
   else
     log_info (_("generating new key\n"));
 
-
+  
+  /* Prepare for key generation by verifying the ADmin PIN.  */
   rc = verify_chv3 (app, pincb, pincb_arg);
   if (rc)
     goto leave;
-
+   
   xfree (buffer); buffer = NULL;
 
 #if 1
@@ -1682,7 +1918,7 @@ app_select_openpgp (app_t app)
 
       app->fnc.deinit = do_deinit;
       app->fnc.learn_status = do_learn_status;
-      app->fnc.readcert = NULL;
+      app->fnc.readkey = do_readkey;
       app->fnc.getattr = do_getattr;
       app->fnc.setattr = do_setattr;
       app->fnc.genkey = do_genkey;
@@ -1818,6 +2054,9 @@ app_openpgp_storekey (app_t app, int keyno,
 
   flush_cache (app);
 
+  gcry_sexp_release (app->app_local->pk[keyno].key);
+  app->app_local->pk[keyno].read_done = 0;
+
   rc = iso7816_put_data (app->slot,
                          (app->card_version > 0x0007? 0xE0 : 0xE9) + keyno,
                          template, template_len);
index 55fb586..fad4eba 100644 (file)
--- a/scd/app.c
+++ b/scd/app.c
@@ -263,6 +263,32 @@ app_readcert (app_t app, const char *certid,
 }
 
 
+/* Read the key with ID KEYID.  On success a canonical encoded
+   S-expression with the public key will get stored at PK and its
+   length (for assertions) at PKLEN; the caller must release that
+   buffer. On error NULL will be stored at PK and PKLEN and an error
+   code returned.
+
+   This function might not be supported by all applications.  */
+int
+app_readkey (app_t app, const char *keyid, unsigned char **pk, size_t *pklen)
+{
+  if (pk)
+    *pk = NULL;
+  if (pklen)
+    *pklen = 0;
+
+  if (!app || !keyid || !pk || !pklen)
+    return gpg_error (GPG_ERR_INV_VALUE);
+  if (!app->initialized)
+    return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
+  if (!app->fnc.readkey)
+    return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
+
+  return app->fnc.readkey (app, keyid, pk, pklen);
+}
+
+
 /* Perform a GETATTR operation.  */
 int 
 app_getattr (APP app, CTRL ctrl, const char *name)
index b41e7aa..72f48b2 100644 (file)
@@ -1,5 +1,5 @@
 /* command.c - SCdaemon command handler
- *     Copyright (C) 2001, 2002, 2003, 2004 Free Software Foundation, Inc.
+ * Copyright (C) 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
@@ -494,9 +494,9 @@ cmd_readcert (ASSUAN_CONTEXT ctx, char *line)
    Return the public key for the given cert or key ID as an standard
    S-Expression.  */
 static int
-cmd_readkey (ASSUAN_CONTEXT ctx, char *line)
+cmd_readkey (assuan_context_t ctx, char *line)
 {
-  CTRL ctrl = assuan_get_pointer (ctx);
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   int rc;
   unsigned char *cert = NULL;
   size_t ncert, n;
@@ -509,9 +509,31 @@ cmd_readkey (ASSUAN_CONTEXT ctx, char *line)
   line = xstrdup (line); /* Need a copy of the line. */
   if (ctrl->app_ctx)
     {
-      rc = app_readcert (ctrl->app_ctx, line, &cert, &ncert);
-      if (rc)
-        log_error ("app_readcert failed: %s\n", gpg_strerror (rc));
+      unsigned char *pk;
+      size_t pklen;
+
+      /* If the application supports the READKEY function we use that.
+         Otherwise we use the old way by extracting it from the
+         certificate.  */
+      rc = app_readkey (ctrl->app_ctx, line, &pk, &pklen);
+      if (!rc)
+        { /* Yeah, got that key - send it back.  */
+          rc = assuan_send_data (ctx, pk, pklen);
+          xfree (pk);
+          rc = map_assuan_err (rc);
+          xfree (line);
+          line = NULL;
+          goto leave;
+        }
+
+      if (gpg_err_code (rc) != GPG_ERR_UNSUPPORTED_OPERATION)
+        log_error ("app_readkey failed: %s\n", gpg_strerror (rc));
+      else  
+        {
+          rc = app_readcert (ctrl->app_ctx, line, &cert, &ncert);
+          if (rc)
+            log_error ("app_readcert failed: %s\n", gpg_strerror (rc));
+        }
     }
   else
     {