Imported Upstream version 2.3.8
[platform/upstream/gpg2.git] / agent / findkey.c
index e7cd79e..20962bd 100644 (file)
@@ -1,7 +1,7 @@
 /* findkey.c - Locate the secret key
  * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007,
  *               2010, 2011 Free Software Foundation, Inc.
- * Copyright (C) 2014 Werner Koch
+ * Copyright (C) 2014, 2019 Werner Koch
  *
  * This file is part of GnuPG.
  *
@@ -16,7 +16,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, see <http://www.gnu.org/licenses/>.
+ * along with this program; if not, see <https://www.gnu.org/licenses/>.
  */
 
 #include <config.h>
 #include <string.h>
 #include <ctype.h>
 #include <fcntl.h>
-#include <assert.h>
 #include <unistd.h>
 #include <sys/stat.h>
-#include <assert.h>
 #include <npth.h> /* (we use pth_sleep) */
 
 #include "agent.h"
-#include "i18n.h"
+#include "../common/i18n.h"
 #include "../common/ssh-utils.h"
 
 #ifndef O_BINARY
@@ -51,12 +49,180 @@ struct try_unprotect_arg_s
 };
 
 
+/* Replace all linefeeds in STRING by "%0A" and return a new malloced
+ * string.  May return NULL on memory error.  */
+static char *
+linefeed_to_percent0A (const char *string)
+{
+  const char *s;
+  size_t n;
+  char *buf, *p;
+
+  for (n=0, s=string; *s; s++)
+    if (*s == '\n')
+      n += 3;
+    else
+      n++;
+  p = buf = xtrymalloc (n+1);
+  if (!buf)
+    return NULL;
+  for (s=string; *s; s++)
+    if (*s == '\n')
+      {
+        memcpy (p, "%0A", 3);
+        p += 3;
+      }
+    else
+      *p++ = *s;
+  *p = 0;
+  return buf;
+}
+
+
+/* Note: Ownership of FNAME and FP are moved to this function.  */
+static gpg_error_t
+write_extended_private_key (char *fname, estream_t fp, int update, int newkey,
+                            const void *buf, size_t len,
+                            const char *serialno, const char *keyref,
+                            time_t timestamp)
+{
+  gpg_error_t err;
+  nvc_t pk = NULL;
+  gcry_sexp_t key = NULL;
+  int remove = 0;
+  char *token = NULL;
+
+  if (update)
+    {
+      int line;
+
+      err = nvc_parse_private_key (&pk, &line, fp);
+      if (err && gpg_err_code (err) != GPG_ERR_ENOENT)
+        {
+          log_error ("error parsing '%s' line %d: %s\n",
+                     fname, line, gpg_strerror (err));
+          goto leave;
+        }
+    }
+  else
+    {
+      pk = nvc_new_private_key ();
+      if (!pk)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+    }
+  es_clearerr (fp);
+
+  err = gcry_sexp_sscan (&key, NULL, buf, len);
+  if (err)
+    goto leave;
+
+  err = nvc_set_private_key (pk, key);
+  if (err)
+    goto leave;
+
+  /* If requested write a Token line.  */
+  if (serialno && keyref)
+    {
+      nve_t item;
+      const char *s;
+
+      token = strconcat (serialno, " ", keyref, NULL);
+      if (!token)
+        {
+          err = gpg_error_from_syserror ();
+          goto leave;
+        }
+
+      /* fixme: the strcmp should compare only the first two strings.  */
+      for (item = nvc_lookup (pk, "Token:");
+           item;
+           item = nve_next_value (item, "Token:"))
+        if ((s = nve_value (item)) && !strcmp (s, token))
+          break;
+      if (!item)
+        {
+          /* No token or no token with that value exists.  Add a new
+           * one so that keys which have been stored on several cards
+           * are well supported.  */
+          err = nvc_add (pk, "Token:", token);
+          if (err)
+            goto leave;
+        }
+    }
+
+  /* If a timestamp has been supplied and the key is new write a
+   * creation timestamp.  (We douple check that there is no Created
+   * item yet.)*/
+  if (timestamp && newkey && !nvc_lookup (pk, "Created:"))
+    {
+      gnupg_isotime_t timebuf;
+
+      epoch2isotime (timebuf, timestamp);
+      err = nvc_add (pk, "Created:", timebuf);
+      if (err)
+        goto leave;
+    }
+
+
+  err = es_fseek (fp, 0, SEEK_SET);
+  if (err)
+    goto leave;
+
+  err = nvc_write (pk, fp);
+  if (!err)
+    err = es_fflush (fp);
+  if (err)
+    {
+      log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
+      remove = 1;
+      goto leave;
+    }
+
+  if (ftruncate (es_fileno (fp), es_ftello (fp)))
+    {
+      err = gpg_error_from_syserror ();
+      log_error ("error truncating '%s': %s\n", fname, gpg_strerror (err));
+      remove = 1;
+      goto leave;
+    }
+
+  if (es_fclose (fp))
+    {
+      err = gpg_error_from_syserror ();
+      log_error ("error closing '%s': %s\n", fname, gpg_strerror (err));
+      remove = 1;
+      goto leave;
+    }
+  else
+    fp = NULL;
+
+  bump_key_eventcounter ();
+
+ leave:
+  es_fclose (fp);
+  if (remove)
+    gnupg_remove (fname);
+  xfree (fname);
+  gcry_sexp_release (key);
+  nvc_release (pk);
+  xfree (token);
+  return err;
+}
+
 /* Write an S-expression formatted key to our key storage.  With FORCE
-   passed as true an existing key with the given GRIP will get
-   overwritten.  */
+ * passed as true an existing key with the given GRIP will get
+ * overwritten.  If SERIALNO and KEYREF are given a Token line is
+ * added to the key if the extended format is used.  If TIMESTAMP is
+ * not zero and the key doies not yet exists it will be recorded as
+ * creation date.  */
 int
 agent_write_private_key (const unsigned char *grip,
-                         const void *buffer, size_t length, int force)
+                         const void *buffer, size_t length, int force,
+                         const char *serialno, const char *keyref,
+                         time_t timestamp)
 {
   char *fname;
   estream_t fp;
@@ -65,26 +231,79 @@ agent_write_private_key (const unsigned char *grip,
   bin2hex (grip, 20, hexgrip);
   strcpy (hexgrip+40, ".key");
 
-  fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+  fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
+                         hexgrip, NULL);
 
   /* FIXME: Write to a temp file first so that write failures during
      key updates won't lead to a key loss.  */
 
-  if (!force && !access (fname, F_OK))
+  if (!force && !gnupg_access (fname, F_OK))
     {
       log_error ("secret key file '%s' already exists\n", fname);
       xfree (fname);
       return gpg_error (GPG_ERR_EEXIST);
     }
 
-  fp = es_fopen (fname, force? "wb,mode=-rw" : "wbx,mode=-rw");
+  fp = es_fopen (fname, force? "rb+,mode=-rw" : "wbx,mode=-rw");
   if (!fp)
     {
       gpg_error_t tmperr = gpg_error_from_syserror ();
-      log_error ("can't create '%s': %s\n", fname, gpg_strerror (tmperr));
-      xfree (fname);
-      return tmperr;
+
+      if (force && gpg_err_code (tmperr) == GPG_ERR_ENOENT)
+        {
+          fp = es_fopen (fname, "wbx,mode=-rw");
+          if (!fp)
+            tmperr = gpg_error_from_syserror ();
+        }
+      if (!fp)
+        {
+          log_error ("can't create '%s': %s\n", fname, gpg_strerror (tmperr));
+          xfree (fname);
+          return tmperr;
+        }
     }
+  else if (force)
+    {
+      gpg_error_t rc;
+      char first;
+
+      /* See if an existing key is in extended format.  */
+      if (es_fread (&first, 1, 1, fp) != 1)
+        {
+          rc = gpg_error_from_syserror ();
+          log_error ("error reading first byte from '%s': %s\n",
+                     fname, strerror (errno));
+          xfree (fname);
+          es_fclose (fp);
+          return rc;
+        }
+
+      rc = es_fseek (fp, 0, SEEK_SET);
+      if (rc)
+        {
+          log_error ("error seeking in '%s': %s\n", fname, strerror (errno));
+          xfree (fname);
+          es_fclose (fp);
+          return rc;
+        }
+
+      if (first != '(')
+        {
+          /* Key is already in the extended format.  */
+          return write_extended_private_key (fname, fp, 1, 0, buffer, length,
+                                             serialno, keyref, timestamp);
+        }
+      if (first == '(' && opt.enable_extended_key_format)
+        {
+          /* Key is in the old format - but we want the extended format.  */
+          return write_extended_private_key (fname, fp, 0, 0, buffer, length,
+                                             serialno, keyref, timestamp);
+        }
+    }
+
+  if (opt.enable_extended_key_format)
+    return write_extended_private_key (fname, fp, 0, 1, buffer, length,
+                                       serialno, keyref, timestamp);
 
   if (es_fwrite (buffer, length, 1, fp) != 1)
     {
@@ -95,6 +314,18 @@ agent_write_private_key (const unsigned char *grip,
       xfree (fname);
       return tmperr;
     }
+
+  /* When force is given, the file might have to be truncated.  */
+  if (force && ftruncate (es_fileno (fp), es_ftello (fp)))
+    {
+      gpg_error_t tmperr = gpg_error_from_syserror ();
+      log_error ("error truncating '%s': %s\n", fname, gpg_strerror (tmperr));
+      es_fclose (fp);
+      gnupg_remove (fname);
+      xfree (fname);
+      return tmperr;
+    }
+
   if (es_fclose (fp))
     {
       gpg_error_t tmperr = gpg_error_from_syserror ();
@@ -109,9 +340,62 @@ agent_write_private_key (const unsigned char *grip,
 }
 
 
+gpg_error_t
+agent_update_private_key (const unsigned char *grip, nvc_t pk)
+{
+  char *fname, *fname0;
+  estream_t fp;
+  char hexgrip[40+8+1];
+  gpg_error_t err;
+
+  bin2hex (grip, 20, hexgrip);
+  strcpy (hexgrip+40, ".key.tmp");
+
+  fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
+                         hexgrip, NULL);
+  fname0 = xstrdup (fname);
+  if (!fname0)
+    {
+      err = gpg_error_from_syserror ();
+      xfree (fname);
+      return err;
+    }
+  fname0[strlen (fname)-4] = 0;
+
+  fp = es_fopen (fname, "wbx,mode=-rw");
+  if (!fp)
+    {
+      err = gpg_error_from_syserror ();
+
+      log_error ("can't create '%s': %s\n", fname, gpg_strerror (err));
+      xfree (fname);
+      return err;
+    }
+
+  err = nvc_write (pk, fp);
+  if (err)
+    log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
+
+  es_fclose (fp);
+
+#ifdef HAVE_W32_SYSTEM
+  /* No atomic mv on W32 systems.  */
+  gnupg_remove (fname0);
+#endif
+  if (rename (fname, fname0))
+    {
+      err = gpg_error_from_errno (errno);
+      log_error (_("error renaming '%s' to '%s': %s\n"),
+                 fname, fname0, strerror (errno));
+    }
+
+  xfree (fname);
+  return err;
+}
+
 /* Callback function to try the unprotection from the passphrase query
    code. */
-static int
+static gpg_error_t
 try_unprotect_cb (struct pin_entry_info_s *pi)
 {
   struct try_unprotect_arg_s *arg = pi->check_cb_arg;
@@ -121,7 +405,7 @@ try_unprotect_cb (struct pin_entry_info_s *pi)
   gnupg_isotime_t now, protected_at, tmptime;
   char *desc = NULL;
 
-  assert (!arg->unprotected_key);
+  log_assert (!arg->unprotected_key);
 
   arg->change_required = 0;
   err = agent_unprotect (ctrl, arg->protected_key, pi->pin, protected_at,
@@ -182,10 +466,37 @@ try_unprotect_cb (struct pin_entry_info_s *pi)
       xfree (desc);
     }
 
+  return err;
+}
+
+
+/* Return true if the STRING has an %C or %c expando.  */
+static int
+has_comment_expando (const char *string)
+{
+  const char *s;
+  int percent = 0;
+
+  if (!string)
+    return 0;
+
+  for (s = string; *s; s++)
+    {
+      if (percent)
+        {
+          if (*s == 'c' || *s == 'C')
+            return 1;
+          percent = 0;
+        }
+      else if (*s == '%')
+        percent = 1;
+    }
   return 0;
 }
 
 
+
+
 /* Modify a Key description, replacing certain special format
    characters.  List of currently supported replacements:
 
@@ -197,9 +508,9 @@ try_unprotect_cb (struct pin_entry_info_s *pi)
    The functions returns 0 on success or an error code.  On success a
    newly allocated string is stored at the address of RESULT.
  */
-static gpg_error_t
-modify_description (const char *in, const char *comment, const gcry_sexp_t key,
-                    char **result)
+gpg_error_t
+agent_modify_description (const char *in, const char *comment,
+                          const gcry_sexp_t key, char **result)
 {
   size_t comment_length;
   size_t in_len;
@@ -208,12 +519,19 @@ modify_description (const char *in, const char *comment, const gcry_sexp_t key,
   size_t i;
   int special, pass;
   char *ssh_fpr = NULL;
+  char *p;
+
+  *result = NULL;
+
+  if (!comment)
+    comment = "";
 
   comment_length = strlen (comment);
   in_len  = strlen (in);
 
   /* First pass calculates the length, second pass does the actual
      copying.  */
+  /* FIXME: This can be simplified by using es_fopenmem.  */
   out = NULL;
   out_len = 0;
   for (pass=0; pass < 2; pass++)
@@ -259,7 +577,8 @@ modify_description (const char *in, const char *comment, const gcry_sexp_t key,
 
                 case 'F': /* SSH style fingerprint.  */
                   if (!ssh_fpr && key)
-                    ssh_get_fingerprint_string (key, &ssh_fpr);
+                    ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest,
+                                                &ssh_fpr);
                   if (ssh_fpr)
                     {
                       if (out)
@@ -303,8 +622,23 @@ modify_description (const char *in, const char *comment, const gcry_sexp_t key,
     }
 
   *out = 0;
-  assert (*result + out_len == out);
+  log_assert (*result + out_len == out);
   xfree (ssh_fpr);
+
+  /* The ssh prompt may sometimes end in
+   *    "...%0A  ()"
+   * The empty parentheses doesn't look very good.  We use this hack
+   * here to remove them as well as the indentation spaces. */
+  p = *result;
+  i = strlen (p);
+  if (i > 2 && !strcmp (p + i - 2, "()"))
+    {
+      p += i - 2;
+      *p-- = 0;
+      while (p > *result && spacep (p))
+        *p-- = 0;
+    }
+
   return 0;
 }
 
@@ -319,7 +653,7 @@ modify_description (const char *in, const char *comment, const gcry_sexp_t key,
    passphrase (entered or from the cache) is stored there; if not NULL
    will be stored.  The caller needs to free the returned
    passphrase. */
-static int
+static gpg_error_t
 unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
            unsigned char **keybuf, const unsigned char *grip,
            cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
@@ -342,7 +676,7 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
     {
       char *pw;
 
-      pw = agent_get_cache (cache_nonce, CACHE_MODE_NONCE);
+      pw = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE);
       if (pw)
         {
           rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
@@ -367,7 +701,7 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
       char *pw;
 
     retry:
-      pw = agent_get_cache (hexgrip, cache_mode);
+      pw = agent_get_cache (ctrl, hexgrip, cache_mode);
       if (pw)
         {
           rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
@@ -384,7 +718,6 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
               return 0;
             }
           xfree (pw);
-          rc  = 0;
         }
       else if (cache_mode == CACHE_MODE_NORMAL)
         {
@@ -406,7 +739,7 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
              We can often avoid the passphrase entry in the second
              step.  We do this only in normal mode, so not to
              interfere with unrelated cache entries.  */
-          pw = agent_get_cache (NULL, cache_mode);
+          pw = agent_get_cache (ctrl, NULL, cache_mode);
           if (pw)
             {
               rc = agent_unprotect (ctrl, *keybuf, pw, NULL,
@@ -422,7 +755,6 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
                   return 0;
                 }
               xfree (pw);
-              rc  = 0;
             }
         }
 
@@ -443,17 +775,17 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
             {
               /* We need to give the other thread a chance to actually put
                  it into the cache. */
-              npth_sleep (1);
+              gnupg_sleep (1);
               goto retry;
             }
           /* Timeout - better call pinentry now the plain way. */
         }
     }
 
-  pi = gcry_calloc_secure (1, sizeof (*pi) + 100);
+  pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
   if (!pi)
     return gpg_error_from_syserror ();
-  pi->max_length = 100;
+  pi->max_length = MAX_PASSPHRASE_LEN + 1;
   pi->min_digits = 0;  /* we want a real passphrase */
   pi->max_digits = 16;
   pi->max_tries = 3;
@@ -465,9 +797,19 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
   pi->check_cb_arg = &arg;
 
   rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi, hexgrip, cache_mode);
-  if (!rc)
+  if (rc)
     {
-      assert (arg.unprotected_key);
+      if ((pi->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
+        {
+          log_error ("Clearing pinentry cache which caused error %s\n",
+                     gpg_strerror (rc));
+
+          agent_clear_passphrase (ctrl, hexgrip, cache_mode);
+        }
+    }
+  else
+    {
+      log_assert (arg.unprotected_key);
       if (arg.change_required)
         {
           /* The callback told as that the user should change their
@@ -475,7 +817,7 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
           size_t canlen, erroff;
           gcry_sexp_t s_skey;
 
-          assert (arg.unprotected_key);
+          log_assert (arg.unprotected_key);
           canlen = gcry_sexp_canon_len (arg.unprotected_key, 0, NULL, NULL);
           rc = gcry_sexp_sscan (&s_skey, &erroff,
                                 (char*)arg.unprotected_key, canlen);
@@ -503,7 +845,7 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
       else
         {
           /* Passphrase is fine.  */
-          agent_put_cache (hexgrip, cache_mode, pi->pin,
+          agent_put_cache (ctrl, hexgrip, cache_mode, pi->pin,
                            lookup_ttl? lookup_ttl (hexgrip) : 0);
           agent_store_cache_hit (hexgrip);
           if (r_passphrase && *pi->pin)
@@ -518,12 +860,15 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
 
 
 /* Read the key identified by GRIP from the private key directory and
-   return it as an gcrypt S-expression object in RESULT.  On failure
-   returns an error code and stores NULL at RESULT. */
+ * return it as an gcrypt S-expression object in RESULT.  If R_KEYMETA
+ * is not NULl and the extended key format is used, the meta data
+ * items are stored there.  However the "Key:" item is removed from
+ * it.  On failure returns an error code and stores NULL at RESULT and
+ * R_KEYMETA. */
 static gpg_error_t
-read_key_file (const unsigned char *grip, gcry_sexp_t *result)
+read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta)
 {
-  int rc;
+  gpg_error_t err;
   char *fname;
   estream_t fp;
   struct stat st;
@@ -531,67 +876,120 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result)
   size_t buflen, erroff;
   gcry_sexp_t s_skey;
   char hexgrip[40+4+1];
+  char first;
 
   *result = NULL;
+  if (r_keymeta)
+    *r_keymeta = NULL;
 
   bin2hex (grip, 20, hexgrip);
   strcpy (hexgrip+40, ".key");
 
-  fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+  fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
+                         hexgrip, NULL);
   fp = es_fopen (fname, "rb");
   if (!fp)
     {
-      rc = gpg_error_from_syserror ();
-      if (gpg_err_code (rc) != GPG_ERR_ENOENT)
-        log_error ("can't open '%s': %s\n", fname, strerror (errno));
+      err = gpg_error_from_syserror ();
+      if (gpg_err_code (err) != GPG_ERR_ENOENT)
+        log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
       xfree (fname);
-      return rc;
+      return err;
+    }
+
+  if (es_fread (&first, 1, 1, fp) != 1)
+    {
+      err = gpg_error_from_syserror ();
+      log_error ("error reading first byte from '%s': %s\n",
+                 fname, gpg_strerror (err));
+      xfree (fname);
+      es_fclose (fp);
+      return err;
+    }
+
+  if (es_fseek (fp, 0, SEEK_SET))
+    {
+      err = gpg_error_from_syserror ();
+      log_error ("error seeking in '%s': %s\n", fname, gpg_strerror (err));
+      xfree (fname);
+      es_fclose (fp);
+      return err;
+    }
+
+  if (first != '(')
+    {
+      /* Key is in extended format.  */
+      nvc_t pk = NULL;
+      int line;
+
+      err = nvc_parse_private_key (&pk, &line, fp);
+      es_fclose (fp);
+
+      if (err)
+        log_error ("error parsing '%s' line %d: %s\n",
+                   fname, line, gpg_strerror (err));
+      else
+        {
+          err = nvc_get_private_key (pk, result);
+          if (err)
+            log_error ("error getting private key from '%s': %s\n",
+                       fname, gpg_strerror (err));
+          else
+            nvc_delete_named (pk, "Key:");
+        }
+
+      if (!err && r_keymeta)
+        *r_keymeta = pk;
+      else
+        nvc_release (pk);
+      xfree (fname);
+      return err;
     }
 
   if (fstat (es_fileno (fp), &st))
     {
-      rc = gpg_error_from_syserror ();
-      log_error ("can't stat '%s': %s\n", fname, strerror (errno));
+      err = gpg_error_from_syserror ();
+      log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err));
       xfree (fname);
       es_fclose (fp);
-      return rc;
+      return err;
     }
 
   buflen = st.st_size;
   buf = xtrymalloc (buflen+1);
   if (!buf)
     {
-      rc = gpg_error_from_syserror ();
+      err = gpg_error_from_syserror ();
       log_error ("error allocating %zu bytes for '%s': %s\n",
-                 buflen, fname, strerror (errno));
+                 buflen, fname, gpg_strerror (err));
       xfree (fname);
       es_fclose (fp);
       xfree (buf);
-      return rc;
+      return err;
 
     }
 
   if (es_fread (buf, buflen, 1, fp) != 1)
     {
-      rc = gpg_error_from_syserror ();
+      err = gpg_error_from_syserror ();
       log_error ("error reading %zu bytes from '%s': %s\n",
-                 buflen, fname, strerror (errno));
+                 buflen, fname, gpg_strerror (err));
       xfree (fname);
       es_fclose (fp);
       xfree (buf);
-      return rc;
+      return err;
     }
 
   /* Convert the file into a gcrypt S-expression object.  */
-  rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
+  err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
   xfree (fname);
   es_fclose (fp);
   xfree (buf);
-  if (rc)
+  if (err)
     {
       log_error ("failed to build S-Exp (off=%u): %s\n",
-                 (unsigned int)erroff, gpg_strerror (rc));
-      return rc;
+                 (unsigned int)erroff, gpg_strerror (err));
+      return err;
     }
   *result = s_skey;
   return 0;
@@ -608,7 +1006,8 @@ remove_key_file (const unsigned char *grip)
 
   bin2hex (grip, 20, hexgrip);
   strcpy (hexgrip+40, ".key");
-  fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+  fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
+                         hexgrip, NULL);
   if (gnupg_remove (fname))
     err = gpg_error_from_syserror ();
   xfree (fname);
@@ -616,8 +1015,137 @@ remove_key_file (const unsigned char *grip)
 }
 
 
+/*
+ * Prompt a user the card insertion, when it's not available yet.
+ */
+static gpg_error_t
+prompt_for_card (ctrl_t ctrl, const unsigned char *grip,
+                 nvc_t keymeta, const unsigned char *shadow_info)
+{
+  char *serialno;
+  char *desc;
+  char *want_sn = NULL;
+  int len;
+  gpg_error_t err;
+  char hexgrip[41];
+  char *comment_buffer = NULL;
+  const char *comment = NULL;
+  int refuse_prompt = 0;
+
+  bin2hex (grip, 20, hexgrip);
+
+  if (keymeta)
+    {
+      const char *p;
+
+      if ((p = nvc_get_string (keymeta, "Prompt:")) && !strcmp (p, "no"))
+        refuse_prompt = 1;
+
+      if ((p = nvc_get_string (keymeta, "Label:")))
+        {
+          if (strchr (p, '\n')
+              && (comment_buffer = linefeed_to_percent0A (p)))
+            comment = comment_buffer;
+          else
+            comment = p;
+        }
+    }
+
+  err = parse_shadow_info (shadow_info, &want_sn, NULL, NULL);
+  if (err)
+    return err;
+
+  len = want_sn? strlen (want_sn) : 0;
+  if (len == 32 && !strncmp (want_sn, "D27600012401", 12))
+    {
+      /* This is an OpenPGP card - reformat  */
+      if (!strncmp (want_sn+16, "0006", 4))
+        {
+          /* This is a Yubikey.  Print the s/n as it would be printed
+           * on Yubikey 5. Example: D2760001240100000006120808620000
+           *                                        mmmm^^^^^^^^      */
+          unsigned long sn;
+
+          sn  = atoi_4 (want_sn+20) * 10000;
+          sn += atoi_4 (want_sn+24);
+          snprintf (want_sn, 32, "%lu %03lu %03lu",
+                    (sn/1000000ul), (sn/1000ul % 1000ul), (sn % 1000ul));
+        }
+      else  /* Default is the Zeitcontrol card print format.  */
+        {
+          memmove (want_sn, want_sn+16, 4);
+          want_sn[4] = ' ';
+          memmove (want_sn+5, want_sn+20, 8);
+          want_sn[13] = 0;
+        }
+    }
+  else if (len == 20 && want_sn[19] == '0')
+    {
+      /* We assume that a 20 byte serial number is a standard one
+       * which has the property to have a zero in the last nibble (Due
+       * to BCD representation).  We don't display this '0' because it
+       * may confuse the user.  */
+      want_sn[19] = 0;
+    }
+
+  for (;;)
+    {
+      /* Scan device(s), and check if key for GRIP is available.  */
+      err = agent_card_serialno (ctrl, &serialno, NULL);
+      if (!err)
+        {
+          struct card_key_info_s *keyinfo;
+
+          xfree (serialno);
+          err = agent_card_keyinfo (ctrl, hexgrip, 0, &keyinfo);
+          if (!err)
+            {
+              /* Key for GRIP found, use it.  */
+              agent_card_free_keyinfo (keyinfo);
+              break;
+            }
+        }
+
+      /* Card is not available.  Prompt the insertion.  */
+      if (refuse_prompt)
+        {
+          err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+          break;
+        }
+
+      if (asprintf (&desc,
+                    "%s:%%0A%%0A"
+                    "  %s%%0A"
+                    "  %s",
+                    L_("Please insert the card with serial number"),
+                    want_sn ? want_sn : "",
+                    comment? comment:"") < 0)
+        err = out_of_core ();
+      else
+        {
+          err = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
+          if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK &&
+              gpg_err_code (err) == GPG_ERR_NO_PIN_ENTRY)
+            err = gpg_error (GPG_ERR_CARD_NOT_PRESENT);
+
+          xfree (desc);
+        }
+
+      if (err)
+        break;
+    }
+
+  xfree (want_sn);
+  gcry_free (comment_buffer);
+  return err;
+}
+
+
 /* Return the secret key as an S-Exp in RESULT after locating it using
-   the GRIP.  If the operation shall be diverted to a token, an
+   the GRIP.  Caller should set GRIP=NULL, when a key in a file is
+   intended to be used for cryptographic operation.  In this case,
+   CTRL->keygrip is used to locate the file, and it may ask a user for
+   confirmation.  If the operation shall be diverted to a token, an
    allocated S-expression with the shadow_info part from the file is
    stored at SHADOW_INFO; if not NULL will be stored at SHADOW_INFO.
    CACHE_MODE defines now the cache shall be used.  DESC_TEXT may be
@@ -636,33 +1164,86 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
                      const char *desc_text,
                      const unsigned char *grip, unsigned char **shadow_info,
                      cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
-                     gcry_sexp_t *result, char **r_passphrase)
+                     gcry_sexp_t *result, char **r_passphrase,
+                     time_t *r_timestamp)
 {
-  int rc;
+  gpg_error_t err;
   unsigned char *buf;
-  size_t len, buflen, erroff;
+  size_t len, erroff;
   gcry_sexp_t s_skey;
+  nvc_t keymeta = NULL;
+  char *desc_text_buffer = NULL;  /* Used in case we extend DESC_TEXT.  */
 
   *result = NULL;
   if (shadow_info)
     *shadow_info = NULL;
   if (r_passphrase)
     *r_passphrase = NULL;
+  if (r_timestamp)
+    *r_timestamp = (time_t)(-1);
 
-  rc = read_key_file (grip, &s_skey);
-  if (rc)
-    {
-      if (gpg_err_code (rc) == GPG_ERR_ENOENT)
-        rc = gpg_error (GPG_ERR_NO_SECKEY);
-      return rc;
-    }
+  if (!grip && !ctrl->have_keygrip)
+    return gpg_error (GPG_ERR_NO_SECKEY);
+
+  err = read_key_file (grip? grip : ctrl->keygrip, &s_skey, &keymeta);
 
   /* For use with the protection functions we also need the key as an
      canonical encoded S-expression in a buffer.  Create this buffer
      now.  */
-  rc = make_canon_sexp (s_skey, &buf, &len);
-  if (rc)
-    return rc;
+  err = make_canon_sexp (s_skey, &buf, &len);
+  if (err)
+    {
+      nvc_release (keymeta);
+      xfree (desc_text_buffer);
+      return err;
+    }
+
+  if (r_timestamp && keymeta)
+    {
+      const char *created = nvc_get_string (keymeta, "Created:");
+
+      if (created)
+        *r_timestamp = isotime2epoch (created);
+    }
+
+  if (!grip && keymeta)
+    {
+      const char *ask_confirmation = nvc_get_string (keymeta, "Confirm:");
+
+      if (ask_confirmation
+          && ((!strcmp (ask_confirmation, "restricted") && ctrl->restricted)
+              || !strcmp (ask_confirmation, "yes")))
+        {
+          char hexgrip[40+4+1];
+          char *prompt;
+          char *comment_buffer = NULL;
+          const char *comment = NULL;
+
+          bin2hex (ctrl->keygrip, 20, hexgrip);
+
+          if ((comment = nvc_get_string (keymeta, "Label:")))
+            {
+              if (strchr (comment, '\n')
+                  && (comment_buffer = linefeed_to_percent0A (comment)))
+                comment = comment_buffer;
+            }
+
+          prompt = xtryasprintf (L_("Requested the use of key%%0A"
+                                    "  %s%%0A"
+                                    "  %s%%0A"
+                                    "Do you want to allow this?"),
+                                 hexgrip, comment? comment:"");
+
+          gcry_free (comment_buffer);
+
+          err = agent_get_confirmation (ctrl, prompt,
+                                        L_("Allow"), L_("Deny"), 0);
+          xfree (prompt);
+
+          if (err)
+            return err;
+        }
+    }
 
   switch (agent_private_key_type (buf))
     {
@@ -673,10 +1254,10 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
         unsigned char *buf_new;
         size_t buf_newlen;
 
-        rc = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen);
-        if (rc)
+        err = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen);
+        if (err)
           log_error ("failed to convert unprotected openpgp key: %s\n",
-                     gpg_strerror (rc));
+                     gpg_strerror (err));
         else
           {
             xfree (buf);
@@ -687,34 +1268,53 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
     case PRIVATE_KEY_PROTECTED:
       {
        char *desc_text_final;
-       char *comment = NULL;
+       char *comment_buffer = NULL;
+       const char *comment = NULL;
 
         /* Note, that we will take the comment as a C string for
-           display purposes; i.e. all stuff beyond a Nul character is
-           ignored.  */
-        {
-          gcry_sexp_t comment_sexp;
+         * display purposes; i.e. all stuff beyond a Nul character is
+         * ignored.  If a "Label" entry is available in the meta data
+         * this is used instead of the s-expression comment.  */
+        if (keymeta && (comment = nvc_get_string (keymeta, "Label:")))
+          {
+            if (strchr (comment, '\n')
+                && (comment_buffer = linefeed_to_percent0A (comment)))
+              comment = comment_buffer;
+            /* In case DESC_TEXT has no escape pattern for a comment
+             * we append one.  */
+            if (desc_text && !has_comment_expando (desc_text))
+              {
+                desc_text_buffer = strconcat (desc_text, "%0A%C", NULL);
+                if (desc_text_buffer)
+                  desc_text = desc_text_buffer;
+              }
+          }
+        else
+          {
+            gcry_sexp_t comment_sexp;
 
-          comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
-          if (comment_sexp)
-            comment = gcry_sexp_nth_string (comment_sexp, 1);
-          gcry_sexp_release (comment_sexp);
-        }
+            comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
+            if (comment_sexp)
+              comment_buffer = gcry_sexp_nth_string (comment_sexp, 1);
+            gcry_sexp_release (comment_sexp);
+            comment = comment_buffer;
+          }
 
         desc_text_final = NULL;
        if (desc_text)
-          rc = modify_description (desc_text, comment? comment:"", s_skey,
-                                   &desc_text_final);
-        gcry_free (comment);
-
-       if (!rc)
-         {
-           rc = unprotect (ctrl, cache_nonce, desc_text_final, &buf, grip,
-                            cache_mode, lookup_ttl, r_passphrase);
-           if (rc)
-             log_error ("failed to unprotect the secret key: %s\n",
-                        gpg_strerror (rc));
-         }
+          err = agent_modify_description (desc_text, comment, s_skey,
+                                          &desc_text_final);
+        gcry_free (comment_buffer);
+
+        if (!err)
+          {
+            err = unprotect (ctrl, cache_nonce, desc_text_final, &buf,
+                             grip? grip : ctrl->keygrip,
+                             cache_mode, lookup_ttl, r_passphrase);
+            if (err)
+              log_error ("failed to unprotect the secret key: %s\n",
+                         gpg_strerror (err));
+          }
 
        xfree (desc_text_final);
       }
@@ -723,36 +1323,49 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
       if (shadow_info)
         {
           const unsigned char *s;
+          unsigned char *shadow_type;
           size_t n;
 
-          rc = agent_get_shadow_info (buf, &s);
-          if (!rc)
+          err = agent_get_shadow_info_type (buf, &s, &shadow_type);
+          if (!err)
             {
               n = gcry_sexp_canon_len (s, 0, NULL,NULL);
-              assert (n);
+              log_assert (n);
               *shadow_info = xtrymalloc (n);
               if (!*shadow_info)
-                rc = out_of_core ();
+                {
+                  err = out_of_core ();
+                  goto shadow_error;
+                }
               else
                 {
                   memcpy (*shadow_info, s, n);
-                  rc = 0;
+                  /*
+                   * When it's a key on card (not on tpm2), maks sure
+                   * it's available.
+                   */
+                  if (strcmp (shadow_type, "t1-v1") == 0 && !grip)
+                    err = prompt_for_card (ctrl, ctrl->keygrip,
+                                           keymeta, *shadow_info);
                 }
             }
-          if (rc)
-            log_error ("get_shadow_info failed: %s\n", gpg_strerror (rc));
+          else
+          shadow_error:
+            log_error ("get_shadow_info failed: %s\n", gpg_strerror (err));
+
+          xfree (shadow_type);
         }
       else
-        rc = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
+        err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
       break;
     default:
       log_error ("invalid private key format\n");
-      rc = gpg_error (GPG_ERR_BAD_SECKEY);
+      err = gpg_error (GPG_ERR_BAD_SECKEY);
       break;
     }
   gcry_sexp_release (s_skey);
   s_skey = NULL;
-  if (rc)
+  if (err)
     {
       xfree (buf);
       if (r_passphrase)
@@ -760,207 +1373,27 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
           xfree (*r_passphrase);
           *r_passphrase = NULL;
         }
-      return rc;
+      nvc_release (keymeta);
+      xfree (desc_text_buffer);
+      return err;
     }
 
-  buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL);
-  rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
-  wipememory (buf, buflen);
+  err = sexp_sscan_private_key (result, &erroff, buf);
   xfree (buf);
-  if (rc)
+  nvc_release (keymeta);
+  xfree (desc_text_buffer);
+  if (err)
     {
       log_error ("failed to build S-Exp (off=%u): %s\n",
-                 (unsigned int)erroff, gpg_strerror (rc));
+                 (unsigned int)erroff, gpg_strerror (err));
       if (r_passphrase)
         {
           xfree (*r_passphrase);
           *r_passphrase = NULL;
         }
-      return rc;
-    }
-
-  *result = s_skey;
-  return 0;
-}
-
-
-/* Return the string name from the S-expression S_KEY as well as a
-   string describing the names of the parameters.  ALGONAMESIZE and
-   ELEMSSIZE give the allocated size of the provided buffers.  The
-   buffers may be NULL if not required.  If R_LIST is not NULL the top
-   level list will be stored there; the caller needs to release it in
-   this case.  */
-static gpg_error_t
-key_parms_from_sexp (gcry_sexp_t s_key, gcry_sexp_t *r_list,
-                     char *r_algoname, size_t algonamesize,
-                     char *r_elems, size_t elemssize)
-{
-  gcry_sexp_t list, l2;
-  const char *name, *algoname, *elems;
-  size_t n;
-
-  if (r_list)
-    *r_list = NULL;
-
-  list = gcry_sexp_find_token (s_key, "shadowed-private-key", 0 );
-  if (!list)
-    list = gcry_sexp_find_token (s_key, "protected-private-key", 0 );
-  if (!list)
-    list = gcry_sexp_find_token (s_key, "private-key", 0 );
-  if (!list)
-    {
-      log_error ("invalid private key format\n");
-      return gpg_error (GPG_ERR_BAD_SECKEY);
-    }
-
-  l2 = gcry_sexp_cadr (list);
-  gcry_sexp_release (list);
-  list = l2;
-  name = gcry_sexp_nth_data (list, 0, &n);
-  if (n==3 && !memcmp (name, "rsa", 3))
-    {
-      algoname = "rsa";
-      elems = "ne";
-    }
-  else if (n==3 && !memcmp (name, "dsa", 3))
-    {
-      algoname = "dsa";
-      elems = "pqgy";
-    }
-  else if (n==3 && !memcmp (name, "ecc", 3))
-    {
-      algoname = "ecc";
-      elems = "pabgnq";
-    }
-  else if (n==5 && !memcmp (name, "ecdsa", 5))
-    {
-      algoname = "ecdsa";
-      elems = "pabgnq";
-    }
-  else if (n==4 && !memcmp (name, "ecdh", 4))
-    {
-      algoname = "ecdh";
-      elems = "pabgnq";
-    }
-  else if (n==3 && !memcmp (name, "elg", 3))
-    {
-      algoname = "elg";
-      elems = "pgy";
-    }
-  else
-    {
-      log_error ("unknown private key algorithm\n");
-      gcry_sexp_release (list);
-      return gpg_error (GPG_ERR_BAD_SECKEY);
-    }
-
-  if (r_algoname)
-    {
-      if (strlen (algoname) >= algonamesize)
-        return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
-      strcpy (r_algoname, algoname);
-    }
-  if (r_elems)
-    {
-      if (strlen (elems) >= elemssize)
-        return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
-      strcpy (r_elems, elems);
-    }
-
-  if (r_list)
-    *r_list = list;
-  else
-    gcry_sexp_release (list);
-
-  return 0;
-}
-
-
-/* Return true if KEYPARMS holds an EdDSA key.  */
-static int
-is_eddsa (gcry_sexp_t keyparms)
-{
-  int result = 0;
-  gcry_sexp_t list;
-  const char *s;
-  size_t n;
-  int i;
-
-  list = gcry_sexp_find_token (keyparms, "flags", 0);
-  for (i = list ? gcry_sexp_length (list)-1 : 0; i > 0; i--)
-    {
-      s = gcry_sexp_nth_data (list, i, &n);
-      if (!s)
-        continue; /* Not a data element. */
-
-      if (n == 5 && !memcmp (s, "eddsa", 5))
-        {
-          result = 1;
-          break;
-        }
     }
-  gcry_sexp_release (list);
-  return result;
-}
-
-
-/* Return the public key algorithm number if S_KEY is a DSA style key.
-   If it is not a DSA style key, return 0.  */
-int
-agent_is_dsa_key (gcry_sexp_t s_key)
-{
-  int result;
-  gcry_sexp_t list;
-  char algoname[6];
 
-  if (!s_key)
-    return 0;
-
-  if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0))
-    return 0; /* Error - assume it is not an DSA key.  */
-
-  if (!strcmp (algoname, "dsa"))
-    result = GCRY_PK_DSA;
-  else if (!strcmp (algoname, "ecc"))
-    {
-      if (is_eddsa (list))
-        result = 0;
-      else
-        result = GCRY_PK_ECDSA;
-    }
-  else if (!strcmp (algoname, "ecdsa"))
-    result = GCRY_PK_ECDSA;
-  else
-    result = 0;
-
-  gcry_sexp_release (list);
-  return result;
-}
-
-
-/* Return true if S_KEY is an EdDSA key as used with curve Ed25519.  */
-int
-agent_is_eddsa_key (gcry_sexp_t s_key)
-{
-  int result;
-  gcry_sexp_t list;
-  char algoname[6];
-
-  if (!s_key)
-    return 0;
-
-  if (key_parms_from_sexp (s_key, &list, algoname, sizeof algoname, NULL, 0))
-    return 0; /* Error - assume it is not an EdDSA key.  */
-
-  if (!strcmp (algoname, "ecc") && is_eddsa (list))
-    result = 1;
-  else if (!strcmp (algoname, "eddsa")) /* backward compatibility.  */
-    result = 1;
-  else
-    result = 0;
-
-  gcry_sexp_release (list);
-  return result;
+  return err;
 }
 
 
@@ -970,7 +1403,7 @@ agent_is_eddsa_key (gcry_sexp_t s_key)
    failure an error code is returned and NULL stored at RESULT. */
 gpg_error_t
 agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
-                         gcry_sexp_t *result)
+                         gcry_sexp_t *result, nvc_t *r_keymeta)
 {
   gpg_error_t err;
   gcry_sexp_t s_skey;
@@ -979,7 +1412,7 @@ agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
 
   *result = NULL;
 
-  err = read_key_file (grip, &s_skey);
+  err = read_key_file (grip, &s_skey, r_keymeta);
   if (!err)
     *result = s_skey;
   return err;
@@ -990,14 +1423,14 @@ agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
    at RESULT.  This function extracts the public key from the private
    key database.  On failure an error code is returned and NULL stored
    at RESULT. */
-gpg_error_t
-agent_public_key_from_file (ctrl_t ctrl,
-                            const unsigned char *grip,
-                            gcry_sexp_t *result)
+static gpg_error_t
+public_key_from_file (ctrl_t ctrl, const unsigned char *grip,
+                      gcry_sexp_t *result, int for_ssh)
 {
   gpg_error_t err;
   int i, idx;
   gcry_sexp_t s_skey;
+  nvc_t keymeta = NULL;
   const char *algoname, *elems;
   int npkey;
   gcry_mpi_t array[10];
@@ -1006,7 +1439,9 @@ agent_public_key_from_file (ctrl_t ctrl,
   gcry_sexp_t uri_sexp, comment_sexp;
   const char *uri, *comment;
   size_t uri_length, comment_length;
-  char *format, *p;
+  int uri_intlen, comment_intlen;
+  membuf_t format_mb;
+  char *format;
   void *args[2+7+2+2+1]; /* Size is 2 + max. # of elements + 2 for uri + 2
                             for comment + end-of-list.  */
   int argidx;
@@ -1017,10 +1452,26 @@ agent_public_key_from_file (ctrl_t ctrl,
 
   *result = NULL;
 
-  err = read_key_file (grip, &s_skey);
+  err = read_key_file (grip, &s_skey, for_ssh? &keymeta : NULL);
   if (err)
     return err;
 
+  if (for_ssh)
+    {
+      /* Use-for-ssh: yes */
+      int is_ssh = 0;
+
+      if (keymeta == NULL)
+        return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+
+      is_ssh = nvc_get_boolean (keymeta, "Use-for-ssh:");
+      nvc_release (keymeta);
+      keymeta = NULL;
+
+      if (!is_ssh)
+        return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
+    }
+
   for (i=0; i < DIM (array); i++)
     array[i] = NULL;
 
@@ -1048,60 +1499,53 @@ agent_public_key_from_file (ctrl_t ctrl,
   s_skey = NULL;
 
 
-  /* FIXME: The following thing is pretty ugly code; we should
-     investigate how to make it cleaner.  Probably code to handle
-     canonical S-expressions in a memory buffer is better suited for
-     such a task.  After all that is what we do in protect.c.  Neeed
-     to find common patterns and write a straightformward API to use
-     them.  */
-  assert (sizeof (size_t) <= sizeof (void*));
-
-  format = xtrymalloc (15+4+7*npkey+10+15+1+1);
-  if (!format)
-    {
-      err = gpg_error_from_syserror ();
-      for (i=0; array[i]; i++)
-        gcry_mpi_release (array[i]);
-      gcry_sexp_release (curve);
-      gcry_sexp_release (flags);
-      gcry_sexp_release (uri_sexp);
-      gcry_sexp_release (comment_sexp);
-      return err;
-    }
+  log_assert (sizeof (size_t) <= sizeof (void*));
 
+  init_membuf (&format_mb, 256);
   argidx = 0;
-  p = stpcpy (stpcpy (format, "(public-key("), algoname);
-  p = stpcpy (p, "%S%S");       /* curve name and flags.  */
+  put_membuf_printf (&format_mb, "(public-key(%s%%S%%S", algoname);
   args[argidx++] = &curve;
   args[argidx++] = &flags;
   for (idx=0, s=elems; idx < npkey; idx++)
     {
-      *p++ = '(';
-      *p++ = *s++;
-      p = stpcpy (p, " %m)");
-      assert (argidx < DIM (args));
+      put_membuf_printf (&format_mb, "(%c %%m)", *s++);
+      log_assert (argidx < DIM (args));
       args[argidx++] = &array[idx];
     }
-  *p++ = ')';
+  put_membuf_str (&format_mb, ")");
   if (uri)
     {
-      p = stpcpy (p, "(uri %b)");
-      assert (argidx+1 < DIM (args));
-      args[argidx++] = (void *)&uri_length;
+      put_membuf_str (&format_mb, "(uri %b)");
+      log_assert (argidx+1 < DIM (args));
+      uri_intlen = (int)uri_length;
+      args[argidx++] = (void *)&uri_intlen;
       args[argidx++] = (void *)&uri;
     }
   if (comment)
     {
-      p = stpcpy (p, "(comment %b)");
-      assert (argidx+1 < DIM (args));
-      args[argidx++] = (void *)&comment_length;
-      args[argidx++] = (void*)&comment;
+      put_membuf_str (&format_mb, "(comment %b)");
+      log_assert (argidx+1 < DIM (args));
+      comment_intlen = (int)comment_length;
+      args[argidx++] = (void *)&comment_intlen;
+      args[argidx++] = (void *)&comment;
     }
-  *p++ = ')';
-  *p = 0;
-  assert (argidx < DIM (args));
+  put_membuf (&format_mb, ")", 2);
+  log_assert (argidx < DIM (args));
   args[argidx] = NULL;
 
+  format = get_membuf (&format_mb, NULL);
+  if (!format)
+    {
+      err = gpg_error_from_syserror ();
+      for (i=0; array[i]; i++)
+        gcry_mpi_release (array[i]);
+      gcry_sexp_release (curve);
+      gcry_sexp_release (flags);
+      gcry_sexp_release (uri_sexp);
+      gcry_sexp_release (comment_sexp);
+      return err;
+    }
+
   err = gcry_sexp_build_array (&list, NULL, format, args);
   xfree (format);
   for (i=0; array[i]; i++)
@@ -1116,9 +1560,24 @@ agent_public_key_from_file (ctrl_t ctrl,
   return err;
 }
 
+gpg_error_t
+agent_public_key_from_file (ctrl_t ctrl,
+                            const unsigned char *grip,
+                            gcry_sexp_t *result)
+{
+  return public_key_from_file (ctrl, grip, result, 0);
+}
+
+gpg_error_t
+agent_ssh_key_from_file (ctrl_t ctrl,
+                         const unsigned char *grip,
+                         gcry_sexp_t *result)
+{
+  return public_key_from_file (ctrl, grip, result, 1);
+}
 
 
-/* Check whether the the secret key identified by GRIP is available.
+/* Check whether the secret key identified by GRIP is available.
    Returns 0 is the key is available.  */
 int
 agent_key_available (const unsigned char *grip)
@@ -1130,8 +1589,9 @@ agent_key_available (const unsigned char *grip)
   bin2hex (grip, 20, hexgrip);
   strcpy (hexgrip+40, ".key");
 
-  fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
-  result = !access (fname, R_OK)? 0 : -1;
+  fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
+                         hexgrip, NULL);
+  result = !gnupg_access (fname, R_OK)? 0 : -1;
   xfree (fname);
   return result;
 }
@@ -1144,7 +1604,8 @@ agent_key_available (const unsigned char *grip)
    S-expression.  */
 gpg_error_t
 agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
-                          int *r_keytype, unsigned char **r_shadow_info)
+                          int *r_keytype, unsigned char **r_shadow_info,
+                          unsigned char **r_shadow_info_type)
 {
   gpg_error_t err;
   unsigned char *buf;
@@ -1161,7 +1622,7 @@ agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
   {
     gcry_sexp_t sexp;
 
-    err = read_key_file (grip, &sexp);
+    err = read_key_file (grip, &sexp, NULL);
     if (err)
       {
         if (gpg_err_code (err) == GPG_ERR_ENOENT)
@@ -1191,11 +1652,11 @@ agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
           const unsigned char *s;
           size_t n;
 
-          err = agent_get_shadow_info (buf, &s);
+          err = agent_get_shadow_info_type (buf, &s, r_shadow_info_type);
           if (!err)
             {
               n = gcry_sexp_canon_len (s, 0, NULL, NULL);
-              assert (n);
+              log_assert (n);
               *r_shadow_info = xtrymalloc (n);
               if (!*r_shadow_info)
                 err = gpg_error_from_syserror ();
@@ -1219,18 +1680,20 @@ agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
 
 \f
 /* Delete the key with GRIP from the disk after having asked for
-   confirmation using DESC_TEXT.  If FORCE is set the fucntion won't
-   require a confirmation via Pinentry or warns if the key is also
-   used by ssh.
-
-   Common error codes are:
-     GPG_ERR_NO_SECKEY
-     GPG_ERR_KEY_ON_CARD
-     GPG_ERR_NOT_CONFIRMED
-*/
+ * confirmation using DESC_TEXT.  If FORCE is set the function won't
+ * require a confirmation via Pinentry or warns if the key is also
+ * used by ssh.  If ONLY_STUBS is set only stub keys (references to
+ * smartcards) will be affected.
+ *
+ * Common error codes are:
+ *   GPG_ERR_NO_SECKEY
+ *   GPG_ERR_KEY_ON_CARD
+ *   GPG_ERR_NOT_CONFIRMED
+ *   GPG_ERR_FORBIDDEN     - Not a stub key and ONLY_STUBS requested.
+ */
 gpg_error_t
 agent_delete_key (ctrl_t ctrl, const char *desc_text,
-                  const unsigned char *grip, int force)
+                  const unsigned char *grip, int force, int only_stubs)
 {
   gpg_error_t err;
   gcry_sexp_t s_skey = NULL;
@@ -1241,8 +1704,9 @@ agent_delete_key (ctrl_t ctrl, const char *desc_text,
   ssh_control_file_t cf = NULL;
   char hexgrip[40+4+1];
   char *default_desc = NULL;
+  int key_type;
 
-  err = read_key_file (grip, &s_skey);
+  err = read_key_file (grip, &s_skey, NULL);
   if (gpg_err_code (err) == GPG_ERR_ENOENT)
     err = gpg_error (GPG_ERR_NO_SECKEY);
   if (err)
@@ -1252,7 +1716,14 @@ agent_delete_key (ctrl_t ctrl, const char *desc_text,
   if (err)
     goto leave;
 
-  switch (agent_private_key_type (buf))
+  key_type = agent_private_key_type (buf);
+  if (only_stubs && key_type != PRIVATE_KEY_SHADOWED)
+    {
+      err  = gpg_error (GPG_ERR_FORBIDDEN);
+      goto leave;
+    }
+
+  switch (key_type)
     {
     case PRIVATE_KEY_CLEAR:
     case PRIVATE_KEY_OPENPGP_NONE:
@@ -1281,8 +1752,8 @@ agent_delete_key (ctrl_t ctrl, const char *desc_text,
           }
 
           if (desc_text)
-            err = modify_description (desc_text, comment? comment:"", s_skey,
-                                      &desc_text_final);
+            err = agent_modify_description (desc_text, comment, s_skey,
+                                            &desc_text_final);
           if (err)
             goto leave;
 
@@ -1311,7 +1782,7 @@ agent_delete_key (ctrl_t ctrl, const char *desc_text,
       break;
 
     case PRIVATE_KEY_SHADOWED:
-      err = gpg_error (GPG_ERR_KEY_ON_CARD);
+      err = remove_key_file (grip);
       break;
 
     default:
@@ -1329,3 +1800,46 @@ agent_delete_key (ctrl_t ctrl, const char *desc_text,
   gcry_sexp_release (s_skey);
   return err;
 }
+
+
+/* Write an S-expression formatted shadow key to our key storage.
+   Shadow key is created by an S-expression public key in PKBUF and
+   card's SERIALNO and the IDSTRING.  With FORCE passed as true an
+   existing key with the given GRIP will get overwritten.  */
+gpg_error_t
+agent_write_shadow_key (const unsigned char *grip,
+                        const char *serialno, const char *keyid,
+                        const unsigned char *pkbuf, int force)
+{
+  gpg_error_t err;
+  unsigned char *shadow_info;
+  unsigned char *shdkey;
+  size_t len;
+
+  /* Just in case some caller did not parse the stuff correctly, skip
+   * leading spaces.  */
+  while (spacep (serialno))
+    serialno++;
+  while (spacep (keyid))
+    keyid++;
+
+  shadow_info = make_shadow_info (serialno, keyid);
+  if (!shadow_info)
+    return gpg_error_from_syserror ();
+
+  err = agent_shadow_key (pkbuf, shadow_info, &shdkey);
+  xfree (shadow_info);
+  if (err)
+    {
+      log_error ("shadowing the key failed: %s\n", gpg_strerror (err));
+      return err;
+    }
+
+  len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL);
+  err = agent_write_private_key (grip, shdkey, len, force, serialno, keyid, 0);
+  xfree (shdkey);
+  if (err)
+    log_error ("error writing key: %s\n", gpg_strerror (err));
+
+  return err;
+}