Support a confirm flag for ssh.
authorWerner Koch <wk@gnupg.org>
Wed, 20 Jul 2011 18:49:41 +0000 (20:49 +0200)
committerWerner Koch <wk@gnupg.org>
Wed, 20 Jul 2011 18:49:41 +0000 (20:49 +0200)
This implements the suggestion from bug#1349.  With this change the
fingerprint of the ssh key is also displayed in the pinentry prompts.

NEWS
agent/ChangeLog
agent/agent.h
agent/command-ssh.c
agent/findkey.c
common/t-ssh-utils.c
doc/gpg-agent.texi

diff --git a/NEWS b/NEWS
index c2271af..ed37e3b 100644 (file)
--- a/NEWS
+++ b/NEWS
@@ -5,6 +5,8 @@ Noteworthy changes in version 2.1.0beta3
 
  * Allow generation of card keys up to 4096 bit.
 
+ * Support the SSH confirm flag.
+
 
 Noteworthy changes in version 2.1.0beta2 (2011-03-08)
 -----------------------------------------------------
index 3234aae..21ccd81 100644 (file)
@@ -1,3 +1,17 @@
+2011-07-20  Werner Koch  <wk@g10code.com>
+
+       * command-ssh.c (ssh_identity_register): Display the ssh
+       fingerprint in the prompt.
+       (add_control_entry): Add arg FMTFPR and use it as comment in
+       sshcontrol.
+       (confirm_flag_from_sshcontrol): New.
+       (data_sign): Ask for confirmaton if requested.
+       (search_control_file): Add new arg R_CONFIRM and enhance parser.
+       * findkey.c (agent_raw_key_from_file): New.
+       (modify_description): Add format letter %F.
+       * findkey.c (agent_key_from_file): Simplify comment extraction by
+       using gcry_sexp_nth_string.
+
 2011-06-28  Ben Kibbey <bjk@luxsci.net>
 
        * command.c (option_handler): Add option s2k-count.
index dfc82ec..fbd71d5 100644 (file)
@@ -268,6 +268,8 @@ gpg_error_t agent_key_from_file (ctrl_t ctrl,
                                  lookup_ttl_t lookup_ttl,
                                  gcry_sexp_t *result,
                                  char **r_passphrase);
+gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
+                                     gcry_sexp_t *result);
 gpg_error_t agent_public_key_from_file (ctrl_t ctrl,
                                         const unsigned char *grip,
                                         gcry_sexp_t *result);
index e3a0410..3fef83e 100644 (file)
@@ -33,6 +33,8 @@
 #include "agent.h"
 
 #include "i18n.h"
+#include "../common/ssh-utils.h"
+
 
 \f
 
@@ -708,17 +710,23 @@ open_control_file (FILE **r_fp, int append)
 /* Search the file at stream FP from the beginning until a matching
    HEXGRIP is found; return success in this case and store true at
    DISABLED if the found key has been disabled.  If R_TTL is not NULL
-   a specified TTL for that key is stored there. */
+   a specified TTL for that key is stored there.  If R_CONFIRM is not
+   NULL it is set to 1 if the key has the confirm flag set. */
 static gpg_error_t
 search_control_file (FILE *fp, const char *hexgrip,
-                     int *r_disabled, int *r_ttl)
+                     int *r_disabled, int *r_ttl, int *r_confirm)
 {
-  int c, i;
+  int c, i, n;
   char *p, *pend, line[256];
   long ttl;
+  int lnr = 0;
+  const char fname[] = "sshcontrol";
 
   assert (strlen (hexgrip) == 40 );
 
+  if (r_confirm)
+    *r_confirm = 0;
+
   fseek (fp, 0, SEEK_SET);
   clearerr (fp);
   *r_disabled = 0;
@@ -731,6 +739,7 @@ search_control_file (FILE *fp, const char *hexgrip,
             return gpg_error (GPG_ERR_EOF);
           return gpg_error (gpg_err_code_from_errno (errno));
         }
+      lnr++;
 
       if (!*line || line[strlen(line)-1] != '\n')
         {
@@ -760,7 +769,7 @@ search_control_file (FILE *fp, const char *hexgrip,
       goto next_line;
   if (i != 40 || !(spacep (p) || *p == '\n'))
     {
-      log_error ("invalid formatted line in ssh control file\n");
+      log_error ("invalid formatted line in `%s', line %d\n", fname, lnr);
       return gpg_error (GPG_ERR_BAD_DATA);
     }
 
@@ -768,13 +777,37 @@ search_control_file (FILE *fp, const char *hexgrip,
   p = pend;
   if (!(spacep (p) || *p == '\n') || ttl < -1)
     {
-      log_error ("invalid TTL value in ssh control file; assuming 0\n");
+      log_error ("invalid TTL value in `%s', line %d; assuming 0\n",
+                 fname, lnr);
       ttl = 0;
     }
   if (r_ttl)
     *r_ttl = ttl;
 
-  /* Here is the place to parse flags if we need them.  */
+  /* Now check for key-value pairs of the form NAME[=VALUE]. */
+  while (*p)
+    {
+      for (; spacep (p) && *p != '\n'; p++)
+        ;
+      if (!*p || *p == '\n')
+        break;
+      n = strcspn (p, "= \t\n");
+      if (p[n] == '=')
+        {
+          log_error ("assigning a value to a flag is not yet supported; "
+                     "in `%s', line %d; flag ignored\n", fname, lnr);
+          p++;
+        }
+      else if (n == 7 && !memcmp (p, "confirm", 7))
+        {
+          if (r_confirm)
+            *r_confirm = 1;
+        }
+      else
+        log_error ("invalid flag `%.*s' in `%s', line %d; ignored\n",
+                   n, p, fname, lnr);
+      p += n;
+    }
 
   return 0; /* Okay:  found it.  */
 }
@@ -783,11 +816,12 @@ search_control_file (FILE *fp, const char *hexgrip,
 
 /* Add an entry to the control file to mark the key with the keygrip
    HEXGRIP as usable for SSH; i.e. it will be returned when ssh asks
-   for it.  This function is in general used to add a key received
-   through the ssh-add function.  We can assume that the user wants to
-   allow ssh using this key. */
+   for it.  FMTFPR is the fingerprint string.  This function is in
+   general used to add a key received through the ssh-add function.
+   We can assume that the user wants to allow ssh using this key. */
 static gpg_error_t
-add_control_entry (ctrl_t ctrl, const char *hexgrip, int ttl)
+add_control_entry (ctrl_t ctrl, const char *hexgrip, const char *fmtfpr,
+                   int ttl, int confirm)
 {
   gpg_error_t err;
   FILE *fp;
@@ -799,7 +833,7 @@ add_control_entry (ctrl_t ctrl, const char *hexgrip, int ttl)
   if (err)
     return err;
 
-  err = search_control_file (fp, hexgrip, &disabled, NULL);
+  err = search_control_file (fp, hexgrip, &disabled, NULL, NULL);
   if (err && gpg_err_code(err) == GPG_ERR_EOF)
     {
       struct tm *tp;
@@ -808,10 +842,12 @@ add_control_entry (ctrl_t ctrl, const char *hexgrip, int ttl)
       /* Not yet in the file - add it. Because the file has been
          opened in append mode, we simply need to write to it.  */
       tp = localtime (&atime);
-      fprintf (fp, "# Key added on %04d-%02d-%02d %02d:%02d:%02d\n%s %d\n",
+      fprintf (fp, ("# Key added on: %04d-%02d-%02d %02d:%02d:%02d\n"
+                    "# Fingerprint:  %s\n"
+                    "%s %d%s\n"),
                1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
                tp->tm_hour, tp->tm_min, tp->tm_sec,
-               hexgrip, ttl);
+               fmtfpr, hexgrip, ttl, confirm? " confirm":"");
 
     }
   fclose (fp);
@@ -832,7 +868,7 @@ ttl_from_sshcontrol (const char *hexgrip)
   if (open_control_file (&fp, 0))
     return 0; /* Error: Use the global default TTL.  */
 
-  if (search_control_file (fp, hexgrip, &disabled, &ttl)
+  if (search_control_file (fp, hexgrip, &disabled, &ttl, NULL)
       || disabled)
     ttl = 0;  /* Use the global default if not found or disabled.  */
 
@@ -842,6 +878,30 @@ ttl_from_sshcontrol (const char *hexgrip)
 }
 
 
+/* Scan the sshcontrol file and return the confirm flag.  */
+static int
+confirm_flag_from_sshcontrol (const char *hexgrip)
+{
+  FILE *fp;
+  int disabled, confirm;
+
+  if (!hexgrip || strlen (hexgrip) != 40)
+    return 1;  /* Wrong input: Better ask for confirmation.  */
+
+  if (open_control_file (&fp, 0))
+    return 1; /* Error: Better ask for confirmation.  */
+
+  if (search_control_file (fp, hexgrip, &disabled, NULL, &confirm)
+      || disabled)
+    confirm = 0;  /* If not found or disabled, there is no reason to
+                     ask for confirmation.  */
+
+  fclose (fp);
+
+  return confirm;
+}
+
+
 
 \f
 
@@ -1588,6 +1648,7 @@ ssh_key_grip (gcry_sexp_t key, unsigned char *buffer)
   return 0;
 }
 
+
 /* Converts the secret key KEY_SECRET into a public key, storing it in
    KEY_PUBLIC.  SPEC is the according key specification.  Returns zero
    on success or an error code.  */
@@ -1909,7 +1970,7 @@ ssh_handler_request_identities (ctrl_t ctrl,
           hexgrip[40] = 0;
           if ( strlen (hexgrip) != 40 )
             continue;
-          if (search_control_file (ctrl_fp, hexgrip, &disabled, NULL)
+          if (search_control_file (ctrl_fp, hexgrip, &disabled, NULL, NULL)
               || disabled)
             continue;
 
@@ -2044,14 +2105,60 @@ data_sign (ctrl_t ctrl, ssh_signature_encoder_t sig_encoder,
   const char *elems;
   size_t elems_n;
   gcry_mpi_t *mpis = NULL;
+  char hexgrip[40+1];
 
   *sig = NULL;
   *sig_n = 0;
 
+  /* Quick check to see whether we have a valid keygrip and convert it
+     to hex.  */
+  if (!ctrl->have_keygrip)
+    {
+      err = gpg_error (GPG_ERR_NO_SECKEY);
+      goto out;
+    }
+  bin2hex (ctrl->keygrip, 20, hexgrip);
+
+  /* Ask for confirmation if needed.  */
+  if (confirm_flag_from_sshcontrol (hexgrip))
+    {
+      gcry_sexp_t key;
+      char *fpr, *prompt;
+      char *comment = NULL;
+
+      err = agent_raw_key_from_file (ctrl, ctrl->keygrip, &key);
+      if (err)
+        goto out;
+      err = ssh_get_fingerprint_string (key, &fpr);
+      if (!err)
+        {
+          gcry_sexp_t tmpsxp = gcry_sexp_find_token (key, "comment", 0);
+          if (tmpsxp)
+            comment = gcry_sexp_nth_string (tmpsxp, 1);
+          gcry_sexp_release (tmpsxp);
+        }
+      gcry_sexp_release (key);
+      if (err)
+        goto out;
+      prompt = xtryasprintf (_("An ssh process requested the use of key%%0A"
+                               "  %s%%0A"
+                               "  (%s)%%0A"
+                               "Do you want to allow this?"),
+                             fpr, comment? comment:"");
+      xfree (fpr);
+      gcry_free (comment);
+      err = agent_get_confirmation (ctrl, prompt, _("Allow"), _("Deny"), 0);
+      xfree (prompt);
+      if (err)
+        goto out;
+    }
+
+  /* Create signature.  */
   ctrl->use_auth_call = 1;
   err = agent_pksign_do (ctrl, NULL,
                          _("Please enter the passphrase "
-                           "for the ssh key%0A  %c"), &signature_sexp,
+                           "for the ssh key%%0A  %F%%0A  (%c)"),
+                         &signature_sexp,
                          CACHE_MODE_SSH, ttl_from_sshcontrol);
   ctrl->use_auth_call = 0;
   if (err)
@@ -2370,7 +2477,7 @@ reenter_compare_cb (struct pin_entry_info_s *pi)
    our key storage, don't do anything.  When entering a new key also
    add an entry to the sshcontrol file.  */
 static gpg_error_t
-ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl)
+ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl, int confirm)
 {
   gpg_error_t err;
   unsigned char key_grip_raw[20];
@@ -2380,6 +2487,7 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl)
   char *description = NULL;
   const char *description2 = _("Please re-enter this passphrase");
   char *comment = NULL;
+  char *key_fpr = NULL;
   const char *initial_errtext = NULL;
   unsigned int i;
   struct pin_entry_info_s *pi = NULL, *pi2;
@@ -2393,6 +2501,9 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl)
   if ( !agent_key_available (key_grip_raw) )
     goto out; /* Yes, key is available.  */
 
+  err = ssh_get_fingerprint_string (key, &key_fpr);
+  if (err)
+    goto out;
 
   err = ssh_key_extract_comment (key, &comment);
   if (err)
@@ -2402,8 +2513,9 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl)
                  _("Please enter a passphrase to protect"
                    " the received secret key%%0A"
                    "   %s%%0A"
+                   "   %s%%0A"
                    "within gpg-agent's key storage"),
-                 comment ? comment : "?") < 0)
+                 key_fpr, comment ? comment : "") < 0)
     {
       err = gpg_error_from_syserror ();
       goto out;
@@ -2460,7 +2572,7 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl)
     goto out;
 
   /* And add an entry to the sshcontrol file.  */
-  err = add_control_entry (ctrl, key_grip, ttl);
+  err = add_control_entry (ctrl, key_grip, key_fpr, ttl, confirm);
 
 
  out:
@@ -2469,6 +2581,7 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl)
   xfree (pi);
   xfree (buffer);
   xfree (comment);
+  xfree (key_fpr);
   xfree (description);
 
   return err;
@@ -2553,9 +2666,7 @@ ssh_handler_add_identity (ctrl_t ctrl, estream_t request, estream_t response)
   if (err)
     goto out;
 
-  /* FIXME: are constraints used correctly?  */
-
-  err = ssh_identity_register (ctrl, key, ttl);
+  err = ssh_identity_register (ctrl, key, ttl, confirm);
 
  out:
 
index 7e1cefc..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.
  *
@@ -33,6 +33,7 @@
 
 #include "agent.h"
 #include "i18n.h"
+#include "../common/ssh-utils.h"
 
 #ifndef O_BINARY
 #define O_BINARY 0
@@ -185,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;
@@ -198,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);
@@ -233,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)
                     {
@@ -259,12 +275,16 @@ modify_description (const char *in, const char *comment, char **result)
         {
           *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;
 }
 
@@ -564,45 +584,26 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
       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)
          {
@@ -613,7 +614,6 @@ agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
                         gpg_strerror (rc));
          }
 
-       gcry_sexp_release (comment_sexp);
        xfree (desc_text_final);
       }
       break;
@@ -793,6 +793,28 @@ agent_is_dsa_key (gcry_sexp_t s_key)
 
 
 
+/* 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
index cd1252f..a8a63cf 100644 (file)
 #include "util.h"
 #include "ssh-utils.h"
 
-#define pass()  do { ; } while(0)
-#define fail(a,e)                                                       \
-  do { fprintf (stderr, "%s:%d: test %d failed (%s)\n",                 \
-                __FILE__,__LINE__, (a), gpg_strerror (e));              \
-    exit (1);                                                           \
-  } while(0)
-
 
 static struct { const char *key; const char *fpr; } sample_keys[] = {
   { "(protected-private-key "
index 84486cf..8811d05 100644 (file)
@@ -646,6 +646,12 @@ digits, optionally followed by the caching TTL in seconds and another
 optional field for arbitrary flags.  A non-zero TTL overrides the global
 default as set by @option{--default-cache-ttl-ssh}.
 
+The only flag support is @code{confirm}.  If this flag is found for a
+key, each use of the key will pop up a pinentry to confirm the use of
+that key.  The flag is automatically set if a new key was loaded into
+@code{gpg-agent} using the option @option{-c} of the @code{ssh-add}
+command.
+
 The keygrip may be prefixed with a @code{!} to disable an entry entry.
 
 The following example lists exactly one key.  Note that keys available
@@ -653,8 +659,9 @@ through a OpenPGP smartcard in the active smartcard reader are
 implicitly added to this list; i.e. there is no need to list them.
 
   @example
-  # Key added on 2005-02-25 15:08:29
-  5A6592BF45DC73BD876874A28FD4639282E29B52 0
+  # Key added on: 2011-07-20 20:38:46
+  # Fingerprint:  5e:8d:c4:ad:e7:af:6e:27:8a:d6:13:e4:79:ad:0b:81
+  34B62F25E277CF13D3C6BCEBFD3F85D08F0A864B 0 confirm
   @end example
 
 @item private-keys-v1.d/