From dde8ddffd37c9ef96cae2e2b1317d1dee607fc0b Mon Sep 17 00:00:00 2001 From: "Neal H. Walfield" Date: Tue, 19 May 2015 13:53:43 +0200 Subject: [PATCH] agent: Backport changes from 2.1 to support an external password manager. * 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 Based on commits: 3a9305439b75ccd4446378d4fd87da087fd9c892 e201c20f25e7bed29088186c5f717d43047a0f4b d7293cb317acc40cc9e5189cef33fe9d8b47e62a 56b5c9f94f2e55d096be585ed061ccf1c9ec0de6 d3b5cad2346bd5747789dc62d7804fa5c15f4f3b 2180845959839705200e3172dbafc94b70b9007f --- agent/agent.h | 14 ++++- agent/call-pinentry.c | 136 ++++++++++++++++++++++++++++++++++++++++-- agent/command-ssh.c | 4 +- agent/command.c | 11 +++- agent/divert-scd.c | 6 +- agent/findkey.c | 2 +- agent/genkey.c | 8 +-- agent/gpg-agent.c | 9 +++ doc/gpg-agent.texi | 14 +++++ tools/gpgconf-comp.c | 3 + 10 files changed, 186 insertions(+), 21 deletions(-) diff --git a/agent/agent.h b/agent/agent.h index 938a9aa0f..f81743f2f 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -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 --*/ diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index c945c1305..75e1b238c 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -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; +} /* 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, /* 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); +} diff --git a/agent/command-ssh.c b/agent/command-ssh.c index ea6080ae7..2aacecc46 100644 --- a/agent/command-ssh.c +++ b/agent/command-ssh.c @@ -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. */ diff --git a/agent/command.c b/agent/command.c index 2405c54e9..765f916b3 100644 --- a/agent/command.c +++ b/agent/command.c @@ -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; } diff --git a/agent/divert-scd.c b/agent/divert-scd.c index 1f36f6e98..34ef498fc 100644 --- a/agent/divert-scd.c +++ b/agent/divert-scd.c @@ -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); } diff --git a/agent/findkey.c b/agent/findkey.c index 550e40319..6d85cfdf7 100644 --- a/agent/findkey.c +++ b/agent/findkey.c @@ -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); diff --git a/agent/genkey.c b/agent/genkey.c index d5af9e06d..65477adf2 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -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. */ diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index bf2a26d47..479f91802 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -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 diff --git a/doc/gpg-agent.texi b/doc/gpg-agent.texi index c3dfd82b7..5c0dec773 100644 --- a/doc/gpg-agent.texi +++ b/doc/gpg-agent.texi @@ -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 diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c index 499398943..2454f9326 100644 --- a/tools/gpgconf-comp.c +++ b/tools/gpgconf-comp.c @@ -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 },