Generate the ChangeLog from commit logs.
[gnupg.git] / agent / findkey.c
index 5668aaf..11b3cca 100644 (file)
@@ -1,6 +1,6 @@
 /* findkey.c - Locate the secret key
  * Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007,
- *               2010 Free Software Foundation, Inc.
+ *               2010, 2011 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
 
 #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;
@@ -59,7 +60,7 @@ agent_write_private_key (const unsigned char *grip,
   char *fname;
   estream_t fp;
   char hexgrip[40+4+1];
-  
+
   bin2hex (grip, 20, hexgrip);
   strcpy (hexgrip+40, ".key");
 
@@ -73,8 +74,8 @@ agent_write_private_key (const unsigned char *grip,
     }
 
   fp = es_fopen (fname, force? "wb,mode=-rw" : "wbx,mode=-rw");
-  if (!fp) 
-    { 
+  if (!fp)
+    {
       gpg_error_t tmperr = gpg_error_from_syserror ();
       log_error ("can't create `%s': %s\n", fname, gpg_strerror (tmperr));
       xfree (fname);
@@ -104,7 +105,7 @@ 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
 try_unprotect_cb (struct pin_entry_info_s *pi)
@@ -143,7 +144,7 @@ try_unprotect_cb (struct pin_entry_info_s *pi)
       if (strcmp (now, tmptime) > 0 )
         {
           /* Passphrase "expired".  */
-          desc = xtryasprintf 
+          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);
@@ -169,7 +170,8 @@ try_unprotect_cb (struct pin_entry_info_s *pi)
                                         _("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);
@@ -184,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;
@@ -197,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);
@@ -232,6 +237,18 @@ modify_description (const char *in, const char *comment, char **result)
                     out_len += comment_length;
                   break;
 
+                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)
                     {
@@ -253,31 +270,40 @@ 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_syserror ();
+            {
+              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.  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,
-           unsigned char **keybuf, const unsigned char *grip, 
-           cache_mode_t cache_mode, lookup_ttl_t lookup_ttl)
+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;
@@ -285,28 +311,57 @@ unprotect (ctrl_t ctrl, const char *desc_text,
   unsigned char *result;
   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 (*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);
           if (!rc)
             {
+              if (r_passphrase)
+                *r_passphrase = pw;
+              else
+                xfree (pw);
               xfree (*keybuf);
               *keybuf = result;
               return 0;
             }
+          xfree (pw);
           rc  = 0;
         }
 
@@ -327,7 +382,7 @@ 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); 
+              pth_sleep (1);
               goto retry;
             }
           /* Timeout - better call pinentry now the plain way. */
@@ -356,7 +411,7 @@ unprotect (ctrl_t ctrl, const char *desc_text,
         {
           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,
@@ -370,11 +425,11 @@ 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)
             {
-              log_error ("changing the passphrase failed: %s\n", 
+              log_error ("changing the passphrase failed: %s\n",
                          gpg_strerror (rc));
               wipememory (arg.unprotected_key, canlen);
               xfree (arg.unprotected_key);
@@ -382,8 +437,13 @@ unprotect (ctrl_t ctrl, const char *desc_text,
               return rc;
             }
         }
-      agent_put_cache (hexgrip, cache_mode, pi->pin, 
-                       lookup_ttl? lookup_ttl (hexgrip) : 0);
+      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;
     }
@@ -406,7 +466,7 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result)
   size_t buflen, erroff;
   gcry_sexp_t s_skey;
   char hexgrip[40+4+1];
-  
+
   *result = NULL;
 
   bin2hex (grip, 20, hexgrip);
@@ -422,7 +482,7 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result)
       xfree (fname);
       return rc;
     }
-  
+
   if (fstat (es_fileno (fp), &st))
     {
       rc = gpg_error_from_syserror ();
@@ -449,7 +509,7 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result)
   if (es_fread (buf, buflen, 1, fp) != 1)
     {
       rc = gpg_error_from_syserror ();
-      log_error ("error reading %zu bytes from `%s': %s\n", 
+      log_error ("error reading %zu bytes from `%s': %s\n",
                  buflen, fname, strerror (errno));
       xfree (fname);
       es_fclose (fp);
@@ -480,24 +540,32 @@ read_key_file (const unsigned char *grip, gcry_sexp_t *result)
    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)
@@ -516,56 +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_syserror ();
-                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,
-                            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));
          }
-        
-       gcry_sexp_release (comment_sexp);
+
        xfree (desc_text_final);
       }
       break;
@@ -606,6 +654,11 @@ 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;
     }
 
@@ -617,6 +670,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;
     }
 
@@ -668,6 +726,16 @@ key_parms_from_sexp (gcry_sexp_t s_key, gcry_sexp_t *r_list,
       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";
@@ -685,7 +753,7 @@ key_parms_from_sexp (gcry_sexp_t s_key, gcry_sexp_t *r_list,
       if (strlen (algoname) >= algonamesize)
         return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
       strcpy (r_algoname, algoname);
-    } 
+    }
   if (r_elems)
     {
       if (strlen (elems) >= elemssize)
@@ -697,13 +765,14 @@ key_parms_from_sexp (gcry_sexp_t s_key, gcry_sexp_t *r_list,
     *r_list = list;
   else
     gcry_sexp_release (list);
-      
+
   return 0;
 }
 
 
-/* Return true if S_KEY is a DSA style key.  */
-int 
+/* 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];
@@ -714,17 +783,44 @@ agent_is_dsa_key (gcry_sexp_t s_key)
   if (key_parms_from_sexp (s_key, NULL, algoname, sizeof algoname, NULL, 0))
     return 0; /* Error - assume it is not an DSA key.  */
 
-  return (!strcmp (algoname, "dsa") || !strcmp (algoname, "ecdsa"));
+  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, 
+agent_public_key_from_file (ctrl_t ctrl,
                             const unsigned char *grip,
                             gcry_sexp_t *result)
 {
@@ -752,7 +848,7 @@ agent_public_key_from_file (ctrl_t ctrl,
   if (err)
     return err;
 
-  err = key_parms_from_sexp (s_skey, &list, 
+  err = key_parms_from_sexp (s_skey, &list,
                             algoname, sizeof algoname,
                             elems, sizeof elems);
   if (err)
@@ -772,7 +868,7 @@ agent_public_key_from_file (ctrl_t ctrl,
       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)
@@ -839,7 +935,7 @@ agent_public_key_from_file (ctrl_t ctrl,
 
   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;
@@ -866,7 +962,7 @@ agent_public_key_from_file (ctrl_t ctrl,
   *p = 0;
   assert (argidx < DIM (args));
   args[argidx] = NULL;
-    
+
   err = gcry_sexp_build_array (&list, NULL, format, args);
   xfree (format);
   for (i=0; array[i]; i++)
@@ -890,7 +986,7 @@ agent_key_available (const unsigned char *grip)
   int result;
   char *fname;
   char hexgrip[40+4+1];
-  
+
   bin2hex (grip, 20, hexgrip);
   strcpy (hexgrip+40, ".key");
 
@@ -916,7 +1012,7 @@ agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
   int keytype;
 
   (void)ctrl;
-  
+
   if (r_keytype)
     *r_keytype = PRIVATE_KEY_UNKNOWN;
   if (r_shadow_info)
@@ -924,7 +1020,7 @@ agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
 
   {
     gcry_sexp_t sexp;
-    
+
     err = read_key_file (grip, &sexp);
     if (err)
       {
@@ -938,12 +1034,12 @@ agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
     if (err)
       return err;
   }
-  
+
   keytype = agent_private_key_type (buf);
   switch (keytype)
     {
     case PRIVATE_KEY_CLEAR:
-      break; 
+      break;
     case PRIVATE_KEY_PROTECTED:
       /* If we ever require it we could retrieve the comment fields
          from such a key. */