Imported Upstream version 2.1.11
[platform/upstream/gpg2.git] / agent / findkey.c
index 6d85cfd..c5e7ae7 100644 (file)
@@ -1,6 +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
  *
  * This file is part of GnuPG.
  *
@@ -29,7 +30,7 @@
 #include <unistd.h>
 #include <sys/stat.h>
 #include <assert.h>
-#include <pth.h> /* (we use pth_sleep) */
+#include <npth.h> /* (we use pth_sleep) */
 
 #include "agent.h"
 #include "i18n.h"
@@ -46,7 +47,7 @@ struct try_unprotect_arg_s
   const unsigned char *protected_key;
   unsigned char *unprotected_key;
   int change_required; /* Set by the callback to indicate that the
-                          user should chnage the passphrase.  */
+                          user should change the passphrase.  */
 };
 
 
@@ -58,69 +59,47 @@ agent_write_private_key (const unsigned char *grip,
                          const void *buffer, size_t length, int force)
 {
   char *fname;
-  FILE *fp;
+  estream_t fp;
   char hexgrip[40+4+1];
-  int fd;
 
   bin2hex (grip, 20, hexgrip);
   strcpy (hexgrip+40, ".key");
 
   fname = make_filename (opt.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))
     {
-      log_error ("secret key file `%s' already exists\n", fname);
+      log_error ("secret key file '%s' already exists\n", fname);
       xfree (fname);
-      return gpg_error (GPG_ERR_GENERAL);
-    }
-
-  /* In FORCE mode we would like to create FNAME but only if it does
-     not already exist.  We cannot make this guarantee just using
-     POSIX (GNU provides the "x" opentype for fopen, however, this is
-     not portable).  Thus, we use the more flexible open function and
-     then use fdopen to obtain a stream. */
-  fd = open (fname, force? (O_CREAT | O_TRUNC | O_WRONLY | O_BINARY)
-                         : (O_CREAT | O_EXCL | O_WRONLY | O_BINARY),
-             S_IRUSR | S_IWUSR
-#ifndef HAVE_W32_SYSTEM
-                 | S_IRGRP
-#endif
-                 );
-  if (fd < 0)
-    fp = NULL;
-  else
-    {
-      fp = fdopen (fd, "wb");
-      if (!fp)
-        {
-          int save_e = errno;
-          close (fd);
-          errno = save_e;
-        }
+      return gpg_error (GPG_ERR_EEXIST);
     }
 
+  fp = es_fopen (fname, force? "wb,mode=-rw" : "wbx,mode=-rw");
   if (!fp)
     {
-      gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
-      log_error ("can't create `%s': %s\n", fname, strerror (errno));
+      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 (fwrite (buffer, length, 1, fp) != 1)
+  if (es_fwrite (buffer, length, 1, fp) != 1)
     {
-      gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
-      log_error ("error writing `%s': %s\n", fname, strerror (errno));
-      fclose (fp);
-      remove (fname);
+      gpg_error_t tmperr = gpg_error_from_syserror ();
+      log_error ("error writing '%s': %s\n", fname, gpg_strerror (tmperr));
+      es_fclose (fp);
+      gnupg_remove (fname);
       xfree (fname);
       return tmperr;
     }
-  if ( fclose (fp) )
+  if (es_fclose (fp))
     {
-      gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
-      log_error ("error closing `%s': %s\n", fname, strerror (errno));
-      remove (fname);
+      gpg_error_t tmperr = gpg_error_from_syserror ();
+      log_error ("error closing '%s': %s\n", fname, gpg_strerror (tmperr));
+      gnupg_remove (fname);
       xfree (fname);
       return tmperr;
     }
@@ -130,12 +109,13 @@ agent_write_private_key (const unsigned char *grip,
 }
 
 
-/* Callback function to try the unprotection from the passpharse query
+/* 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;
+  ctrl_t ctrl = arg->ctrl;
   size_t dummy;
   gpg_error_t err;
   gnupg_isotime_t now, protected_at, tmptime;
@@ -144,18 +124,18 @@ try_unprotect_cb (struct pin_entry_info_s *pi)
   assert (!arg->unprotected_key);
 
   arg->change_required = 0;
-  err = agent_unprotect (arg->protected_key, pi->pin, protected_at,
+  err = agent_unprotect (ctrl, arg->protected_key, pi->pin, protected_at,
                          &arg->unprotected_key, &dummy);
   if (err)
     return err;
-  if (!opt.max_passphrase_days || arg->ctrl->in_passwd)
+  if (!opt.max_passphrase_days || ctrl->in_passwd)
     return 0;  /* No regular passphrase change required.  */
 
   if (!*protected_at)
     {
       /* No protection date known - must force passphrase change.  */
-      desc = xtrystrdup (_("Note: This passphrase has never been changed.%0A"
-                           "Please change it now."));
+      desc = xtrystrdup (L_("Note: This passphrase has never been changed.%0A"
+                            "Please change it now."));
       if (!desc)
         return gpg_error_from_syserror ();
     }
@@ -170,8 +150,8 @@ try_unprotect_cb (struct pin_entry_info_s *pi)
         {
           /* Passphrase "expired".  */
           desc = xtryasprintf
-            (_("This passphrase has not been changed%%0A"
-               "since %.4s-%.2s-%.2s.  Please change it now."),
+            (L_("This passphrase has not been changed%%0A"
+                "since %.4s-%.2s-%.2s.  Please change it now."),
              protected_at, protected_at+4, protected_at+6);
           if (!desc)
             return gpg_error_from_syserror ();
@@ -183,19 +163,20 @@ try_unprotect_cb (struct pin_entry_info_s *pi)
       /* Change required.  */
       if (opt.enforce_passphrase_constraints)
         {
-          err = agent_get_confirmation (arg->ctrl, desc,
-                                        _("Change passphrase"), NULL, 0);
+          err = agent_get_confirmation (ctrl, desc,
+                                        L_("Change passphrase"), NULL, 0);
           if (!err)
             arg->change_required = 1;
         }
       else
         {
-          err = agent_get_confirmation (arg->ctrl, desc,
-                                        _("Change passphrase"),
-                                        _("I'll change it later"), 0);
+          err = agent_get_confirmation (ctrl, desc,
+                                        L_("Change passphrase"),
+                                        L_("I'll change it later"), 0);
           if (!err)
             arg->change_required = 1;
-          else if (gpg_err_code (err) == GPG_ERR_CANCELED)
+          else if (gpg_err_code (err) == GPG_ERR_CANCELED
+                   || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
             err = 0;
         }
       xfree (desc);
@@ -210,6 +191,7 @@ try_unprotect_cb (struct pin_entry_info_s *pi)
 
    %% - Replaced by a single %
    %c - Replaced by the content of COMMENT.
+   %C - Same as %c but put into parentheses.
    %F - Replaced by an ssh style fingerprint computed from KEY.
 
    The functions returns 0 on success or an error code.  On success a
@@ -261,6 +243,20 @@ modify_description (const char *in, const char *comment, const gcry_sexp_t key,
                     out_len += comment_length;
                   break;
 
+                case 'C': /* Comment.  */
+                  if (!comment_length)
+                    ;
+                  else if (out)
+                    {
+                      *out++ = '(';
+                      memcpy (out, comment, comment_length);
+                      out += comment_length;
+                      *out++ = ')';
+                    }
+                  else
+                    out_len += comment_length + 2;
+                  break;
+
                 case 'F': /* SSH style fingerprint.  */
                   if (!ssh_fpr && key)
                     ssh_get_fingerprint_string (key, &ssh_fpr);
@@ -318,11 +314,16 @@ modify_description (const char *in, const char *comment, const gcry_sexp_t key,
    should be the hex encoded keygrip of that key to be used with the
    caching mechanism. DESC_TEXT may be set to override the default
    description used for the pinentry.  If LOOKUP_TTL is given this
-   function is used to lookup the default ttl. */
+   function is used to lookup the default ttl.  If R_PASSPHRASE is not
+   NULL, the function succeeded and the key was protected the used
+   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
-unprotect (ctrl_t ctrl, const char *desc_text,
+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)
+           cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
+           char **r_passphrase)
 {
   struct pin_entry_info_s *pi;
   struct try_unprotect_arg_s arg;
@@ -331,29 +332,99 @@ unprotect (ctrl_t ctrl, const char *desc_text,
   size_t resultlen;
   char hexgrip[40+1];
 
+  if (r_passphrase)
+    *r_passphrase = NULL;
+
   bin2hex (grip, 20, hexgrip);
 
+  /* Initially try to get it using a cache nonce.  */
+  if (cache_nonce)
+    {
+      char *pw;
+
+      pw = agent_get_cache (cache_nonce, CACHE_MODE_NONCE);
+      if (pw)
+        {
+          rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
+          if (!rc)
+            {
+              if (r_passphrase)
+                *r_passphrase = pw;
+              else
+                xfree (pw);
+              xfree (*keybuf);
+              *keybuf = result;
+              return 0;
+            }
+          xfree (pw);
+        }
+    }
+
   /* First try to get it from the cache - if there is none or we can't
      unprotect it, we fall back to ask the user */
   if (cache_mode != CACHE_MODE_IGNORE)
     {
-      void *cache_marker;
-      const char *pw;
+      char *pw;
 
     retry:
-      pw = agent_get_cache (hexgrip, cache_mode, &cache_marker);
+      pw = agent_get_cache (hexgrip, cache_mode);
       if (pw)
         {
-          rc = agent_unprotect (*keybuf, pw, NULL, &result, &resultlen);
-          agent_unlock_cache_entry (&cache_marker);
+          rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
           if (!rc)
             {
+              if (cache_mode == CACHE_MODE_NORMAL)
+                agent_store_cache_hit (hexgrip);
+              if (r_passphrase)
+                *r_passphrase = pw;
+              else
+                xfree (pw);
               xfree (*keybuf);
               *keybuf = result;
               return 0;
             }
+          xfree (pw);
           rc  = 0;
         }
+      else if (cache_mode == CACHE_MODE_NORMAL)
+        {
+          /* The standard use of GPG keys is to have a signing and an
+             encryption subkey.  Commonly both use the same
+             passphrase.  We try to help the user to enter the
+             passphrase only once by silently trying the last
+             correctly entered passphrase.  Checking one additional
+             passphrase should be acceptable; despite the S2K
+             introduced delays. The assumed workflow is:
+
+               1. Read encrypted message in a MUA and thus enter a
+                  passphrase for the encryption subkey.
+
+               2. Reply to that mail with an encrypted and signed
+                  mail, thus entering the passphrase for the signing
+                  subkey.
+
+             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);
+          if (pw)
+            {
+              rc = agent_unprotect (ctrl, *keybuf, pw, NULL,
+                                    &result, &resultlen);
+              if (!rc)
+                {
+                  if (r_passphrase)
+                    *r_passphrase = pw;
+                  else
+                    xfree (pw);
+                  xfree (*keybuf);
+                  *keybuf = result;
+                  return 0;
+                }
+              xfree (pw);
+              rc  = 0;
+            }
+        }
 
       /* If the pinentry is currently in use, we wait up to 60 seconds
          for it to close and check the cache again.  This solves a common
@@ -372,17 +443,17 @@ unprotect (ctrl_t ctrl, const char *desc_text,
             {
               /* We need to give the other thread a chance to actually put
                  it into the cache. */
-              pth_sleep (1);
+              npth_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;
@@ -399,6 +470,8 @@ unprotect (ctrl_t ctrl, const char *desc_text,
       assert (arg.unprotected_key);
       if (arg.change_required)
         {
+          /* The callback told as that the user should change their
+             passphrase.  Present the dialog to do.  */
           size_t canlen, erroff;
           gcry_sexp_t s_skey;
 
@@ -415,7 +488,7 @@ unprotect (ctrl_t ctrl, const char *desc_text,
               xfree (pi);
               return rc;
             }
-          rc = agent_protect_and_store (ctrl, s_skey);
+          rc = agent_protect_and_store (ctrl, s_skey, NULL);
           gcry_sexp_release (s_skey);
           if (rc)
             {
@@ -428,8 +501,14 @@ unprotect (ctrl_t ctrl, const char *desc_text,
             }
         }
       else
-        agent_put_cache (hexgrip, cache_mode, pi->pin,
-                         lookup_ttl? lookup_ttl (hexgrip) : 0);
+        {
+          /* Passphrase is fine.  */
+          agent_put_cache (hexgrip, cache_mode, pi->pin,
+                           lookup_ttl? lookup_ttl (hexgrip) : 0);
+          agent_store_cache_hit (hexgrip);
+          if (r_passphrase && *pi->pin)
+            *r_passphrase = xtrystrdup (pi->pin);
+        }
       xfree (*keybuf);
       *keybuf = arg.unprotected_key;
     }
@@ -446,7 +525,7 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result)
 {
   int rc;
   char *fname;
-  FILE *fp;
+  estream_t fp;
   struct stat st;
   unsigned char *buf;
   size_t buflen, erroff;
@@ -459,33 +538,46 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result)
   strcpy (hexgrip+40, ".key");
 
   fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
-  fp = fopen (fname, "rb");
+  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));
+        log_error ("can't open '%s': %s\n", fname, strerror (errno));
       xfree (fname);
       return rc;
     }
 
-  if (fstat (fileno(fp), &st))
+  if (fstat (es_fileno (fp), &st))
     {
       rc = gpg_error_from_syserror ();
-      log_error ("can't stat `%s': %s\n", fname, strerror (errno));
+      log_error ("can't stat '%s': %s\n", fname, strerror (errno));
       xfree (fname);
-      fclose (fp);
+      es_fclose (fp);
       return rc;
     }
 
   buflen = st.st_size;
   buf = xtrymalloc (buflen+1);
-  if (!buf || fread (buf, buflen, 1, fp) != 1)
+  if (!buf)
+    {
+      rc = gpg_error_from_syserror ();
+      log_error ("error allocating %zu bytes for '%s': %s\n",
+                 buflen, fname, strerror (errno));
+      xfree (fname);
+      es_fclose (fp);
+      xfree (buf);
+      return rc;
+
+    }
+
+  if (es_fread (buf, buflen, 1, fp) != 1)
     {
       rc = gpg_error_from_syserror ();
-      log_error ("error reading `%s': %s\n", fname, strerror (errno));
+      log_error ("error reading %zu bytes from '%s': %s\n",
+                 buflen, fname, strerror (errno));
       xfree (fname);
-      fclose (fp);
+      es_fclose (fp);
       xfree (buf);
       return rc;
     }
@@ -493,7 +585,7 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result)
   /* Convert the file into a gcrypt S-expression object.  */
   rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
   xfree (fname);
-  fclose (fp);
+  es_fclose (fp);
   xfree (buf);
   if (rc)
     {
@@ -506,35 +598,64 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result)
 }
 
 
+/* Remove the key identified by GRIP from the private key directory.  */
+static gpg_error_t
+remove_key_file (const unsigned char *grip)
+{
+  gpg_error_t err = 0;
+  char *fname;
+  char hexgrip[40+4+1];
+
+  bin2hex (grip, 20, hexgrip);
+  strcpy (hexgrip+40, ".key");
+  fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
+  if (gnupg_remove (fname))
+    err = gpg_error_from_syserror ();
+  xfree (fname);
+  return err;
+}
+
+
 /* Return the secret key as an S-Exp in RESULT after locating it using
-   the GRIP.  Stores NULL at RESULT if the operation shall be diverted
-   to a token; in this case an allocated S-expression with the
-   shadow_info part from the file is stored at SHADOW_INFO.
+   the GRIP.  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
    set to present a custom description for the pinentry.  LOOKUP_TTL
    is an optional function to convey a TTL to the cache manager; we do
-   not simply pass the TTL value because the value is only needed if an
-   unprotect action was needed and looking up the TTL may have some
-   overhead (e.g. scanning the sshcontrol file). */
+   not simply pass the TTL value because the value is only needed if
+   an unprotect action was needed and looking up the TTL may have some
+   overhead (e.g. scanning the sshcontrol file).  If a CACHE_NONCE is
+   given that cache item is first tried to get a passphrase.  If
+   R_PASSPHRASE is not NULL, the function succeeded and the key was
+   protected the used passphrase (entered or from the cache) is stored
+   there; if not NULL will be stored.  The caller needs to free the
+   returned passphrase.   */
 gpg_error_t
-agent_key_from_file (ctrl_t ctrl, const char *desc_text,
+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)
+                     gcry_sexp_t *result, char **r_passphrase)
 {
   int rc;
   unsigned char *buf;
   size_t len, buflen, erroff;
   gcry_sexp_t s_skey;
-  int got_shadow_info = 0;
 
   *result = NULL;
   if (shadow_info)
     *shadow_info = NULL;
+  if (r_passphrase)
+    *r_passphrase = NULL;
 
   rc = read_key_file (grip, &s_skey);
   if (rc)
-    return rc;
+    {
+      if (gpg_err_code (rc) == GPG_ERR_ENOENT)
+        rc = gpg_error (GPG_ERR_NO_SECKEY);
+      return rc;
+    }
 
   /* For use with the protection functions we also need the key as an
      canonical encoded S-expression in a buffer.  Create this buffer
@@ -547,6 +668,22 @@ agent_key_from_file (ctrl_t ctrl, const char *desc_text,
     {
     case PRIVATE_KEY_CLEAR:
       break; /* no unprotection needed */
+    case PRIVATE_KEY_OPENPGP_NONE:
+      {
+        unsigned char *buf_new;
+        size_t buf_newlen;
+
+        rc = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen);
+        if (rc)
+          log_error ("failed to convert unprotected openpgp key: %s\n",
+                     gpg_strerror (rc));
+        else
+          {
+            xfree (buf);
+            buf = buf_new;
+          }
+      }
+      break;
     case PRIVATE_KEY_PROTECTED:
       {
        char *desc_text_final;
@@ -572,8 +709,8 @@ agent_key_from_file (ctrl_t ctrl, const char *desc_text,
 
        if (!rc)
          {
-           rc = unprotect (ctrl, desc_text_final, &buf, grip,
-                            cache_mode, lookup_ttl);
+           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));
@@ -600,7 +737,6 @@ agent_key_from_file (ctrl_t ctrl, const char *desc_text,
                 {
                   memcpy (*shadow_info, s, n);
                   rc = 0;
-                  got_shadow_info = 1;
                 }
             }
           if (rc)
@@ -616,9 +752,14 @@ agent_key_from_file (ctrl_t ctrl, const char *desc_text,
     }
   gcry_sexp_release (s_skey);
   s_skey = NULL;
-  if (rc || got_shadow_info)
+  if (rc)
     {
       xfree (buf);
+      if (r_passphrase)
+        {
+          xfree (*r_passphrase);
+          *r_passphrase = NULL;
+        }
       return rc;
     }
 
@@ -630,6 +771,11 @@ agent_key_from_file (ctrl_t ctrl, const char *desc_text,
     {
       log_error ("failed to build S-Exp (off=%u): %s\n",
                  (unsigned int)erroff, gpg_strerror (rc));
+      if (r_passphrase)
+        {
+          xfree (*r_passphrase);
+          *r_passphrase = NULL;
+        }
       return rc;
     }
 
@@ -638,6 +784,186 @@ agent_key_from_file (ctrl_t ctrl, const char *desc_text,
 }
 
 
+/* 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 the key for the keygrip GRIP.  The result is stored at
    RESULT.  This function extracts the key from the private key
    database and returns it as an S-expression object as it is.  On
@@ -669,109 +995,42 @@ agent_public_key_from_file (ctrl_t ctrl,
                             const unsigned char *grip,
                             gcry_sexp_t *result)
 {
-  int i, idx, rc;
+  gpg_error_t err;
+  int i, idx;
   gcry_sexp_t s_skey;
-  const char *algoname;
+  const char *algoname, *elems;
+  int npkey;
+  gcry_mpi_t array[10];
+  gcry_sexp_t curve = NULL;
+  gcry_sexp_t flags = NULL;
   gcry_sexp_t uri_sexp, comment_sexp;
   const char *uri, *comment;
   size_t uri_length, comment_length;
   char *format, *p;
-  void *args[4+2+2+1]; /* Size is max. # of elements + 2 for uri + 2
-                           for comment + end-of-list.  */
+  void *args[2+7+2+2+1]; /* Size is 2 + max. # of elements + 2 for uri + 2
+                            for comment + end-of-list.  */
   int argidx;
-  gcry_sexp_t list, l2;
-  const char *name;
+  gcry_sexp_t list = NULL;
   const char *s;
-  size_t n;
-  const char *elems;
-  gcry_mpi_t *array;
 
   (void)ctrl;
 
   *result = NULL;
 
-  rc = read_key_file (grip, &s_skey);
-  if (rc)
-    return rc;
+  err = read_key_file (grip, &s_skey);
+  if (err)
+    return err;
 
-  list = gcry_sexp_find_token (s_skey, "shadowed-private-key", 0 );
-  if (!list)
-    list = gcry_sexp_find_token (s_skey, "protected-private-key", 0 );
-  if (!list)
-    list = gcry_sexp_find_token (s_skey, "private-key", 0 );
-  if (!list)
-    {
-      log_error ("invalid private key format\n");
-      gcry_sexp_release (s_skey);
-      return gpg_error (GPG_ERR_BAD_SECKEY);
-    }
+  for (i=0; i < DIM (array); i++)
+    array[i] = NULL;
 
-  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, "elg", 3))
-    {
-      algoname = "elg";
-      elems = "pgy";
-    }
-  else
-    {
-      log_error ("unknown private key algorithm\n");
-      gcry_sexp_release (list);
-      gcry_sexp_release (s_skey);
-      return gpg_error (GPG_ERR_BAD_SECKEY);
-    }
-
-  /* Allocate an array for the parameters and copy them out of the
-     secret key.   FIXME: We should have a generic copy function. */
-  array = xtrycalloc (strlen(elems) + 1, sizeof *array);
-  if (!array)
+  err = extract_private_key (s_skey, 0, &algoname, &npkey, NULL, &elems,
+                             array, DIM (array), &curve, &flags);
+  if (err)
     {
-      rc = gpg_error_from_syserror ();
-      gcry_sexp_release (list);
       gcry_sexp_release (s_skey);
-      return rc;
-    }
-
-  for (idx=0, s=elems; *s; s++, idx++ )
-    {
-      l2 = gcry_sexp_find_token (list, s, 1);
-      if (!l2)
-        {
-          /* Required parameter not found.  */
-          for (i=0; i<idx; i++)
-            gcry_mpi_release (array[i]);
-          xfree (array);
-          gcry_sexp_release (list);
-          gcry_sexp_release (s_skey);
-          return gpg_error (GPG_ERR_BAD_SECKEY);
-       }
-      array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
-      gcry_sexp_release (l2);
-      if (!array[idx])
-        {
-          /* Required parameter is invalid. */
-          for (i=0; i<idx; i++)
-            gcry_mpi_release (array[i]);
-          xfree (array);
-          gcry_sexp_release (list);
-          gcry_sexp_release (s_skey);
-          return gpg_error (GPG_ERR_BAD_SECKEY);
-       }
+      return err;
     }
-  gcry_sexp_release (list);
-  list = NULL;
 
   uri = NULL;
   uri_length = 0;
@@ -790,31 +1049,35 @@ agent_public_key_from_file (ctrl_t ctrl,
 
 
   /* 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 suioted for
+     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+7*strlen (elems)+10+15+1+1);
+  format = xtrymalloc (15+4+7*npkey+10+15+1+1);
   if (!format)
     {
-      rc = gpg_error_from_syserror ();
+      err = gpg_error_from_syserror ();
       for (i=0; array[i]; i++)
         gcry_mpi_release (array[i]);
-      xfree (array);
+      gcry_sexp_release (curve);
+      gcry_sexp_release (flags);
       gcry_sexp_release (uri_sexp);
       gcry_sexp_release (comment_sexp);
-      return rc;
+      return err;
     }
 
   argidx = 0;
   p = stpcpy (stpcpy (format, "(public-key("), algoname);
-  for (idx=0, s=elems; *s; s++, idx++ )
+  p = stpcpy (p, "%S%S");       /* curve name and flags.  */
+  args[argidx++] = &curve;
+  args[argidx++] = &flags;
+  for (idx=0, s=elems; idx < npkey; idx++)
     {
       *p++ = '(';
-      *p++ = *s;
+      *p++ = *s++;
       p = stpcpy (p, " %m)");
       assert (argidx < DIM (args));
       args[argidx++] = &array[idx];
@@ -839,23 +1102,24 @@ agent_public_key_from_file (ctrl_t ctrl,
   assert (argidx < DIM (args));
   args[argidx] = NULL;
 
-  rc = gcry_sexp_build_array (&list, NULL, format, args);
+  err = gcry_sexp_build_array (&list, NULL, format, args);
   xfree (format);
   for (i=0; array[i]; i++)
     gcry_mpi_release (array[i]);
-  xfree (array);
+  gcry_sexp_release (curve);
+  gcry_sexp_release (flags);
   gcry_sexp_release (uri_sexp);
   gcry_sexp_release (comment_sexp);
 
-  if (!rc)
+  if (!err)
     *result = list;
-  return rc;
+  return err;
 }
 
 
 
-/* Return the secret key as an S-Exp after locating it using the grip.
-   Returns NULL if key is not available. 0 = key is available */
+/* Check whether the the secret key identified by GRIP is available.
+   Returns 0 is the key is available.  */
 int
 agent_key_available (const unsigned char *grip)
 {
@@ -915,6 +1179,7 @@ agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
   switch (keytype)
     {
     case PRIVATE_KEY_CLEAR:
+    case PRIVATE_KEY_OPENPGP_NONE:
       break;
     case PRIVATE_KEY_PROTECTED:
       /* If we ever require it we could retrieve the comment fields
@@ -950,3 +1215,117 @@ agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
   xfree (buf);
   return err;
 }
+
+
+\f
+/* Delete the key with GRIP from the disk after having asked for
+   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.
+
+   Common error codes are:
+     GPG_ERR_NO_SECKEY
+     GPG_ERR_KEY_ON_CARD
+     GPG_ERR_NOT_CONFIRMED
+*/
+gpg_error_t
+agent_delete_key (ctrl_t ctrl, const char *desc_text,
+                  const unsigned char *grip, int force)
+{
+  gpg_error_t err;
+  gcry_sexp_t s_skey = NULL;
+  unsigned char *buf = NULL;
+  size_t len;
+  char *desc_text_final = NULL;
+  char *comment = NULL;
+  ssh_control_file_t cf = NULL;
+  char hexgrip[40+4+1];
+  char *default_desc = NULL;
+
+  err = read_key_file (grip, &s_skey);
+  if (gpg_err_code (err) == GPG_ERR_ENOENT)
+    err = gpg_error (GPG_ERR_NO_SECKEY);
+  if (err)
+    goto leave;
+
+  err = make_canon_sexp (s_skey, &buf, &len);
+  if (err)
+    goto leave;
+
+  switch (agent_private_key_type (buf))
+    {
+    case PRIVATE_KEY_CLEAR:
+    case PRIVATE_KEY_OPENPGP_NONE:
+    case PRIVATE_KEY_PROTECTED:
+      bin2hex (grip, 20, hexgrip);
+      if (!force)
+        {
+          if (!desc_text)
+            {
+              default_desc = xtryasprintf
+          (L_("Do you really want to delete the key identified by keygrip%%0A"
+              "  %s%%0A  %%C%%0A?"), hexgrip);
+              desc_text = default_desc;
+            }
+
+          /* 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;
+
+            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);
+          }
+
+          if (desc_text)
+            err = modify_description (desc_text, comment? comment:"", s_skey,
+                                      &desc_text_final);
+          if (err)
+            goto leave;
+
+          err = agent_get_confirmation (ctrl, desc_text_final,
+                                        L_("Delete key"), L_("No"), 0);
+          if (err)
+            goto leave;
+
+          cf = ssh_open_control_file ();
+          if (cf)
+            {
+              if (!ssh_search_control_file (cf, hexgrip, NULL, NULL, NULL))
+                {
+                  err = agent_get_confirmation
+                    (ctrl,
+                     L_("Warning: This key is also listed for use with SSH!\n"
+                        "Deleting the key might remove your ability to "
+                        "access remote machines."),
+                     L_("Delete key"), L_("No"), 0);
+                  if (err)
+                    goto leave;
+                }
+            }
+        }
+      err = remove_key_file (grip);
+      break;
+
+    case PRIVATE_KEY_SHADOWED:
+      err = gpg_error (GPG_ERR_KEY_ON_CARD);
+      break;
+
+    default:
+      log_error ("invalid private key format\n");
+      err = gpg_error (GPG_ERR_BAD_SECKEY);
+      break;
+    }
+
+ leave:
+  ssh_close_control_file (cf);
+  gcry_free (comment);
+  xfree (desc_text_final);
+  xfree (default_desc);
+  xfree (buf);
+  gcry_sexp_release (s_skey);
+  return err;
+}