From eace4bbe1ded8b01f9ad52ebc1871f2fd13c3a08 Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Wed, 8 Jul 2020 14:20:01 +0200 Subject: [PATCH] agent: New option --newsymkey for GET_PASSPHRASE * agent/call-pinentry.c (agent_get_passphrase): Add arg pininfo. * agent/genkey.c (check_passphrase_constraints): New arg no_empty. * agent/command.c (reenter_passphrase_cmp_cb): New. (cmd_get_passphrase): Add option --newsymkey. -- This new option allows to present a passphrase with the usual repeat box as it is used by gpg-agent's internal key generation. Signed-off-by: Werner Koch --- agent/agent.h | 5 +- agent/call-pinentry.c | 197 ++++++++++++++++++++++++++++++++++++------ agent/command.c | 170 ++++++++++++++++++++++++++++++------ agent/genkey.c | 8 +- 4 files changed, 322 insertions(+), 58 deletions(-) diff --git a/agent/agent.h b/agent/agent.h index 9c84f6a17..7ef0ffca7 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -478,7 +478,8 @@ gpg_error_t agent_askpin (ctrl_t ctrl, int agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *desc, const char *prompt, const char *errtext, int with_qualitybar, - const char *keyinfo, cache_mode_t cache_mode); + const char *keyinfo, cache_mode_t cache_mode, + struct pin_entry_info_s *pininfo); 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); @@ -515,7 +516,7 @@ int agent_pkdecrypt (ctrl_t ctrl, const char *desc_text, membuf_t *outbuf, int *r_padding); /*-- genkey.c --*/ -int check_passphrase_constraints (ctrl_t ctrl, const char *pw, +int check_passphrase_constraints (ctrl_t ctrl, const char *pw, int no_empty, char **failed_constraint); gpg_error_t agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt, char **r_passphrase); diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index 658be4611..89249b6e8 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -849,7 +849,7 @@ inq_quality (void *opaque, const char *line) else { percent = estimate_passphrase_quality (pin); - if (check_passphrase_constraints (NULL, pin, NULL)) + if (check_passphrase_constraints (NULL, pin, 0, NULL)) percent = -percent; snprintf (numbuf, sizeof numbuf, "%d", percent); rc = assuan_send_data (ctx, numbuf, strlen (numbuf)); @@ -1301,9 +1301,11 @@ agent_askpin (ctrl_t ctrl, } if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) - /* The password was read from the cache. Don't count this - against the retry count. */ - pininfo->failed_tries --; + { + /* The password was read from the cache. Don't count this + against the retry count. */ + pininfo->failed_tries --; + } } return unlock_pinentry (ctrl, gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN @@ -1313,14 +1315,20 @@ 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. PININFO is optional + and can be used to have constraints checinkg while the pinentry + dialog is open (like what we do in agent_askpin). This is very + similar to agent_akpin and we should eventually merge the two + functions. */ int agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *desc, const char *prompt, const char *errtext, int with_qualitybar, - const char *keyinfo, cache_mode_t cache_mode) + const char *keyinfo, cache_mode_t cache_mode, + struct pin_entry_info_s *pininfo) { int rc; + int is_pin; char line[ASSUAN_LINELENGTH]; struct entry_parm_s parm; @@ -1330,17 +1338,42 @@ agent_get_passphrase (ctrl_t ctrl, if (ctrl->pinentry_mode != PINENTRY_MODE_ASK) { + unsigned char *passphrase; + size_t size; + if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL) return gpg_error (GPG_ERR_CANCELED); - if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) + if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK && pininfo) { - size_t size; + *pininfo->pin = 0; /* Reset the PIN. */ + rc = pinentry_loopback (ctrl, "PASSPHRASE", + &passphrase, &size, + pininfo->max_length - 1); + if (rc) + return rc; + memcpy (&pininfo->pin, passphrase, size); + wipememory (passphrase, size); + xfree (passphrase); + pininfo->pin[size] = 0; + if (pininfo->check_cb) + { + /* More checks by utilizing the optional callback. */ + pininfo->cb_errtext = NULL; + rc = pininfo->check_cb (pininfo); + } + return rc; + + } + else if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) + { + /* Legacy variant w/o PININFO. */ return pinentry_loopback (ctrl, "PASSPHRASE", (unsigned char **)retpass, &size, MAX_PASSPHRASE_LEN); } + return gpg_error (GPG_ERR_NO_PIN_ENTRY); } @@ -1348,9 +1381,14 @@ agent_get_passphrase (ctrl_t ctrl, if (rc) return rc; - if (!prompt) - prompt = desc && strstr (desc, "PIN")? L_("PIN:"): L_("Passphrase:"); - + /* Set IS_PIN and if needed a default prompt. */ + if (prompt) + is_pin = !!strstr (prompt, "PIN"); + else + { + is_pin = desc && strstr (desc, "PIN"); + prompt = is_pin? L_("PIN:"): L_("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 @@ -1371,7 +1409,6 @@ agent_get_passphrase (ctrl_t ctrl, if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD) return unlock_pinentry (ctrl, rc); - if (desc) build_cmd_setdesc (line, DIM(line), desc); else @@ -1385,7 +1422,8 @@ agent_get_passphrase (ctrl_t ctrl, if (rc) return unlock_pinentry (ctrl, rc); - if (with_qualitybar && opt.min_passphrase_len) + if ((with_qualitybar || (pininfo && pininfo->with_qualitybar)) + && opt.min_passphrase_len) { rc = setup_qualitybar (ctrl); if (rc) @@ -1395,23 +1433,132 @@ agent_get_passphrase (ctrl_t ctrl, if (errtext) { snprintf (line, DIM(line), "SETERROR %s", errtext); - rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); if (rc) return unlock_pinentry (ctrl, rc); } - memset (&parm, 0, sizeof parm); - parm.size = ASSUAN_LINELENGTH/2 - 5; - parm.buffer = gcry_malloc_secure (parm.size+10); - if (!parm.buffer) - return unlock_pinentry (ctrl, out_of_core ()); + if (!pininfo) + { + /* Legacy method without PININFO. */ + memset (&parm, 0, sizeof parm); + parm.size = ASSUAN_LINELENGTH/2 - 5; + parm.buffer = gcry_malloc_secure (parm.size+10); + if (!parm.buffer) + return unlock_pinentry (ctrl, out_of_core ()); - rc = do_getpin (ctrl, &parm); - if (rc) - xfree (parm.buffer); - else - *retpass = parm.buffer; - return unlock_pinentry (ctrl, rc); + rc = do_getpin (ctrl, &parm); + if (rc) + xfree (parm.buffer); + else + *retpass = parm.buffer; + return unlock_pinentry (ctrl, rc); + } + + /* We got PININFO. */ + + if (pininfo->with_repeat) + { + snprintf (line, DIM(line), "SETREPEATERROR %s", + L_("does not match - try again")); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + pininfo->with_repeat = 0; /* Pinentry does not support it. */ + } + pininfo->repeat_okay = 0; + pininfo->status = 0; + + for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++) + { + memset (&parm, 0, sizeof parm); + parm.size = pininfo->max_length; + parm.buffer = (unsigned char*)pininfo->pin; + *pininfo->pin = 0; /* Reset the PIN. */ + + if (errtext) + { + /* TRANSLATORS: The string is appended to an error message in + the pinentry. The %s is the actual error message, the + two %d give the current and maximum number of tries. */ + snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"), + errtext, pininfo->failed_tries+1, pininfo->max_tries); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + errtext = NULL; + } + + if (pininfo->with_repeat) + { + snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:")); + rc = assuan_transact (entry_ctx, line, + NULL, NULL, NULL, NULL, NULL, NULL); + if (rc) + return unlock_pinentry (ctrl, rc); + } + + rc = do_getpin (ctrl, &parm); + pininfo->status = parm.status; + if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA) + errtext = is_pin? L_("PIN too long") + : L_("Passphrase too long"); + else if (rc) + return unlock_pinentry (ctrl, rc); + + if (!errtext && pininfo->min_digits) + { + /* do some basic checks on the entered PIN. */ + if (!all_digitsp (pininfo->pin)) + errtext = L_("Invalid characters in PIN"); + else if (pininfo->max_digits + && strlen (pininfo->pin) > pininfo->max_digits) + errtext = L_("PIN too long"); + else if (strlen (pininfo->pin) < pininfo->min_digits) + errtext = L_("PIN too short"); + } + + if (!errtext && pininfo->check_cb) + { + /* More checks by utilizing the optional callback. */ + pininfo->cb_errtext = NULL; + rc = pininfo->check_cb (pininfo); + /* When pinentry cache causes an error, return now. */ + if (rc && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) + return unlock_pinentry (ctrl, rc); + + if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) + { + if (pininfo->cb_errtext) + errtext = pininfo->cb_errtext; + else if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE + || gpg_err_code (rc) == GPG_ERR_BAD_PIN) + errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase")); + } + else if (rc) + return unlock_pinentry (ctrl, rc); + } + + if (!errtext) + { + if (pininfo->with_repeat + && (pininfo->status & PINENTRY_STATUS_PIN_REPEATED)) + pininfo->repeat_okay = 1; + return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */ + } + + if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE)) + { + /* The password was read from the Pinentry's own cache. + Don't count this against the retry count. */ + pininfo->failed_tries--; + } + } + + return unlock_pinentry (ctrl, gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN + : GPG_ERR_BAD_PASSPHRASE)); } diff --git a/agent/command.c b/agent/command.c index 689507bbc..f54762b44 100644 --- a/agent/command.c +++ b/agent/command.c @@ -1543,9 +1543,22 @@ send_back_passphrase (assuan_context_t ctx, int via_data, const char *pw) } +/* Callback function to compare the first entered PIN with the one + currently being entered. */ +static gpg_error_t +reenter_passphrase_cmp_cb (struct pin_entry_info_s *pi) +{ + const char *pin1 = pi->check_cb_arg; + + if (!strcmp (pin1, pi->pin)) + return 0; /* okay */ + return gpg_error (GPG_ERR_BAD_PASSPHRASE); +} + + static const char hlp_get_passphrase[] = "GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]]\n" - " [--qualitybar] \n" + " [--qualitybar] [--newsymkey] \n" " [ ]\n" "\n" "This function is usually used to ask for a passphrase to be used\n" @@ -1567,6 +1580,9 @@ static const char hlp_get_passphrase[] = "cache the user will not be asked to enter a passphrase but the error\n" "code GPG_ERR_NO_DATA is returned. \n" "\n" + "If the option\"--newsymkey\" is used the agent asks for a new passphrase\n" + "to be used in symmetric-only encryption. This must not be empty.\n" + "\n" "If the option \"--qualitybar\" is used a visual indication of the\n" "entered passphrase quality is shown. (Unless no minimum passphrase\n" "length has been configured.)"; @@ -1576,13 +1592,19 @@ cmd_get_passphrase (assuan_context_t ctx, char *line) ctrl_t ctrl = assuan_get_pointer (ctx); int rc; char *pw; - char *response; - char *cacheid = NULL, *desc = NULL, *prompt = NULL, *errtext = NULL; + char *response = NULL; + char *response2 = NULL; + char *cacheid = NULL; /* May point into LINE. */ + char *desc = NULL; /* Ditto */ + char *prompt = NULL; /* Ditto */ + char *errtext = NULL; /* Ditto */ const char *desc2 = _("Please re-enter this passphrase"); char *p; - int opt_data, opt_check, opt_no_ask, opt_qualbar; + int opt_data, opt_check, opt_no_ask, opt_qualbar, opt_newsymkey; int opt_repeat = 0; char *entry_errtext = NULL; + struct pin_entry_info_s *pi = NULL; + struct pin_entry_info_s *pi2 = NULL; if (ctrl->restricted) return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN)); @@ -1599,6 +1621,7 @@ cmd_get_passphrase (assuan_context_t ctx, char *line) opt_repeat = 1; } opt_qualbar = has_option (line, "--qualitybar"); + opt_newsymkey = has_option (line, "--newsymkey"); line = skip_options (line); cacheid = line; @@ -1648,26 +1671,116 @@ cmd_get_passphrase (assuan_context_t ctx, char *line) { rc = send_back_passphrase (ctx, opt_data, pw); xfree (pw); + goto leave; } else if (opt_no_ask) - rc = gpg_error (GPG_ERR_NO_DATA); + { + rc = gpg_error (GPG_ERR_NO_DATA); + goto leave; + } + + /* Note, that we only need to replace the + characters and should + * leave the other escaping in place because the escaped string is + * send verbatim to the pinentry which does the unescaping (but not + * the + replacing) */ + if (errtext) + plus_to_blank (errtext); + if (prompt) + plus_to_blank (prompt); + if (desc) + plus_to_blank (desc); + + if (opt_newsymkey) + { + /* We do not want to break any existing usage of this command + * and thus we introduced the option --newsymkey to make this + * command more useful to query the passphrase for symmetric + * encryption. */ + pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1); + if (!pi) + { + rc = gpg_error_from_syserror (); + goto leave; + } + pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1); + if (!pi2) + { + rc = gpg_error_from_syserror (); + goto leave; + } + pi->max_length = MAX_PASSPHRASE_LEN + 1; + pi->max_tries = 3; + pi->with_qualitybar = opt_qualbar; + pi->with_repeat = opt_repeat; + pi2->max_length = MAX_PASSPHRASE_LEN + 1; + pi2->max_tries = 3; + pi2->check_cb = reenter_passphrase_cmp_cb; + pi2->check_cb_arg = pi->pin; + + for (;;) /* (degenerated for-loop) */ + { + xfree (response); + response = NULL; + rc = agent_get_passphrase (ctrl, &response, + desc, + prompt, + entry_errtext? entry_errtext:errtext, + opt_qualbar, cacheid, CACHE_MODE_USER, + pi); + if (rc) + goto leave; + xfree (entry_errtext); + entry_errtext = NULL; + /* We don't allow an empty passpharse in this mode. */ + if (check_passphrase_constraints (ctrl, pi->pin, 1, &entry_errtext)) + { + pi->failed_tries = 0; + pi2->failed_tries = 0; + continue; + } + if (*pi->pin && !pi->repeat_okay) + { + /* The passphrase is empty and the pinentry did not + * already run the repetition check, do it here. This + * is only called when using an old and simple pinentry. */ + xfree (response); + response = NULL; + rc = agent_get_passphrase (ctrl, &response, + L_("Please re-enter this passphrase"), + prompt, + entry_errtext? entry_errtext:errtext, + opt_qualbar, cacheid, CACHE_MODE_USER, + pi2); + if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE) + { /* The re-entered passphrase one did not match and + * the user did not hit cancel. */ + entry_errtext = xtrystrdup (L_("does not match - try again")); + if (!entry_errtext) + { + rc = gpg_error_from_syserror (); + goto leave; + } + continue; + } + } + break; + } + if (!rc && *pi->pin) + { + /* Return the passphrase. */ + if (cacheid) + agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, pi->pin, 0); + rc = send_back_passphrase (ctx, opt_data, pi->pin); + } + } else { - /* Note, that we only need to replace the + characters and - should leave the other escaping in place because the escaped - string is send verbatim to the pinentry which does the - unescaping (but not the + replacing) */ - if (errtext) - plus_to_blank (errtext); - if (prompt) - plus_to_blank (prompt); - if (desc) - plus_to_blank (desc); - next_try: + xfree (response); + response = NULL; rc = agent_get_passphrase (ctrl, &response, desc, prompt, entry_errtext? entry_errtext:errtext, - opt_qualbar, cacheid, CACHE_MODE_USER); + opt_qualbar, cacheid, CACHE_MODE_USER, NULL); xfree (entry_errtext); entry_errtext = NULL; if (!rc) @@ -1675,27 +1788,24 @@ cmd_get_passphrase (assuan_context_t ctx, char *line) int i; if (opt_check - && check_passphrase_constraints (ctrl, response, &entry_errtext)) + && check_passphrase_constraints (ctrl, response,0,&entry_errtext)) { - xfree (response); goto next_try; } for (i = 0; i < opt_repeat; i++) { - char *response2; - if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK) break; + xfree (response2); + response2 = NULL; rc = agent_get_passphrase (ctrl, &response2, desc2, prompt, errtext, 0, - cacheid, CACHE_MODE_USER); + cacheid, CACHE_MODE_USER, NULL); if (rc) break; if (strcmp (response2, response)) { - xfree (response2); - xfree (response); entry_errtext = try_percent_escape (_("does not match - try again"), NULL); if (!entry_errtext) @@ -1705,7 +1815,6 @@ cmd_get_passphrase (assuan_context_t ctx, char *line) } goto next_try; } - xfree (response2); } if (!rc) { @@ -1713,10 +1822,15 @@ cmd_get_passphrase (assuan_context_t ctx, char *line) agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, response, 0); rc = send_back_passphrase (ctx, opt_data, response); } - xfree (response); } } + leave: + xfree (response); + xfree (response2); + xfree (entry_errtext); + xfree (pi2); + xfree (pi); return leave_cmd (ctx, rc); } @@ -3555,7 +3669,9 @@ command_has_option (const char *cmd, const char *cmdopt) if (!strcmp (cmd, "GET_PASSPHRASE")) { if (!strcmp (cmdopt, "repeat")) - return 1; + return 1; + if (!strcmp (cmdopt, "newsymkey")) + return 1; } return 0; diff --git a/agent/genkey.c b/agent/genkey.c index 0d2038016..6f863db95 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -161,7 +161,7 @@ take_this_one_anyway (ctrl_t ctrl, const char *desc, const char *anyway_btn) message describing the problem is returned in *FAILED_CONSTRAINT. */ int -check_passphrase_constraints (ctrl_t ctrl, const char *pw, +check_passphrase_constraints (ctrl_t ctrl, const char *pw, int no_empty, char **failed_constraint) { gpg_error_t err = 0; @@ -180,7 +180,7 @@ check_passphrase_constraints (ctrl_t ctrl, const char *pw, /* The first check is to warn about an empty passphrase. */ if (!*pw) { - const char *desc = (opt.enforce_passphrase_constraints? + const char *desc = (opt.enforce_passphrase_constraints || no_empty? L_("You have not entered a passphrase!%0A" "An empty passphrase is not allowed.") : L_("You have not entered a passphrase - " @@ -191,7 +191,7 @@ check_passphrase_constraints (ctrl_t ctrl, const char *pw, err = 1; if (failed_constraint) { - if (opt.enforce_passphrase_constraints) + if (opt.enforce_passphrase_constraints || no_empty) *failed_constraint = xstrdup (desc); else err = take_this_one_anyway (ctrl, desc, @@ -381,7 +381,7 @@ agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt, initial_errtext = NULL; if (!err) { - if (check_passphrase_constraints (ctrl, pi->pin, &initial_errtext)) + if (check_passphrase_constraints (ctrl, pi->pin, 0, &initial_errtext)) { pi->failed_tries = 0; pi2->failed_tries = 0;