Imported Upstream version 2.3.8
[platform/upstream/gpg2.git] / agent / findkey.c
index dadcc3c..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.
  *
 #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 "../common/i18n.h"
 #include "../common/ssh-utils.h"
-#include "../common/name-value.h"
 
 #ifndef O_BINARY
 #define O_BINARY 0
@@ -52,64 +49,67 @@ struct try_unprotect_arg_s
 };
 
 
-/* Return the file name for the 20 byte keygrip GRIP.  Return NULL on
- * error. */
+/* Replace all linefeeds in STRING by "%0A" and return a new malloced
+ * string.  May return NULL on memory error.  */
 static char *
-fname_from_keygrip (const unsigned char *grip)
+linefeed_to_percent0A (const char *string)
 {
-  char hexgrip[40+4+1];
-
-  bin2hex (grip, 20, hexgrip);
-  strcpy (hexgrip+40, ".key");
-
-  return make_filename_try (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
-                            hexgrip, NULL);
+  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.
- * OLD_FORMAT is true if the file exists but is still in the
- * non-extended mode format.  If MAYBE_UPDATE is set the function
- * assumes that the file exists but writes it only if it figures that
- * an update is required.  */
+/* Note: Ownership of FNAME and FP are moved to this function.  */
 static gpg_error_t
-write_extended_private_key (int maybe_update,
-                            char *fname, estream_t fp,
-                            int old_format, int newkey,
-                            const void *buf, size_t len, time_t timestamp,
+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,
-                            const char *dispserialno)
+                            time_t timestamp)
 {
   gpg_error_t err;
   nvc_t pk = NULL;
   gcry_sexp_t key = NULL;
   int remove = 0;
-  char *token0 = NULL;
   char *token = NULL;
-  char *dispserialno_buffer = NULL;
-  char **tokenfields = NULL;
 
-  if (old_format || newkey)
+  if (update)
     {
-      /* We must create a new NVC if the key is still in the old
-       * format and of course if it is a new key.  */
-      pk = nvc_new_private_key ();
-      if (!pk)
+      int line;
+
+      err = nvc_parse_private_key (&pk, &line, fp);
+      if (err && gpg_err_code (err) != GPG_ERR_ENOENT)
         {
-          err = gpg_error_from_syserror ();
+          log_error ("error parsing '%s' line %d: %s\n",
+                     fname, line, gpg_strerror (err));
           goto leave;
         }
-      maybe_update = 0; /* Always write.  */
     }
   else
-    { /* Parse the existing NVC.  */
-      int lineno = 0;
-
-      err = nvc_parse_private_key (&pk, &lineno, fp);
-      if (err)
+    {
+      pk = nvc_new_private_key ();
+      if (!pk)
         {
-          log_error ("error parsing '%s' line %d: %s\n",
-                     fname, lineno, gpg_strerror (err));
+          err = gpg_error_from_syserror ();
           goto leave;
         }
     }
@@ -123,53 +123,24 @@ write_extended_private_key (int maybe_update,
   if (err)
     goto leave;
 
-  /* If a timestamp has been supplied and the key is new write a
-   * creation timestamp.  Note that we can't add this item if we are
-   * still in the old format.  We also add an extra 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;
-    }
-
   /* If requested write a Token line.  */
   if (serialno && keyref)
     {
       nve_t item;
       const char *s;
-      size_t token0len;
-
-      if (dispserialno)
-        {
-          /* Escape the DISPSERIALNO.  */
-          dispserialno_buffer = percent_plus_escape (dispserialno);
-          if (!dispserialno_buffer)
-            {
-              err = gpg_error_from_syserror ();
-              goto leave;
-            }
-          dispserialno = dispserialno_buffer;
-        }
 
-      token0 = strconcat (serialno, " ", keyref, NULL);
-      if (token0)
-        token = strconcat (token0, " - ", dispserialno? dispserialno:"-", NULL);
-      if (!token0 || !token)
+      token = strconcat (serialno, " ", keyref, NULL);
+      if (!token)
         {
           err = gpg_error_from_syserror ();
           goto leave;
         }
 
-      token0len = strlen (token0);
+      /* 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)) && !strncmp (s, token0, token0len))
+        if ((s = nve_value (item)) && !strcmp (s, token))
           break;
       if (!item)
         {
@@ -179,50 +150,43 @@ write_extended_private_key (int maybe_update,
           err = nvc_add (pk, "Token:", token);
           if (err)
             goto leave;
-          maybe_update = 0; /* Force write.  */
-        }
-      else
-        {
-          /* Token exists: Update the display s/n.  It may have
-           * changed due to changes in a newer software version.  */
-          if (maybe_update && s && (tokenfields = strtokenize (s, " \t\n"))
-              && tokenfields[0] && tokenfields[1] && tokenfields[2]
-              && tokenfields[3]
-              && !strcmp (tokenfields[3], dispserialno))
-            ; /* No need to update Token entry.  */
-          else
-            {
-              err = nve_set (item, token);
-              if (err)
-                goto leave;
-              maybe_update = 0; /* Force write.  */
-            }
         }
     }
 
+  /* 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;
 
-  if (!maybe_update)
+  err = nvc_write (pk, fp);
+  if (!err)
+    err = es_fflush (fp);
+  if (err)
     {
-      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;
-        }
+      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 (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))
@@ -235,41 +199,40 @@ write_extended_private_key (int maybe_update,
   else
     fp = NULL;
 
-  if (!maybe_update)
-    bump_key_eventcounter ();
+  bump_key_eventcounter ();
 
  leave:
   es_fclose (fp);
   if (remove)
     gnupg_remove (fname);
   xfree (fname);
-  xfree (token);
-  xfree (token0);
-  xfree (dispserialno_buffer);
-  xfree (tokenfields);
   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.  If TIMESTAMP is not zero and the key does not yet
- * exists it will be recorded as creation date.  If SERIALNO, KEYREF,
- * of DISPSERIALNO are not NULL they will be recorded as well.  */
+ * 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, time_t timestamp,
+                         const void *buffer, size_t length, int force,
                          const char *serialno, const char *keyref,
-                         const char *dispserialno)
+                         time_t timestamp)
 {
   char *fname;
   estream_t fp;
+  char hexgrip[40+4+1];
 
-  fname = fname_from_keygrip (grip);
-  if (!fname)
-    return gpg_error_from_syserror ();
+  bin2hex (grip, 20, hexgrip);
+  strcpy (hexgrip+40, ".key");
+
+  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.  */
@@ -327,26 +290,20 @@ agent_write_private_key (const unsigned char *grip,
       if (first != '(')
         {
           /* Key is already in the extended format.  */
-          return write_extended_private_key (0, fname, fp, 0, 0,
-                                             buffer, length,
-                                             timestamp, serialno, keyref,
-                                             dispserialno);
+          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 (0, fname, fp, 1, 0,
-                                             buffer, length,
-                                             timestamp, serialno, keyref,
-                                             dispserialno);
+          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 (0, fname, fp, 0, 1,
-                                       buffer, length,
-                                       timestamp, serialno, keyref,
-                                       dispserialno);
+    return write_extended_private_key (fname, fp, 0, 1, buffer, length,
+                                       serialno, keyref, timestamp);
 
   if (es_fwrite (buffer, length, 1, fp) != 1)
     {
@@ -383,6 +340,59 @@ 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 gpg_error_t
@@ -395,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,
@@ -460,6 +470,33 @@ try_unprotect_cb (struct pin_entry_info_s *pi)
 }
 
 
+/* 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:
 
@@ -738,7 +775,7 @@ 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. */
@@ -772,7 +809,7 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
     }
   else
     {
-      assert (arg.unprotected_key);
+      log_assert (arg.unprotected_key);
       if (arg.change_required)
         {
           /* The callback told as that the user should change their
@@ -780,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);
@@ -826,7 +863,8 @@ unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
  * 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. */
+ * 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, nvc_t *r_keymeta)
 {
@@ -881,7 +919,7 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result, nvc_t *r_keymeta)
   if (first != '(')
     {
       /* Key is in extended format.  */
-      nvc_t pk;
+      nvc_t pk = NULL;
       int line;
 
       err = nvc_parse_private_key (&pk, &line, fp);
@@ -977,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
@@ -997,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)
 {
   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);
 
-  err = read_key_file (grip, &s_skey, NULL);
-  if (err)
-    {
-      if (gpg_err_code (err) == GPG_ERR_ENOENT)
-        err = gpg_error (GPG_ERR_NO_SECKEY);
-      return err;
-    }
+  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.  */
   err = make_canon_sexp (s_skey, &buf, &len);
   if (err)
-    return 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))
     {
@@ -1048,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)
           err = agent_modify_description (desc_text, comment, s_skey,
                                           &desc_text_final);
-        gcry_free (comment);
+        gcry_free (comment_buffer);
 
-       if (!err)
-         {
-           err = unprotect (ctrl, cache_nonce, desc_text_final, &buf, grip,
-                            cache_mode, lookup_ttl, r_passphrase);
-           if (err)
-             log_error ("failed to unprotect the secret key: %s\n",
-                        gpg_strerror (err));
-         }
+        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);
       }
@@ -1084,24 +1323,37 @@ 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;
 
-          err = agent_get_shadow_info (buf, &s);
+          err = agent_get_shadow_info_type (buf, &s, &shadow_type);
           if (!err)
             {
               n = gcry_sexp_canon_len (s, 0, NULL,NULL);
               log_assert (n);
               *shadow_info = xtrymalloc (n);
               if (!*shadow_info)
-                err = out_of_core ();
+                {
+                  err = out_of_core ();
+                  goto shadow_error;
+                }
               else
                 {
                   memcpy (*shadow_info, s, n);
-                  err = 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 (err)
+          else
+          shadow_error:
             log_error ("get_shadow_info failed: %s\n", gpg_strerror (err));
+
+          xfree (shadow_type);
         }
       else
         err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
@@ -1121,13 +1373,15 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
           xfree (*r_passphrase);
           *r_passphrase = NULL;
         }
+      nvc_release (keymeta);
+      xfree (desc_text_buffer);
       return err;
     }
 
-  buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL);
-  err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
-  wipememory (buf, buflen);
+  err = sexp_sscan_private_key (result, &erroff, buf);
   xfree (buf);
+  nvc_release (keymeta);
+  xfree (desc_text_buffer);
   if (err)
     {
       log_error ("failed to build S-Exp (off=%u): %s\n",
@@ -1137,191 +1391,9 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
           xfree (*r_passphrase);
           *r_passphrase = NULL;
         }
-      return err;
-    }
-
-  *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;
 }
 
 
@@ -1331,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;
@@ -1340,40 +1412,25 @@ agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
 
   *result = NULL;
 
-  err = read_key_file (grip, &s_skey, NULL);
+  err = read_key_file (grip, &s_skey, r_keymeta);
   if (!err)
     *result = s_skey;
   return err;
 }
 
 
-gpg_error_t
-agent_keymeta_from_file (ctrl_t ctrl, const unsigned char *grip,
-                         nvc_t *r_keymeta)
-{
-  gpg_error_t err;
-  gcry_sexp_t s_skey;
-
-  (void)ctrl;
-
-  err = read_key_file (grip, &s_skey, r_keymeta);
-  gcry_sexp_release (s_skey);
-  return err;
-}
-
-
 /* Return the public key for the keygrip GRIP.  The result is stored
    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];
@@ -1383,7 +1440,8 @@ agent_public_key_from_file (ctrl_t ctrl,
   const char *uri, *comment;
   size_t uri_length, comment_length;
   int uri_intlen, comment_intlen;
-  char *format, *p;
+  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;
@@ -1394,10 +1452,26 @@ agent_public_key_from_file (ctrl_t ctrl,
 
   *result = NULL;
 
-  err = read_key_file (grip, &s_skey, NULL);
+  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;
 
@@ -1425,62 +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.  Need
-     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));
+      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));
+      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;
+      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++)
@@ -1495,6 +1560,21 @@ 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 secret key identified by GRIP is available.
@@ -1524,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;
@@ -1571,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 ();
@@ -1724,27 +1805,16 @@ agent_delete_key (ctrl_t ctrl, const char *desc_text,
 /* 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. If
-   DISPSERIALNO is not NULL the human readable s/n will also be
-   recorded in the key file.  If MAYBE_UPDATE is set it is assumed that
-   the shadow key already exists and we test whether we should update
-   it (FORCE is ignored in this case).  */
+   existing key with the given GRIP will get overwritten.  */
 gpg_error_t
-agent_write_shadow_key (int maybe_update, const unsigned char *grip,
+agent_write_shadow_key (const unsigned char *grip,
                         const char *serialno, const char *keyid,
-                        const unsigned char *pkbuf, int force,
-                        const char *dispserialno)
+                        const unsigned char *pkbuf, int force)
 {
   gpg_error_t err;
   unsigned char *shadow_info;
   unsigned char *shdkey;
   size_t len;
-  char *fname = NULL;
-  estream_t fp = NULL;
-  char first;
-
-  if (maybe_update && !opt.enable_extended_key_format)
-    return 0;  /* Silently ignore.  */
 
   /* Just in case some caller did not parse the stuff correctly, skip
    * leading spaces.  */
@@ -1766,62 +1836,10 @@ agent_write_shadow_key (int maybe_update, const unsigned char *grip,
     }
 
   len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL);
-
-  if (maybe_update)  /* Update mode.  */
-    {
-      fname = fname_from_keygrip (grip);
-      if (!fname)
-        {
-          err = gpg_error_from_syserror ();
-          goto leave;
-        }
-
-      fp = es_fopen (fname, "rb+,mode=-rw");
-      if (!fp)
-        {
-          err = gpg_error_from_syserror ();
-          log_error ("shadow key file '%s' disappeared\n", fname);
-          goto leave;
-        }
-
-      /* See if an existing key is in extended format.  */
-      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));
-          goto leave;
-        }
-
-      if (es_fseek (fp, 0, SEEK_SET))
-        {
-          err = gpg_error_from_syserror ();
-          log_error ("error seeking in '%s': %s\n", fname, gpg_strerror (err));
-          goto leave;
-        }
-
-      /* "(first == '(')" indicates that the key is in the old format.  */
-      err = write_extended_private_key (maybe_update,
-                                        fname, fp, (first == '('), 0,
-                                        shdkey, len,
-                                        0, serialno, keyid,
-                                        dispserialno);
-      fname = NULL;  /* Ownership was transferred.  */
-      fp = NULL;     /* Ditto.  */
-    }
-  else  /* Standard mode */
-    {
-      err = agent_write_private_key (grip, shdkey, len, force, 0,
-                                     serialno, keyid, dispserialno);
-    }
-
- leave:
-  xfree (fname);
-  es_fclose (fp);
+  err = agent_write_private_key (grip, shdkey, len, force, serialno, keyid, 0);
   xfree (shdkey);
   if (err)
-    log_error ("error %s key: %s\n", maybe_update? "updating":"writing",
-               gpg_strerror (err));
+    log_error ("error writing key: %s\n", gpg_strerror (err));
 
   return err;
 }