agent: Backport changes from 2.1 to support an external password manager.
authorNeal H. Walfield <neal@gnu.org>
Tue, 19 May 2015 11:53:43 +0000 (13:53 +0200)
committerNeal H. Walfield <neal@gnu.org>
Tue, 19 May 2015 13:32:54 +0000 (15:32 +0200)
* agent/agent.h (agent_askpin): Add arguments keyinfo and cache_mode.
Update callers.
(agent_get_passphrase): Likewise.
(agent_clear_passphrase): New function.
(opt): Add field allow_external_cache.
* agent/call-pinentry.c (start_pinentry): Send "OPTION
allow-external-password-cache" to the pinentry.
(PINENTRY_STATUS_PASSWORD_FROM_CACHE): New constant.
(pinentry_status_cb): New function.
(agent_askpin): Add arguments keyinfo and cache_mode.  If KEYINFO and
CACHE_MODE describe a cachable key, then send SETKEYINFO to the
pinentry.  Pass PINENTRY_STATUS_CB to the "GETPIN" invocation.  If the
passphrase was incorrect and PINENTRY_STATUS_PASSWORD_FROM_CACHE is
set, decrement PININFO->FAILED_TRIES.
(agent_get_passphrase): Add arguments keyinfo and cache_mode.  If
KEYINFO and CACHE_MODE describe a cachable key, then send SETKEYINFO
to the pinentry.
(agent_clear_passphrase): New function.
* agent/call-pinentry.c (start_pinentry): Act upon new var,
allow_external_cache.
* agent/command.c (cmd_clear_passphrase): Call agent_clear_passphrase.
* agent/gpg-agent.c (oNoAllowExternalCache): New.
(opts): Add option --no-allow-external-cache.
(parse_rereadable_options): Set this option.

--
Signed-off-by: Neal H. Walfield <neal@g10code.com>
Based on commits:

3a9305439b75ccd4446378d4fd87da087fd9c892
e201c20f25e7bed29088186c5f717d43047a0f4b
d7293cb317acc40cc9e5189cef33fe9d8b47e62a
56b5c9f94f2e55d096be585ed061ccf1c9ec0de6
d3b5cad2346bd5747789dc62d7804fa5c15f4f3b
2180845959839705200e3172dbafc94b70b9007f

agent/agent.h
agent/call-pinentry.c
agent/command-ssh.c
agent/command.c
agent/divert-scd.c
agent/findkey.c
agent/genkey.c
agent/gpg-agent.c
doc/gpg-agent.texi
tools/gpgconf-comp.c

index 938a9aa..f81743f 100644 (file)
@@ -104,6 +104,12 @@ struct
   int ignore_cache_for_signing;
   int allow_mark_trusted;
   int allow_preset_passphrase;
+
+  /* Allow the use of an external password cache.  If this option is
+     enabled (which is the default) we send an option to Pinentry
+     to allow it to enable such a cache.  */
+  int allow_external_cache;
+
   int keep_tty;      /* Don't switch the TTY (for pinentry) on request */
   int keep_display;  /* Don't switch the DISPLAY (for pinentry) on request */
   int ssh_support;   /* Enable ssh-agent emulation.  */
@@ -273,16 +279,20 @@ int pinentry_active_p (ctrl_t ctrl, int waitseconds);
 int agent_askpin (ctrl_t ctrl,
                   const char *desc_text, const char *prompt_text,
                   const char *inital_errtext,
-                  struct pin_entry_info_s *pininfo);
+                  struct pin_entry_info_s *pininfo,
+                  const char *keyinfo, cache_mode_t cache_mode);
 int agent_get_passphrase (ctrl_t ctrl, char **retpass,
                           const char *desc, const char *prompt,
-                          const char *errtext, int with_qualitybar);
+                          const char *errtext, int with_qualitybar,
+                         const char *keyinfo, cache_mode_t cache_mode);
 int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok,
                            const char *notokay, int with_cancel);
 int agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn);
 int agent_popup_message_start (ctrl_t ctrl,
                                const char *desc, const char *ok_btn);
 void agent_popup_message_stop (ctrl_t ctrl);
+int agent_clear_passphrase (ctrl_t ctrl,
+                           const char *keyinfo, cache_mode_t cache_mode);
 
 
 /*-- cache.c --*/
index c945c13..75e1b23 100644 (file)
@@ -352,6 +352,19 @@ start_pinentry (ctrl_t ctrl)
   if (rc)
     return unlock_pinentry (rc);
 
+
+  /* Indicate to the pinentry that it may read from an external cache.
+
+     It is essential that the pinentry respect this.  If the cached
+     password is not up to date and retry == 1, then, using a version
+     of GPG Agent that doesn't support this, won't issue another pin
+     request and the user won't get a chance to correct the
+     password.  */
+  rc = assuan_transact (entry_ctx, "OPTION allow-external-password-cache",
+                       NULL, NULL, NULL, NULL, NULL, NULL);
+  if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
+    return unlock_pinentry (rc);
+
   value = session_env_getenv (ctrl->session_env, "GPG_TTY");
   if (value)
     {
@@ -399,6 +412,22 @@ start_pinentry (ctrl_t ctrl)
        return unlock_pinentry (rc);
     }
 
+  if (opt.allow_external_cache)
+    {
+      /* Indicate to the pinentry that it may read from an external cache.
+
+         It is essential that the pinentry respect this.  If the
+         cached password is not up to date and retry == 1, then, using
+         a version of GPG Agent that doesn't support this, won't issue
+         another pin request and the user won't get a chance to
+         correct the password.  */
+      rc = assuan_transact (entry_ctx, "OPTION allow-external-password-cache",
+                            NULL, NULL, NULL, NULL, NULL, NULL);
+      if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
+        return unlock_pinentry (rc);
+    }
+
+
   {
     /* Provide a few default strings for use by the pinentries.  This
        may help a pinentry to avoid implementing localization code.  */
@@ -411,6 +440,7 @@ start_pinentry (ctrl_t ctrl)
       { "ok",     N_("|pinentry-label|_OK") },
       { "cancel", N_("|pinentry-label|_Cancel") },
       { "prompt", N_("|pinentry-label|PIN:") },
+      { "pwmngr", N_("|pinentry-label|_Save in password manager") },
       { NULL, NULL}
     };
     char *optstr;
@@ -700,15 +730,36 @@ setup_qualitybar (void)
 }
 
 
+enum
+  {
+    PINENTRY_STATUS_PASSWORD_FROM_CACHE = 1 << 9
+  };
+
+/* Check the button_info line for a close action.  Also check for the
+   PIN_REPEATED flag.  */
+static gpg_error_t
+pinentry_status_cb (void *opaque, const char *line)
+{
+  unsigned int *flag = opaque;
+
+  if (strcmp (line, "PASSWORD_FROM_CACHE") == 0)
+    {
+      *flag |= PINENTRY_STATUS_PASSWORD_FROM_CACHE;
+    }
+
+  return 0;
+}
 \f
 /* Call the Entry and ask for the PIN.  We do check for a valid PIN
    number here and repeat it as long as we have invalid formed
-   numbers. */
+   numbers.  KEYINFO and CACHEMODE are used to tell pinentry something
+   about the key. */
 int
 agent_askpin (ctrl_t ctrl,
               const char *desc_text, const char *prompt_text,
               const char *initial_errtext,
-              struct pin_entry_info_s *pininfo)
+              struct pin_entry_info_s *pininfo,
+              const char *keyinfo, cache_mode_t cache_mode)
 {
   int rc;
   char line[ASSUAN_LINELENGTH];
@@ -716,6 +767,7 @@ agent_askpin (ctrl_t ctrl,
   const char *errtext = NULL;
   int is_pin = 0;
   int saveflag;
+  unsigned int pinentry_status;
 
   if (opt.batch)
     return 0; /* fixme: we should return BAD PIN */
@@ -738,6 +790,25 @@ agent_askpin (ctrl_t ctrl,
   if (rc)
     return rc;
 
+  /* If we have a KEYINFO string and are normal, user, or ssh cache
+     mode, we tell that the Pinentry so it may use it for own caching
+     purposes.  Most pinentries won't have this implemented and thus
+     we do not error out in this case.  */
+  if (keyinfo && (cache_mode == CACHE_MODE_NORMAL
+                  || cache_mode == CACHE_MODE_USER
+                  || cache_mode == CACHE_MODE_SSH))
+    snprintf (line, DIM(line)-1, "SETKEYINFO %c/%s",
+             cache_mode == CACHE_MODE_USER? 'u' :
+             cache_mode == CACHE_MODE_SSH? 's' : 'n',
+             keyinfo);
+  else
+    snprintf (line, DIM(line)-1, "SETKEYINFO --clear");
+
+  rc = assuan_transact (entry_ctx, line,
+                       NULL, NULL, NULL, NULL, NULL, NULL);
+  if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD)
+    return unlock_pinentry (rc);
+
   snprintf (line, DIM(line)-1, "SETDESC %s", desc_text);
   line[DIM(line)-1] = 0;
   rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
@@ -792,11 +863,13 @@ agent_askpin (ctrl_t ctrl,
             return unlock_pinentry (rc);
           errtext = NULL;
         }
-      
+
       saveflag = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL);
       assuan_begin_confidential (entry_ctx);
+      pinentry_status = 0;
       rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, &parm,
-                            inq_quality, entry_ctx, NULL, NULL);
+                            inq_quality, entry_ctx,
+                           pinentry_status_cb, &pinentry_status);
       assuan_set_flag (entry_ctx, ASSUAN_CONFIDENTIAL, saveflag);
       /* Most pinentries out in the wild return the old Assuan error code
          for canceled which gets translated to an assuan Cancel error and
@@ -840,6 +913,11 @@ agent_askpin (ctrl_t ctrl,
 
       if (!errtext)
         return unlock_pinentry (0); /* okay, got a PIN or passphrase */
+
+      if ((pinentry_status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
+       /* The password was read from the cache.  Don't count this
+          against the retry count.  */
+       pininfo->failed_tries --;
     }
 
   return unlock_pinentry (gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN
@@ -849,11 +927,12 @@ agent_askpin (ctrl_t ctrl,
 
 \f
 /* Ask for the passphrase using the supplied arguments.  The returned
-   passphrase needs to be freed by the caller. */
+   passphrase needs to be freed by the caller.  */
 int 
 agent_get_passphrase (ctrl_t ctrl,
                       char **retpass, const char *desc, const char *prompt,
-                      const char *errtext, int with_qualitybar)
+                      const char *errtext, int with_qualitybar,
+                     const char *keyinfo, cache_mode_t cache_mode)
 {
 
   int rc;
@@ -873,6 +952,26 @@ agent_get_passphrase (ctrl_t ctrl,
     prompt = desc && strstr (desc, "PIN")? "PIN": _("Passphrase");
 
 
+  /* If we have a KEYINFO string and are normal, user, or ssh cache
+     mode, we tell that the Pinentry so it may use it for own caching
+     purposes.  Most pinentries won't have this implemented and thus
+     we do not error out in this case.  */
+  if (keyinfo && (cache_mode == CACHE_MODE_NORMAL
+                  || cache_mode == CACHE_MODE_USER
+                  || cache_mode == CACHE_MODE_SSH))
+    snprintf (line, DIM(line)-1, "SETKEYINFO %c/%s",
+             cache_mode == CACHE_MODE_USER? 'u' :
+             cache_mode == CACHE_MODE_SSH? 's' : 'n',
+             keyinfo);
+  else
+    snprintf (line, DIM(line)-1, "SETKEYINFO --clear");
+
+  rc = assuan_transact (entry_ctx, line,
+                       NULL, NULL, NULL, NULL, NULL, NULL);
+  if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD)
+    return unlock_pinentry (rc);
+
+
   if (desc)
     snprintf (line, DIM(line)-1, "SETDESC %s", desc);
   else
@@ -1185,3 +1284,28 @@ agent_popup_message_stop (ctrl_t ctrl)
 }
 
 
+int
+agent_clear_passphrase (ctrl_t ctrl,
+                       const char *keyinfo, cache_mode_t cache_mode)
+{
+  int rc;
+  char line[ASSUAN_LINELENGTH];
+
+  if (! (keyinfo && (cache_mode == CACHE_MODE_NORMAL
+                    || cache_mode == CACHE_MODE_USER
+                    || cache_mode == CACHE_MODE_SSH)))
+    return gpg_error (GPG_ERR_NOT_SUPPORTED);
+
+  rc = start_pinentry (ctrl);
+  if (rc)
+    return rc;
+
+  snprintf (line, DIM(line)-1, "CLEARPASSPHRASE %c/%s",
+           cache_mode == CACHE_MODE_USER? 'u' :
+           cache_mode == CACHE_MODE_SSH? 's' : 'n',
+           keyinfo);
+  rc = assuan_transact (entry_ctx, line,
+                       NULL, NULL, NULL, NULL, NULL, NULL);
+
+  return unlock_pinentry (rc);
+}
index ea6080a..2aacecc 100644 (file)
@@ -2881,7 +2881,7 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl, int confirm)
   pi2->check_cb_arg = pi->pin;
 
  next_try:
-  err = agent_askpin (ctrl, description, NULL, initial_errtext, pi);
+  err = agent_askpin (ctrl, description, NULL, initial_errtext, pi, NULL, 0);
   initial_errtext = NULL;
   if (err)
     goto out;
@@ -2889,7 +2889,7 @@ ssh_identity_register (ctrl_t ctrl, gcry_sexp_t key, int ttl, int confirm)
   /* Unless the passphrase is empty, ask to confirm it.  */
   if (pi->pin && *pi->pin)
     {
-      err = agent_askpin (ctrl, description2, NULL, NULL, pi2);
+      err = agent_askpin (ctrl, description2, NULL, NULL, pi2, NULL, 0);
       if (err == -1)
        { /* The re-entered one did not match and the user did not
             hit cancel. */
index 2405c54..765f916 100644 (file)
@@ -1269,8 +1269,8 @@ cmd_get_passphrase (assuan_context_t ctx, char *line)
 
     next_try:
       rc = agent_get_passphrase (ctrl, &response, desc, prompt,
-                                 repeat_errtext? repeat_errtext:errtext,
-                                 opt_qualbar);
+                                repeat_errtext? repeat_errtext:errtext,
+                                opt_qualbar, cacheid, CACHE_MODE_USER);
       xfree (repeat_errtext);
       repeat_errtext = NULL;
       if (!rc)
@@ -1287,7 +1287,8 @@ cmd_get_passphrase (assuan_context_t ctx, char *line)
               char *response2;
 
               rc = agent_get_passphrase (ctrl, &response2, desc2, prompt,
-                                         errtext, 0);
+                                         errtext, 0,
+                                        cacheid, CACHE_MODE_USER);
               if (rc)
                 break;
               if (strcmp (response2, response))
@@ -1329,6 +1330,7 @@ static const char hlp_clear_passphrase[] =
 static gpg_error_t
 cmd_clear_passphrase (assuan_context_t ctx, char *line)
 {
+  ctrl_t ctrl = assuan_get_pointer (ctx);
   char *cacheid = NULL;
   char *p;
 
@@ -1343,6 +1345,9 @@ cmd_clear_passphrase (assuan_context_t ctx, char *line)
     return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
 
   agent_put_cache (cacheid, CACHE_MODE_USER, NULL, 0);
+
+  agent_clear_passphrase (ctrl, cacheid, CACHE_MODE_USER);
+
   return 0;
 }
 
index 1f36f6e..34ef498 100644 (file)
@@ -266,7 +266,7 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf)
 
   if (any_flags)
     {
-      rc = agent_askpin (ctrl, info, prompt, again_text, pi);
+      rc = agent_askpin (ctrl, info, prompt, again_text, pi, NULL, 0);
       again_text = NULL;
       if (!rc && newpin)
         {
@@ -288,7 +288,7 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf)
                               is_puk?
                               _("Repeat this PUK"):
                               _("Repeat this PIN")),
-                             prompt, NULL, pi2);
+                             prompt, NULL, pi2, NULL, 0);
           if (!rc && strcmp (pi->pin, pi2->pin))
             {
               again_text = (resetcode? 
@@ -312,7 +312,7 @@ getpin_cb (void *opaque, const char *info, char *buf, size_t maxbuf)
                      info? info:"",
                      info? "')":"") < 0)
         desc = NULL;
-      rc = agent_askpin (ctrl, desc?desc:info, prompt, NULL, pi);
+      rc = agent_askpin (ctrl, desc?desc:info, prompt, NULL, pi, NULL, 0);
       xfree (desc);
     }
 
index 550e403..6d85cfd 100644 (file)
@@ -393,7 +393,7 @@ unprotect (ctrl_t ctrl, const char *desc_text,
   arg.change_required = 0;
   pi->check_cb_arg = &arg;
 
-  rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi);
+  rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi, hexgrip, cache_mode);
   if (!rc)
     {
       assert (arg.unprotected_key);
index d5af9e0..65477ad 100644 (file)
@@ -321,7 +321,7 @@ agent_genkey (ctrl_t ctrl, const char *keyparam, size_t keyparamlen,
     pi2->check_cb_arg = pi->pin;
 
   next_try:
-    rc = agent_askpin (ctrl, text1, NULL, initial_errtext, pi);
+    rc = agent_askpin (ctrl, text1, NULL, initial_errtext, pi, NULL, 0);
     initial_errtext = NULL;
     if (!rc)
       {
@@ -333,7 +333,7 @@ agent_genkey (ctrl_t ctrl, const char *keyparam, size_t keyparamlen,
           }
         if (pi->pin && *pi->pin)
           {
-            rc = agent_askpin (ctrl, text2, NULL, NULL, pi2);
+            rc = agent_askpin (ctrl, text2, NULL, NULL, pi2, NULL, 0);
             if (rc == -1)
               { /* The re-entered one did not match and the user did not
                    hit cancel. */
@@ -443,7 +443,7 @@ agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey)
     pi2->check_cb_arg = pi->pin;
 
   next_try:
-    rc = agent_askpin (ctrl, text1, NULL, initial_errtext, pi);
+    rc = agent_askpin (ctrl, text1, NULL, initial_errtext, pi, NULL, 0);
     initial_errtext = NULL;
     if (!rc)
       {
@@ -456,7 +456,7 @@ agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey)
         /* Unless the passphrase is empty, ask to confirm it.  */
         if (pi->pin && *pi->pin)
           {
-            rc = agent_askpin (ctrl, text2, NULL, NULL, pi2);
+            rc = agent_askpin (ctrl, text2, NULL, NULL, pi2, NULL, 0);
             if (rc == -1)
               { /* The re-entered one did not match and the user did not
                    hit cancel. */
index bf2a26d..479f918 100644 (file)
@@ -114,6 +114,7 @@ enum cmd_and_opt_values
   oAllowMarkTrusted,
   oNoAllowMarkTrusted,
   oAllowPresetPassphrase,
+  oNoAllowExternalCache,
   oKeepTTY,
   oKeepDISPLAY,
   oSSHSupport,
@@ -198,6 +199,8 @@ static ARGPARSE_OPTS opts[] = {
       "@"
 #endif
   },
+  { oNoAllowExternalCache, "no-allow-external-cache", 0,
+            N_("disallow the use of an external password cache") },
   { oWriteEnvFile, "write-env-file", 2|8,
             N_("|FILE|write environment settings also to FILE")},
   {0}
@@ -509,6 +512,7 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
       opt.ignore_cache_for_signing = 0;
       opt.allow_mark_trusted = 1;
       opt.disable_scdaemon = 0;
+      opt.allow_external_cache = 1;
       return 1;
     }
 
@@ -571,6 +575,9 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread)
 
     case oAllowPresetPassphrase: opt.allow_preset_passphrase = 1; break;
 
+    case oNoAllowExternalCache: opt.allow_external_cache = 0;
+      break;
+
     default:
       return 0; /* not handled */
     }
@@ -969,6 +976,8 @@ main (int argc, char **argv )
               GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
       printf ("no-allow-mark-trusted:%lu:\n",
               GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
+      printf ("no-allow-external-cache:%lu:\n",
+              GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
       printf ("disable-scdaemon:%lu:\n",
               GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME);
 #ifdef HAVE_W32_SYSTEM
index c3dfd82..5c0dec7 100644 (file)
@@ -352,6 +352,19 @@ Allow clients to use the loopback pinentry features; see the option
 @option{pinentry-mode} for details.
 @end ifset
 
+@ifset gpgtwoone
+@item --no-allow-external-cache
+@opindex no-allow-external-cache
+Tell Pinentry not to enable features which use an external cache for
+passphrases.
+
+Some desktop environments prefer to unlock all
+credentials with one master password and may have installed a Pinentry
+which employs an additional external cache to implement such a policy.
+By using this option the Pinentry is advised not to make use of such a
+cache and instead always ask the user for the requested passphrase.
+@end ifset
+
 @item --ignore-cache-for-signing
 @opindex ignore-cache-for-signing
 This option will let @command{gpg-agent} bypass the passphrase cache for all
@@ -713,6 +726,7 @@ again.  Only certain options are honored: @code{quiet},
 @code{verbose}, @code{debug}, @code{debug-all}, @code{debug-level},
 @code{no-grab}, @code{pinentry-program}, @code{default-cache-ttl},
 @code{max-cache-ttl}, @code{ignore-cache-for-signing},
+@code{no-allow-external-cache},
 @code{allow-mark-trusted}, @code{disable-scdaemon}, and
 @code{disable-check-own-socket}.  @code{scdaemon-program} is also
 supported but due to the current implementation, which calls the
index 4993989..2454f93 100644 (file)
@@ -532,6 +532,9 @@ static gc_option_t gc_options_gpg_agent[] =
    { "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME,
      GC_LEVEL_BASIC, "gnupg", "do not use the PIN cache when signing",
      GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
+   { "no-allow-external-cache", GC_OPT_FLAG_RUNTIME,
+     GC_LEVEL_BASIC, "gnupg", "disallow the use of an external password cache",
+     GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },
    { "no-allow-mark-trusted", GC_OPT_FLAG_RUNTIME,
      GC_LEVEL_ADVANCED, "gnupg", "disallow clients to mark keys as \"trusted\"",
      GC_ARG_TYPE_NONE, GC_BACKEND_GPG_AGENT },