Add missing variable.
[gnupg.git] / agent / findkey.c
index 86a28d5..a03d0c4 100644 (file)
@@ -1,11 +1,12 @@
-/* findkey.c - locate the secret key
- * Copyright (C) 2001, 2002, 2003, 2004, 2005 Free Software Foundation, Inc.
+/* findkey.c - Locate the secret key
+ * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007,
+ *               2010, 2011 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
  * GnuPG is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * GnuPG is distributed in the hope that it will be useful,
@@ -14,8 +15,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, write to the Free Software
- * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
 #include <config.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/ssh-utils.h"
+
+#ifndef O_BINARY
+#define O_BINARY 0
+#endif
 
 /* Helper to pass data to the check callback of the unprotect function. */
-struct try_unprotect_arg_s {
+struct try_unprotect_arg_s
+{
+  ctrl_t ctrl;
   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.  */
 };
 
 
 /* Write an S-expression formatted key to our key storage.  With FORCE
-   pased as true an existsing key with the given GRIP will get
+   passed as true an existing key with the given GRIP will get
    overwritten.  */
 int
 agent_write_private_key (const unsigned char *grip,
                          const void *buffer, size_t length, int force)
 {
-  int i;
   char *fname;
-  FILE *fp;
+  estream_t fp;
   char hexgrip[40+4+1];
-  int fd;
-  
-  for (i=0; i < 20; i++)
-    sprintf (hexgrip+2*i, "%02X", grip[i]);
+
+  bin2hex (grip, 20, hexgrip);
   strcpy (hexgrip+40, ".key");
 
   fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
@@ -62,76 +70,114 @@ agent_write_private_key (const unsigned char *grip,
     {
       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_CREAT | O_EXCL | O_WRONLY),
-             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);
     }
 
-  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));
+  fp = es_fopen (fname, force? "wb,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 (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;
     }
-
+  bump_key_eventcounter ();
   xfree (fname);
   return 0;
 }
 
 
-/* Callback function to try the unprotection from the passpharse query
+/* Callback function to try the unprotection from the passphrase query
    code. */
 static int
 try_unprotect_cb (struct pin_entry_info_s *pi)
 {
   struct try_unprotect_arg_s *arg = pi->check_cb_arg;
   size_t dummy;
+  gpg_error_t err;
+  gnupg_isotime_t now, protected_at, tmptime;
+  char *desc = NULL;
 
   assert (!arg->unprotected_key);
-  return agent_unprotect (arg->protected_key, pi->pin,
-                          &arg->unprotected_key, &dummy);
+
+  arg->change_required = 0;
+  err = agent_unprotect (arg->protected_key, pi->pin, protected_at,
+                         &arg->unprotected_key, &dummy);
+  if (err)
+    return err;
+  if (!opt.max_passphrase_days || arg->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."));
+      if (!desc)
+        return gpg_error_from_syserror ();
+    }
+  else
+    {
+      gnupg_get_isotime (now);
+      gnupg_copy_time (tmptime, protected_at);
+      err = add_days_to_isotime (tmptime, opt.max_passphrase_days);
+      if (err)
+        return err;
+      if (strcmp (now, tmptime) > 0 )
+        {
+          /* Passphrase "expired".  */
+          desc = xtryasprintf
+            (_("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 ();
+        }
+    }
+
+  if (desc)
+    {
+      /* Change required.  */
+      if (opt.enforce_passphrase_constraints)
+        {
+          err = agent_get_confirmation (arg->ctrl, desc,
+                                        _("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);
+          if (!err)
+            arg->change_required = 1;
+          else if (gpg_err_code (err) == GPG_ERR_CANCELED
+                   || gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
+            err = 0;
+        }
+      xfree (desc);
+    }
+
+  return 0;
 }
 
 
@@ -140,12 +186,14 @@ try_unprotect_cb (struct pin_entry_info_s *pi)
 
    %% - Replaced by a single %
    %c - Replaced by the content of COMMENT.
+   %F - Replaced by an ssh style fingerprint computed from KEY.
 
    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, char **result)
+modify_description (const char *in, const char *comment, const gcry_sexp_t key,
+                    char **result)
 {
   size_t comment_length;
   size_t in_len;
@@ -153,6 +201,7 @@ modify_description (const char *in, const char *comment, char **result)
   char *out;
   size_t i;
   int special, pass;
+  char *ssh_fpr = NULL;
 
   comment_length = strlen (comment);
   in_len  = strlen (in);
@@ -166,9 +215,7 @@ modify_description (const char *in, const char *comment, char **result)
       special = 0;
       for (i = 0; i < in_len; i++)
         {
-          if (in[i] == '%')
-            special = 1;
-          else if (special)
+          if (special)
             {
               special = 0;
               switch (in[i])
@@ -190,10 +237,31 @@ modify_description (const char *in, const char *comment, char **result)
                     out_len += comment_length;
                   break;
 
-                default: /* Invalid special sequences are ignored.  */
+                case 'F': /* SSH style fingerprint.  */
+                  if (!ssh_fpr && key)
+                    ssh_get_fingerprint_string (key, &ssh_fpr);
+                  if (ssh_fpr)
+                    {
+                      if (out)
+                        out = stpcpy (out, ssh_fpr);
+                      else
+                        out_len += strlen (ssh_fpr);
+                    }
+                  break;
+
+                default: /* Invalid special sequences are kept as they are. */
+                  if (out)
+                    {
+                      *out++ = '%';
+                      *out++ = in[i];
+                    }
+                  else
+                    out_len+=2;
                   break;
                 }
             }
+          else if (in[i] == '%')
+            special = 1;
           else
             {
               if (out)
@@ -202,78 +270,180 @@ modify_description (const char *in, const char *comment, char **result)
                 out_len++;
             }
         }
-      
+
       if (!pass)
         {
           *result = out = xtrymalloc (out_len + 1);
           if (!out)
-            return gpg_error_from_errno (errno);
+            {
+              xfree (ssh_fpr);
+              return gpg_error_from_syserror ();
+            }
         }
     }
 
   *out = 0;
   assert (*result + out_len == out);
+  xfree (ssh_fpr);
   return 0;
 }
 
-  
+
 
 /* Unprotect the canconical encoded S-expression key in KEYBUF.  GRIP
    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. */
+   description used for the pinentry.  If LOOKUP_TTL is given this
+   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 ctrl, const char *desc_text,
-           unsigned char **keybuf, const unsigned char *grip, int ignore_cache)
+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,
+           char **r_passphrase)
 {
   struct pin_entry_info_s *pi;
   struct try_unprotect_arg_s arg;
-  int rc, i;
+  int rc;
   unsigned char *result;
   size_t resultlen;
   char hexgrip[40+1];
-  
-  for (i=0; i < 20; i++)
-    sprintf (hexgrip+2*i, "%02X", grip[i]);
-  hexgrip[40] = 0;
+
+  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 (*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 (!ignore_cache)
+  if (cache_mode != CACHE_MODE_IGNORE)
     {
-      void *cache_marker;
-      const char *pw = agent_get_cache (hexgrip, &cache_marker);
+      char *pw;
+
+    retry:
+      pw = agent_get_cache (hexgrip, cache_mode);
       if (pw)
         {
-          rc = agent_unprotect (*keybuf, pw, &result, &resultlen);
-          agent_unlock_cache_entry (&cache_marker);
+          rc = agent_unprotect (*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
+         situation where several requests for unprotecting a key have
+         been made but the user is still entering the passphrase for
+         the first request.  Because all requests to agent_askpin are
+         serialized they would then pop up one after the other to
+         request the passphrase - despite that the user has already
+         entered it and is then available in the cache.  This
+         implementation is not race free but in the worst case the
+         user has to enter the passphrase only once more. */
+      if (pinentry_active_p (ctrl, 0))
+        {
+          /* Active - wait */
+          if (!pinentry_active_p (ctrl, 60))
+            {
+              /* We need to give the other thread a chance to actually put
+                 it into the cache. */
+              npth_sleep (1);
+              goto retry;
+            }
+          /* Timeout - better call pinentry now the plain way. */
+        }
     }
-  
+
   pi = gcry_calloc_secure (1, sizeof (*pi) + 100);
   if (!pi)
-    return gpg_error_from_errno (errno);
+    return gpg_error_from_syserror ();
   pi->max_length = 100;
   pi->min_digits = 0;  /* we want a real passphrase */
-  pi->max_digits = 8;
+  pi->max_digits = 16;
   pi->max_tries = 3;
   pi->check_cb = try_unprotect_cb;
+  arg.ctrl = ctrl;
   arg.protected_key = *keybuf;
   arg.unprotected_key = NULL;
+  arg.change_required = 0;
   pi->check_cb_arg = &arg;
 
-  rc = agent_askpin (ctrl, desc_text, NULL, pi);
+  rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi);
   if (!rc)
     {
       assert (arg.unprotected_key);
-      agent_put_cache (hexgrip, pi->pin, 0);
+      if (arg.change_required)
+        {
+          size_t canlen, erroff;
+          gcry_sexp_t s_skey;
+
+          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);
+          if (rc)
+            {
+              log_error ("failed to build S-Exp (off=%u): %s\n",
+                         (unsigned int)erroff, gpg_strerror (rc));
+              wipememory (arg.unprotected_key, canlen);
+              xfree (arg.unprotected_key);
+              xfree (pi);
+              return rc;
+            }
+          rc = agent_protect_and_store (ctrl, s_skey, NULL);
+          gcry_sexp_release (s_skey);
+          if (rc)
+            {
+              log_error ("changing the passphrase failed: %s\n",
+                         gpg_strerror (rc));
+              wipememory (arg.unprotected_key, canlen);
+              xfree (arg.unprotected_key);
+              xfree (pi);
+              return rc;
+            }
+        }
+      else
+        {
+          agent_put_cache (hexgrip, cache_mode, pi->pin,
+                           lookup_ttl? lookup_ttl (hexgrip) : 0);
+          if (r_passphrase && *pi->pin)
+            *r_passphrase = xtrystrdup (pi->pin);
+        }
       xfree (*keybuf);
       *keybuf = arg.unprotected_key;
     }
@@ -288,56 +458,69 @@ unprotect (CTRL ctrl, const char *desc_text,
 static gpg_error_t
 read_key_file (const unsigned char *grip, gcry_sexp_t *result)
 {
-  int i, rc;
+  int rc;
   char *fname;
-  FILE *fp;
+  estream_t fp;
   struct stat st;
   unsigned char *buf;
   size_t buflen, erroff;
   gcry_sexp_t s_skey;
   char hexgrip[40+4+1];
-  
+
   *result = NULL;
 
-  for (i=0; i < 20; i++)
-    sprintf (hexgrip+2*i, "%02X", grip[i]);
+  bin2hex (grip, 20, hexgrip);
   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_errno (errno);
-      log_error ("can't open `%s': %s\n", fname, strerror (errno));
+      rc = gpg_error_from_syserror ();
+      if (gpg_err_code (rc) != GPG_ERR_ENOENT)
+        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_errno (errno);
+      rc = gpg_error_from_syserror ();
       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_errno (errno);
-      log_error ("error reading `%s': %s\n", fname, strerror (errno));
+      rc = gpg_error_from_syserror ();
+      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;
     }
 
   /* Convert the file into a gcrypt S-expression object.  */
-  rc = gcry_sexp_sscan (&s_skey, &erroff, buf, buflen);
+  rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
   xfree (fname);
-  fclose (fp);
+  es_fclose (fp);
   xfree (buf);
   if (rc)
     {
@@ -351,46 +534,49 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result)
 
 
 /* Return the secret key as an S-Exp in RESULT after locating it using
-   the grip.  Returns NULL in RESULT if the operation should be
-   diverted to a token; SHADOW_INFO will point then to an allocated
-   S-Expression with the shadow_info part from the file.  With
-   IGNORE_CACHE passed as true the passphrase is not taken from the
-   cache.  DESC_TEXT may be set to present a custom description for the
-   pinentry. */
+   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.
+   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).  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,
-                     int ignore_cache, gcry_sexp_t *result)
+                     cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
+                     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;
+    *shadow_info = NULL;
+  if (r_passphrase)
+    *r_passphrase = NULL;
 
   rc = read_key_file (grip, &s_skey);
   if (rc)
     return rc;
 
   /* For use with the protection functions we also need the key as an
-     canonical encoded S-expression in abuffer.  Create this buffer
+     canonical encoded S-expression in a buffer.  Create this buffer
      now.  */
-  len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0);
-  assert (len);
-  buf = xtrymalloc (len);
-  if (!buf)
-    {
-      rc = gpg_error_from_errno (errno);
-      gcry_sexp_release (s_skey);
-      return rc;
-    }
-  len = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, buf, len);
-  assert (len);
-
+  rc = make_canon_sexp (s_skey, &buf, &len);
+  if (rc)
+    return rc;
 
   switch (agent_private_key_type (buf))
     {
@@ -398,55 +584,36 @@ agent_key_from_file (ctrl_t ctrl, const char *desc_text,
       break; /* no unprotection needed */
     case PRIVATE_KEY_PROTECTED:
       {
-       gcry_sexp_t comment_sexp;
-       size_t comment_length;
        char *desc_text_final;
-       const char *comment = NULL;
+       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.  */
-       comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
-       if (comment_sexp)
-         comment = gcry_sexp_nth_data (comment_sexp, 1, &comment_length);
-       if (!comment)
-         {
-           comment = "";
-           comment_length = 0;
-         }
+        {
+          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);
+        }
 
         desc_text_final = NULL;
        if (desc_text)
-         {
-            if (comment[comment_length])
-              {
-                /* Not a C-string; create one.  We might here allocate
-                   more than actually displayed but well, that
-                   shouldn't be a problem.  */
-                char *tmp = xtrymalloc (comment_length+1);
-                if (!tmp)
-                  rc = gpg_error_from_errno (errno);
-                else
-                  {
-                    memcpy (tmp, comment, comment_length);
-                    tmp[comment_length] = 0;
-                    rc = modify_description (desc_text, tmp, &desc_text_final);
-                    xfree (tmp);
-                  }
-              }
-            else
-              rc = modify_description (desc_text, comment, &desc_text_final);
-         }
+          rc = modify_description (desc_text, comment? comment:"", s_skey,
+                                   &desc_text_final);
+        gcry_free (comment);
 
        if (!rc)
          {
-           rc = unprotect (ctrl, desc_text_final, &buf, grip, ignore_cache);
+           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));
          }
-        
-       gcry_sexp_release (comment_sexp);
+
        xfree (desc_text_final);
       }
       break;
@@ -487,17 +654,27 @@ agent_key_from_file (ctrl_t ctrl, const char *desc_text,
   if (rc || got_shadow_info)
     {
       xfree (buf);
+      if (r_passphrase)
+        {
+          xfree (*r_passphrase);
+          *r_passphrase = NULL;
+        }
       return rc;
     }
 
   buflen = gcry_sexp_canon_len (buf, 0, NULL, NULL);
-  rc = gcry_sexp_sscan (&s_skey, &erroff, buf, buflen);
+  rc = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
   wipememory (buf, buflen);
   xfree (buf);
   if (rc)
     {
       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;
     }
 
@@ -506,48 +683,32 @@ agent_key_from_file (ctrl_t ctrl, const char *desc_text,
 }
 
 
-
-/* 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)
+/* 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 tehre; 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)
 {
-  int i, idx, rc;
-  gcry_sexp_t s_skey;
-  const char *algoname;
-  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.  */
-  int argidx;
   gcry_sexp_t list, l2;
-  const char *name;
-  const char *s;
+  const char *name, *algoname, *elems;
   size_t n;
-  const char *elems;
-  gcry_mpi_t *array;
 
-  *result = NULL;
-
-  rc = read_key_file (grip, &s_skey);
-  if (rc)
-    return rc;
+  if (r_list)
+    *r_list = NULL;
 
-  list = gcry_sexp_find_token (s_skey, "shadowed-private-key", 0 );
+  list = gcry_sexp_find_token (s_key, "shadowed-private-key", 0 );
   if (!list)
-    list = gcry_sexp_find_token (s_skey, "protected-private-key", 0 );
+    list = gcry_sexp_find_token (s_key, "protected-private-key", 0 );
   if (!list)
-    list = gcry_sexp_find_token (s_skey, "private-key", 0 );
+    list = gcry_sexp_find_token (s_key, "private-key", 0 );
   if (!list)
     {
       log_error ("invalid private key format\n");
-      gcry_sexp_release (s_skey);
       return gpg_error (GPG_ERR_BAD_SECKEY);
     }
 
@@ -565,6 +726,16 @@ agent_public_key_from_file (ctrl_t ctrl,
       algoname = "dsa";
       elems = "pqgy";
     }
+  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";
@@ -574,22 +745,130 @@ agent_public_key_from_file (ctrl_t ctrl,
     {
       log_error ("unknown private key algorithm\n");
       gcry_sexp_release (list);
-      gcry_sexp_release (s_skey);
       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 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)
+{
+  char algoname[6];
+
+  if (!s_key)
+    return 0;
+
+  if (key_parms_from_sexp (s_key, NULL, algoname, sizeof algoname, NULL, 0))
+    return 0; /* Error - assume it is not an DSA key.  */
+
+  if (!strcmp (algoname, "dsa"))
+    return GCRY_PK_DSA;
+  else if (!strcmp (algoname, "ecdsa"))
+    return GCRY_PK_ECDSA;
+  else
+    return 0;
+}
+
+
+
+/* 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
+   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)
+{
+  gpg_error_t err;
+  gcry_sexp_t s_skey;
+
+  (void)ctrl;
+
+  *result = NULL;
+
+  err = read_key_file (grip, &s_skey);
+  if (!err)
+    *result = 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)
+{
+  gpg_error_t err;
+  int i, idx;
+  gcry_sexp_t s_skey;
+  char algoname[6];
+  char elems[6];
+  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.  */
+  int argidx;
+  gcry_sexp_t list, l2;
+  const char *s;
+  gcry_mpi_t *array;
+
+  (void)ctrl;
+
+  *result = NULL;
+
+  err = read_key_file (grip, &s_skey);
+  if (err)
+    return err;
+
+  err = key_parms_from_sexp (s_skey, &list,
+                            algoname, sizeof algoname,
+                            elems, sizeof elems);
+  if (err)
+    {
+      gcry_sexp_release (s_skey);
+      return err;
+    }
+
   /* 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)
     {
-      rc = gpg_error_from_errno (errno);
+      err = gpg_error_from_syserror ();
       gcry_sexp_release (list);
       gcry_sexp_release (s_skey);
-      return rc;
+      return err;
     }
 
-  for (idx=0, s=elems; *s; s++, idx++ ) 
+  for (idx=0, s=elems; *s; s++, idx++ )
     {
       l2 = gcry_sexp_find_token (list, s, 1);
       if (!l2)
@@ -635,8 +914,8 @@ 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.  */
@@ -645,24 +924,24 @@ agent_public_key_from_file (ctrl_t ctrl,
   format = xtrymalloc (15+7*strlen (elems)+10+15+1+1);
   if (!format)
     {
-      rc = gpg_error_from_errno (errno);
+      err = gpg_error_from_syserror ();
       for (i=0; array[i]; i++)
         gcry_mpi_release (array[i]);
       xfree (array);
       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++ ) 
+  for (idx=0, s=elems; *s; s++, idx++ )
     {
       *p++ = '(';
       *p++ = *s;
       p = stpcpy (p, " %m)");
       assert (argidx < DIM (args));
-      args[argidx++] = array[idx];
+      args[argidx++] = &array[idx];
     }
   *p++ = ')';
   if (uri)
@@ -683,8 +962,8 @@ agent_public_key_from_file (ctrl_t ctrl,
   *p = 0;
   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]);
@@ -692,31 +971,106 @@ agent_public_key_from_file (ctrl_t ctrl,
   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)
 {
-  int i;
+  int result;
   char *fname;
   char hexgrip[40+4+1];
-  
-  for (i=0; i < 20; i++)
-    sprintf (hexgrip+2*i, "%02X", grip[i]);
+
+  bin2hex (grip, 20, hexgrip);
   strcpy (hexgrip+40, ".key");
 
   fname = make_filename (opt.homedir, GNUPG_PRIVATE_KEYS_DIR, hexgrip, NULL);
-  i = !access (fname, R_OK)? 0 : -1;
+  result = !access (fname, R_OK)? 0 : -1;
   xfree (fname);
-  return i;
+  return result;
 }
 
 
 
+/* Return the information about the secret key specified by the binary
+   keygrip GRIP.  If the key is a shadowed one the shadow information
+   will be stored at the address R_SHADOW_INFO as an allocated
+   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)
+{
+  gpg_error_t err;
+  unsigned char *buf;
+  size_t len;
+  int keytype;
+
+  (void)ctrl;
+
+  if (r_keytype)
+    *r_keytype = PRIVATE_KEY_UNKNOWN;
+  if (r_shadow_info)
+    *r_shadow_info = NULL;
+
+  {
+    gcry_sexp_t sexp;
+
+    err = read_key_file (grip, &sexp);
+    if (err)
+      {
+        if (gpg_err_code (err) == GPG_ERR_ENOENT)
+          return gpg_error (GPG_ERR_NOT_FOUND);
+        else
+          return err;
+      }
+    err = make_canon_sexp (sexp, &buf, &len);
+    gcry_sexp_release (sexp);
+    if (err)
+      return err;
+  }
+
+  keytype = agent_private_key_type (buf);
+  switch (keytype)
+    {
+    case PRIVATE_KEY_CLEAR:
+      break;
+    case PRIVATE_KEY_PROTECTED:
+      /* If we ever require it we could retrieve the comment fields
+         from such a key. */
+      break;
+    case PRIVATE_KEY_SHADOWED:
+      if (r_shadow_info)
+        {
+          const unsigned char *s;
+          size_t n;
+
+          err = agent_get_shadow_info (buf, &s);
+          if (!err)
+            {
+              n = gcry_sexp_canon_len (s, 0, NULL, NULL);
+              assert (n);
+              *r_shadow_info = xtrymalloc (n);
+              if (!*r_shadow_info)
+                err = gpg_error_from_syserror ();
+              else
+                memcpy (*r_shadow_info, s, n);
+            }
+        }
+      break;
+    default:
+      err = gpg_error (GPG_ERR_BAD_SECKEY);
+      break;
+    }
+
+  if (!err && r_keytype)
+    *r_keytype = keytype;
+
+  xfree (buf);
+  return err;
+}