scd: Store a new PIV public key in the certificate DO.
authorWerner Koch <wk@gnupg.org>
Thu, 7 Feb 2019 15:13:21 +0000 (16:13 +0100)
committerWerner Koch <wk@gnupg.org>
Thu, 7 Feb 2019 15:14:09 +0000 (16:14 +0100)
* scd/app-piv.c (struct genkey_result_s): Remove type and all users.
(send_keypair_and_cert_info): Print certinfo only if we got a cert..
(readcert_by_tag): Add arg r_mechanism and implement reading of public
keys.
(get_keygrip_by_tag): Use a public key to compute the keygrip.
(do_readcert): Make sure to only return a certificate.
(do_readkey): Read public key from the DO if a certificate is missing.
(get_key_algorithm_by_dobj): Get the algorithm also from a public key.
(does_key_exist): String changes.
(do_genkey): Remove result caching and store public key in the DO.
--

This removes the result cache and instead stores the public key in the
certificate object.  This allows to properly list public keys at any
time after generating a key and before a new certificate is stored
there.

Signed-off-by: Werner Koch <wk@gnupg.org>
scd/app-piv.c
scd/command.c

index 59f2725..f4eb918 100644 (file)
  *   |---------------------------------------------------------------|
  *   (9B indicates the 24 byte PIV Card Application Administration Key)
  *
+ * - When generating a key we store the created public key in the
+ *   corresponding data object, so that gpg and gpgsm are able to get
+ *   the public key, create a certificate and store that then in that
+ *   data object.  That is not standard compliant but due to the use
+ *   of other tags, it should not harm.  See do_genkey for the actual
+ *   used tag structure.
  */
 
 #include <config.h>
@@ -153,22 +159,11 @@ struct cache_s {
 };
 
 
-/* A cache item used by genkey.  */
-struct genkey_result_s {
-  struct genkey_result_s *next;
-  int keyref;
-  gcry_sexp_t s_pkey;
-};
-
-
 /* Object with application specific data.  */
 struct app_local_s {
   /* A linked list with cached DOs.  */
   struct cache_s *cache;
 
-  /* A list with results from recent genkey operations.  */
-  struct genkey_result_s *genkey_results;
-
   /* Various flags.  */
   struct
   {
@@ -180,7 +175,9 @@ struct app_local_s {
 
 /***** Local prototypes  *****/
 static gpg_error_t get_keygrip_by_tag (app_t app, unsigned int tag,
-                                       char **r_keygripstr);
+                                       char **r_keygripstr, int *got_cert);
+static gpg_error_t genkey_parse_rsa (const unsigned char *data, size_t datalen,
+                                     gcry_sexp_t *r_sexp);
 
 
 
@@ -193,19 +190,12 @@ do_deinit (app_t app)
   if (app && app->app_local)
     {
       struct cache_s *c, *c2;
-      struct genkey_result_s *gr, *gr2;
 
       for (c = app->app_local->cache; c; c = c2)
         {
           c2 = c->next;
           xfree (c);
         }
-      for (gr = app->app_local->genkey_results; gr; gr = gr2)
-        {
-          gr2 = gr->next;
-          gcry_sexp_release (gr->s_pkey);
-          xfree (gr);
-        }
 
       xfree (app->app_local);
       app->app_local = NULL;
@@ -567,7 +557,6 @@ put_data (int slot, unsigned int tag, ...)
       p += argv[i].len;
     }
   log_assert ( data + datalen == p );
-  log_printhex (data, datalen, "Put data");
   err = iso7816_put_data_odd (slot, -1 /* use command chaining */,
                               0x3fff, data, datalen);
 
@@ -978,9 +967,10 @@ send_keypair_and_cert_info (app_t app, ctrl_t ctrl, data_object_t dobj,
 {
   gpg_error_t err = 0;
   char *keygripstr = NULL;
+  int got_cert;
   char idbuf[50];
 
-  err = get_keygrip_by_tag (app, dobj->tag, &keygripstr);
+  err = get_keygrip_by_tag (app, dobj->tag, &keygripstr, &got_cert);
   if (err)
     goto leave;
 
@@ -989,7 +979,7 @@ send_keypair_and_cert_info (app_t app, ctrl_t ctrl, data_object_t dobj,
                     keygripstr, strlen (keygripstr),
                     idbuf, strlen (idbuf),
                     NULL, (size_t)0);
-  if (!only_keypair)
+  if (!only_keypair && got_cert)
     {
       /* All certificates are of type 100 (Regular X.509 Cert).  */
       send_status_info (ctrl, "CERTINFO",
@@ -1026,20 +1016,24 @@ do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
 
 /* Core of do_readcert which fetches the certificate based on the
  * given tag and returns it in a freshly allocated buffer stored at
- * R_CERT and the length of the certificate stored at R_CERTLEN.  */
+ * R_CERT and the length of the certificate stored at R_CERTLEN.  If
+ * on success a non-zero value is stored at R_MECHANISM, the returned
+ * data is not certificate but a public key (in the format used by the
+ * container '7f49'.  */
 static gpg_error_t
 readcert_by_tag (app_t app, unsigned int tag,
-                 unsigned char **r_cert, size_t *r_certlen)
+                 unsigned char **r_cert, size_t *r_certlen, int *r_mechanism)
 {
   gpg_error_t err;
   unsigned char *buffer;
   size_t buflen;
   void *relptr;
-  const unsigned char *s;
-  size_t n;
+  const unsigned char *s, *s2;
+  size_t n, n2;
 
   *r_cert = NULL;
   *r_certlen = 0;
+  *r_mechanism = 0;
 
   relptr = get_one_do (app, tag, &buffer, &buflen, NULL);
   if (!relptr || !buflen)
@@ -1049,36 +1043,61 @@ readcert_by_tag (app_t app, unsigned int tag,
     }
 
   s = find_tlv (buffer, buflen, 0x71, &n);
-  if (!s || n != 1)
-    {
-      log_error ("piv: no or invalid CertInfo in 0x%X\n", tag);
-      err = gpg_error (GPG_ERR_INV_CERT_OBJ);
-      goto leave;
-    }
-  if (*s == 0x01)
+  if (!s)
     {
-      log_error ("piv: gzip compression not yet supported (tag 0x%X)\n", tag);
-      err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
-      goto leave;
+      /* No certificate; check whether a public key has been stored
+       * using our own scheme.  */
+      s = find_tlv (buffer, buflen, 0x7f49, &n);
+      if (!s || !n)
+        {
+          log_error ("piv: No public key in 0x%X\n", tag);
+          err = gpg_error (GPG_ERR_NO_PUBKEY);
+          goto leave;
+        }
+      s2 = find_tlv (buffer, buflen, 0x80, &n2);
+      if (!s2 || n2 != 1 || !*s2)
+        {
+          log_error ("piv: No mechanism for public key in 0x%X\n", tag);
+          err = gpg_error (GPG_ERR_NO_PUBKEY);
+          goto leave;
+        }
+      *r_mechanism = *s2;
     }
-  if (*s)
+  else
     {
-      log_error ("piv: invalid CertInfo 0x%02x in 0x%X\n", *s, tag);
-      err = gpg_error (GPG_ERR_INV_CERT_OBJ);
-      goto leave;
-    }
+      if (n != 1)
+        {
+          log_error ("piv: invalid CertInfo in 0x%X\n", tag);
+          err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+          goto leave;
+        }
+      if (*s == 0x01)
+        {
+          log_error ("piv: gzip compression not yet supported (tag 0x%X)\n",
+                     tag);
+          err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
+          goto leave;
+        }
+      if (*s)
+        {
+          log_error ("piv: invalid CertInfo 0x%02x in 0x%X\n", *s, tag);
+          err = gpg_error (GPG_ERR_INV_CERT_OBJ);
+          goto leave;
+        }
 
-  /* Note: We don't check that the LRC octet has a length of zero as
-   * required by the specs.  */
+      /* Note: We don't check that the LRC octet has a length of zero
+       * as required by the specs.  */
 
-  /* Get the cert from the container.  */
-  s = find_tlv (buffer, buflen, 0x70, &n);
-  if (!s || !n)
-    {
-      err = gpg_error (GPG_ERR_NOT_FOUND);
-      goto leave;
+      /* Get the cert from the container.  */
+      s = find_tlv (buffer, buflen, 0x70, &n);
+      if (!s || !n)
+        {
+          err = gpg_error (GPG_ERR_NOT_FOUND);
+          goto leave;
+        }
     }
 
+  /* The next is common for certificate and public key.  */
   if (!(*r_cert = xtrymalloc (n)))
     {
       err = gpg_error_from_syserror ();
@@ -1095,17 +1114,22 @@ readcert_by_tag (app_t app, unsigned int tag,
 }
 
 
-/* Get the keygrip of a key from the certificate stored at TAG.
- * Caller must free the string at R_KEYGRIPSTR. */
+/* Get the keygrip in hex format of a key from the certificate stored
+ * at TAG.  Caller must free the string at R_KEYGRIPSTR. */
 static gpg_error_t
-get_keygrip_by_tag (app_t app, unsigned int tag, char **r_keygripstr)
+get_keygrip_by_tag (app_t app, unsigned int tag,
+                    char **r_keygripstr, int *r_got_cert)
 {
   gpg_error_t err;
   unsigned char *certbuf = NULL;
   size_t certbuflen;
+  int mechanism;
+  gcry_sexp_t s_pkey = NULL;
   ksba_cert_t cert = NULL;
+  unsigned char grip[KEYGRIP_LEN];
 
-  *r_keygripstr = xtrymalloc (40+1);
+  *r_got_cert = 0;
+  *r_keygripstr = xtrymalloc (2*KEYGRIP_LEN+1);
   if (!r_keygripstr)
     {
       err = gpg_error_from_syserror ();
@@ -1113,21 +1137,41 @@ get_keygrip_by_tag (app_t app, unsigned int tag, char **r_keygripstr)
     }
 
   /* We need to get the public key from the certificate.  */
-  err = readcert_by_tag (app, tag, &certbuf, &certbuflen);
+  err = readcert_by_tag (app, tag, &certbuf, &certbuflen, &mechanism);
   if (err)
     goto leave;
+  if (mechanism) /* Compute keygrip from public key.  */
+    {
+      if (mechanism == PIV_ALGORITHM_RSA)
+        err = genkey_parse_rsa (certbuf, certbuflen, &s_pkey);
+      else
+        err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+      if (err)
+        goto leave;
 
-  /* Compute the keygrip.  */
-  err = ksba_cert_new (&cert);
-  if (err)
-    goto leave;
-  err = ksba_cert_init_from_mem (cert, certbuf, certbuflen);
-  if (err)
-    goto leave;
-  err = app_help_get_keygrip_string (cert, *r_keygripstr);
+      if (!gcry_pk_get_keygrip (s_pkey, grip))
+        {
+          log_error ("piv: error computing keygrip\n");
+          err = gpg_error (GPG_ERR_GENERAL);
+          goto leave;
+        }
 
+      bin2hex (grip, sizeof grip, *r_keygripstr);
+    }
+  else /* Compute keygrip from certificate.  */
+    {
+      *r_got_cert = 0;
+      err = ksba_cert_new (&cert);
+      if (err)
+        goto leave;
+      err = ksba_cert_init_from_mem (cert, certbuf, certbuflen);
+      if (err)
+        goto leave;
+      err = app_help_get_keygrip_string (cert, *r_keygripstr);
+    }
 
  leave:
+  gcry_sexp_release (s_pkey);
   ksba_cert_release (cert);
   xfree (certbuf);
   if (err)
@@ -1193,7 +1237,9 @@ static gpg_error_t
 do_readcert (app_t app, const char *certid,
              unsigned char **r_cert, size_t *r_certlen)
 {
+  gpg_error_t err;
   data_object_t dobj;
+  int mechanism;
 
   *r_cert = NULL;
   *r_certlen = 0;
@@ -1202,7 +1248,16 @@ do_readcert (app_t app, const char *certid,
   if (!dobj)
     return gpg_error (GPG_ERR_INV_ID);
 
-  return readcert_by_tag (app, dobj->tag, r_cert, r_certlen);
+  err = readcert_by_tag (app, dobj->tag, r_cert, r_certlen, &mechanism);
+  if (!err && mechanism)
+    {
+      /* Well, no certificate but a public key - we don't want it.  */
+      xfree (*r_cert);
+      *r_cert = NULL;
+      *r_certlen = 0;
+      err = gpg_error (GPG_ERR_NOT_FOUND);
+    }
+  return err;
 }
 
 
@@ -1212,7 +1267,7 @@ do_readcert (app_t app, const char *certid,
  * result from key generation.  If no cached result is available, the
  * error GPG_ERR_UNSUPPORTED_OPERATION is returned so that the higher
  * layer can then to get the key by reading the matching certificate.
- * On success a canonical encoded S-expression with the public key is
+ * On success a canonical encoded s-expression with the public key is
  * stored at (R_PK,R_PKLEN); the caller must release that buffer.  On
  * error R_PK and R_PKLEN are not changed and an error code is
  * returned.
@@ -1224,7 +1279,10 @@ do_readkey (app_t app, int advanced, const char *keyrefstr,
   gpg_error_t err;
   data_object_t dobj;
   int keyref;
-  struct genkey_result_s *gres;
+  unsigned char *cert = NULL;
+  size_t certlen;
+  int mechanism;
+  gcry_sexp_t s_pkey = NULL;
   unsigned char *pk = NULL;
   size_t pklen;
 
@@ -1234,16 +1292,28 @@ do_readkey (app_t app, int advanced, const char *keyrefstr,
       err = gpg_error (GPG_ERR_INV_ID);
       goto leave;
     }
-  for (gres = app->app_local->genkey_results; gres; gres = gres->next)
-    if (gres->keyref == keyref)
-      break;
-  if (!gres || !gres->s_pkey)
+
+  err = readcert_by_tag (app, dobj->tag, &cert, &certlen, &mechanism);
+  if (err)
+    goto leave;
+  if (!mechanism)
     {
-      err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
+      /* We got a certificate.  Let the upper layer handle the
+       * extraction of the key.  FIXME: It would be better to have a
+       * shared fucntion to dothis here.  */
+      err = gpg_error (GPG_ERR_NOT_FOUND);
       goto leave;
     }
 
-  err = make_canon_sexp (gres->s_pkey, &pk, &pklen);
+  /* Convert the public key into the expected s-expression.  */
+  if (mechanism == PIV_ALGORITHM_RSA)
+    err = genkey_parse_rsa (cert, certlen, &s_pkey);
+  else
+    err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+  if (err)
+    goto leave;
+
+  err = make_canon_sexp (s_pkey, &pk, &pklen);
   if (err)
     goto leave;
   if (advanced)
@@ -1265,7 +1335,9 @@ do_readkey (app_t app, int advanced, const char *keyrefstr,
   *r_pklen = pklen;
 
  leave:
+  gcry_sexp_release (s_pkey);
   xfree (pk);
+  xfree (cert);
   return err;
 }
 
@@ -1279,6 +1351,7 @@ get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo)
   gpg_error_t err;
   unsigned char *certbuf = NULL;
   size_t certbuflen;
+  int mechanism;
   ksba_cert_t cert = NULL;
   ksba_sexp_t k_pkey = NULL;
   gcry_sexp_t s_pkey = NULL;
@@ -1290,9 +1363,28 @@ get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_algo)
 
   *r_algo = 0;
 
-  err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen);
+  err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen, &mechanism);
   if (err)
     goto leave;
+  if (mechanism)
+    {
+      /* A public key was found.  That makes it easy.  */
+      switch (mechanism)
+        {
+        case PIV_ALGORITHM_RSA:
+        case PIV_ALGORITHM_ECC_P256:
+        case PIV_ALGORITHM_ECC_P384:
+          *r_algo = mechanism;
+          break;
+
+        default:
+          err = gpg_error (GPG_ERR_PUBKEY_ALGO);
+          log_error ("piv: unknown mechanism %d in public key at %s\n",
+                     mechanism, dobj->keyref);
+          break;
+        }
+      goto leave;
+    }
 
   err = ksba_cert_new (&cert);
   if (err)
@@ -1971,22 +2063,22 @@ does_key_exist (app_t app, data_object_t dobj, int generating, int force)
 
   if (found && !force)
     {
-      log_error ("piv: %s", _("key already exists\n"));
+      log_error (_("key already exists\n"));
       return gpg_error (GPG_ERR_EEXIST);
     }
 
   if (found)
-    log_info ("piv: %s", _("existing key will be replaced\n"));
+    log_info (_("existing key will be replaced\n"));
   else if (generating)
-    log_info ("piv: %s", _("generating new key\n"));
+    log_info (_("generating new key\n"));
   else
-    log_info ("piv: %s", _("writing new key\n"));
+    log_info (_("writing new key\n"));
   return 0;
 }
 
 
 /* Parse an RSA response object, consisting of the content of tag
- * 0x7f49, into a gcrypt s-expresstion object and store that R_SEXP.
+ * 0x7f49, into a gcrypt s-expression object and store that R_SEXP.
  * On error NULL is stored at R_SEXP. */
 static gpg_error_t
 genkey_parse_rsa (const unsigned char *data, size_t datalen,
@@ -2096,8 +2188,6 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype,
   size_t tmpllen;
   const unsigned char *keydata;
   size_t keydatalen;
-  gcry_sexp_t s_pkey = NULL;
-  struct genkey_result_s *gres;
 
   (void)ctrl;
   (void)createtime;
@@ -2151,7 +2241,7 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype,
   if (err)
     {
       log_error (_("generating key failed\n"));
-      return gpg_error (GPG_ERR_CARD);
+      return err;
     }
 
   {
@@ -2171,36 +2261,20 @@ do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype,
       goto leave;
     }
 
-  if (mechanism == PIV_ALGORITHM_RSA)
-    err = genkey_parse_rsa (keydata, keydatalen, &s_pkey);
-  else
-    err = gpg_error (GPG_ERR_BUG);
+  tmpl[0] = mechanism;
+  flush_cached_data (app, dobj->tag);
+  err = put_data (app->slot, dobj->tag,
+                  (int)0x80,   (size_t)1,          tmpl,
+                  (int)0x7f49, (size_t)keydatalen, keydata,
+                  (int)0,      (size_t)0,          NULL);
   if (err)
-    goto leave;
-
-  for (gres = app->app_local->genkey_results; gres; gres = gres->next)
-    if (gres->keyref == keyref)
-      break;
-  if (!gres)
     {
-      gres = xtrycalloc (1, sizeof *gres);
-      if (!gres)
-        {
-          err = gpg_error_from_syserror ();
-          goto leave;
-        }
-      gres->keyref = keyref;
-      gres->next = app->app_local->genkey_results;
-      app->app_local->genkey_results = gres;
+      log_error ("piv: failed to write key to the cert DO %s: %s\n",
+                 dobj->keyref, gpg_strerror (err));
+      goto leave;
     }
-  else
-    gcry_sexp_release (gres->s_pkey);
-  gres->s_pkey = s_pkey;
-  s_pkey = NULL;
-
 
  leave:
-  gcry_sexp_release (s_pkey);
   xfree (buffer);
   return err;
 }
@@ -2230,7 +2304,6 @@ do_writecert (app_t app, ctrl_t ctrl,
   /* FIXME: Check that the authentication has already been done.  */
 
   flush_cached_data (app, dobj->tag);
-
   err = put_data (app->slot, dobj->tag,
                   (int)0x70, (size_t)certlen, cert,/* Certificate */
                   (int)0x71, (size_t)1,       "",  /* No compress */
index 8fd6ba5..127fb5d 100644 (file)
@@ -544,7 +544,8 @@ cmd_readkey (assuan_context_t ctx, char *line)
       goto leave;
     }
 
-  if (gpg_err_code (rc) != GPG_ERR_UNSUPPORTED_OPERATION)
+  if (gpg_err_code (rc) != GPG_ERR_UNSUPPORTED_OPERATION
+      && gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
     log_error ("app_readkey failed: %s\n", gpg_strerror (rc));
   else
     {