diff options
Diffstat (limited to '')
-rw-r--r-- | agent/ChangeLog | 19 | ||||
-rw-r--r-- | agent/agent.h | 8 | ||||
-rw-r--r-- | agent/call-pinentry.c | 52 | ||||
-rw-r--r-- | agent/genkey.c | 156 | ||||
-rw-r--r-- | agent/gpg-agent.c | 28 | ||||
-rw-r--r-- | agent/trustlist.c | 72 |
6 files changed, 311 insertions, 24 deletions
diff --git a/agent/ChangeLog b/agent/ChangeLog index 4c7df8b6d..4b8d5d3b1 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,22 @@ +2007-08-27 Werner Koch <[email protected]> + + * gpg-agent.c: Add options --min-passphrase-nonalpha, + --check-passphrase-pattern and --enforce-passphrase-constraints. + (MIN_PASSPHRASE_NONALPHA): Init nonalpha option to 1. + (main): Declare options for gpgconf. + * agent.h (struct): Add members MIN_PASSPHRASE_NONALPHA, + ENFORCE_PASSPHRASE_CONSTRAINTS and CHECK_PASSPHRASE_PATTERN. + * genkey.c (nonalpha_charcount): New. + (check_passphrase_pattern): New. + (check_passphrase_constraints): Implement. Factor some code out... + (take_this_one_anyway, take_this_one_anyway2): .. New. + + * call-pinentry.c (agent_show_message): New. + (agent_askpin): We better reset the pin buffer before asking. + + * trustlist.c (insert_colons): New. + (agent_marktrusted): Pretty print the fpr. + 2007-08-22 Werner Koch <[email protected]> * findkey.c (O_BINARY): Make sure it is defined. diff --git a/agent/agent.h b/agent/agent.h index 22225f6a2..8531f395a 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -80,8 +80,15 @@ struct unsigned long max_cache_ttl; /* Default. */ unsigned long max_cache_ttl_ssh; /* for SSH. */ + /* Flag disallowin bypassing of the warning. */ + int enforce_passphrase_constraints; /* The require minmum length of a passphrase. */ unsigned int min_passphrase_len; + /* The minimum number of non-alpha characters in a passphrase. */ + unsigned int min_passphrase_nonalpha; + /* File name with a patternfile or NULL if not enabled. */ + const char *check_passphrase_pattern; + int running_detached; /* We are running detached from the tty. */ @@ -227,6 +234,7 @@ int agent_get_passphrase (ctrl_t ctrl, char **retpass, const char *errtext); int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok, const char *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); diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c index 4631eadea..f29d3c4af 100644 --- a/agent/call-pinentry.c +++ b/agent/call-pinentry.c @@ -213,7 +213,9 @@ start_pinentry (ctrl_t ctrl) #endif if (fflush (NULL)) { +#ifndef HAVE_W32_SYSTEM gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno)); +#endif log_error ("error flushing pending output: %s\n", strerror (errno)); /* At least Windows XP fails here with EBADF. According to docs and Wine an fflush(NULL) is the same as _flushall. However @@ -476,6 +478,7 @@ agent_askpin (ctrl_t ctrl, { memset (&parm, 0, sizeof parm); parm.size = pininfo->max_length; + *pininfo->pin = 0; /* Reset the PIN. */ parm.buffer = (unsigned char*)pininfo->pin; if (errtext) @@ -671,6 +674,55 @@ agent_get_confirmation (ctrl_t ctrl, } + +/* Pop up the PINentry, display the text DESC and a button with the + text OK_BTN (which may be NULL to use the default of "OK") and waut + for the user to hit this button. The return value is not + relevant. */ +int +agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn) +{ + int rc; + char line[ASSUAN_LINELENGTH]; + + rc = start_pinentry (ctrl); + if (rc) + return rc; + + if (desc) + snprintf (line, DIM(line)-1, "SETDESC %s", desc); + else + snprintf (line, DIM(line)-1, "RESET"); + line[DIM(line)-1] = 0; + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL); + /* Most pinentries out in the wild return the old Assuan error code + for canceled which gets translated to an assuan Cancel error and + not to the code for a user cancel. Fix this here. */ + if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); + + if (rc) + return unlock_pinentry (rc); + + if (ok_btn) + { + snprintf (line, DIM(line)-1, "SETOK %s", ok_btn); + line[DIM(line)-1] = 0; + rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, + NULL, NULL, NULL); + if (rc) + return unlock_pinentry (rc); + } + + rc = assuan_transact (entry_ctx, "CONFIRM --one-button", NULL, NULL, NULL, + NULL, NULL, NULL); + if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED) + rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED); + + return unlock_pinentry (rc); +} + + /* The thread running the popup message. */ static void * popup_message_thread (void *arg) diff --git a/agent/genkey.c b/agent/genkey.c index e160f453f..09cd9f738 100644 --- a/agent/genkey.c +++ b/agent/genkey.c @@ -27,6 +27,8 @@ #include "agent.h" #include "i18n.h" +#include "exechelp.h" +#include "sysutils.h" static int store_key (gcry_sexp_t private, const char *passphrase, int force) @@ -70,6 +72,100 @@ store_key (gcry_sexp_t private, const char *passphrase, int force) } +/* Count the number of non-alpha characters in S. Control characters + and non-ascii characters are not considered. */ +static size_t +nonalpha_count (const char *s) +{ + size_t n; + + for (n=0; *s; s++) + if (isascii (*s) && ( isdigit (*s) || ispunct (*s) )) + n++; + + return n; +} + + +/* Check PW against a list of pattern. Return 0 if PW does not match + these pattern. */ +static int +check_passphrase_pattern (ctrl_t ctrl, const char *pw) +{ + gpg_error_t err = 0; + const char *pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CHECK_PATTERN); + FILE *infp; + const char *argv[10]; + pid_t pid; + int result, i; + + infp = gnupg_tmpfile (); + if (!infp) + { + err = gpg_error_from_syserror (); + log_error (_("error creating temporary file: %s\n"), strerror (errno)); + return 1; /* Error - assume password should not be used. */ + } + + if (fwrite (pw, strlen (pw), 1, infp) != 1) + { + err = gpg_error_from_syserror (); + log_error (_("error writing to temporary file: %s\n"), + strerror (errno)); + fclose (infp); + return 1; /* Error - assume password should not be used. */ + } + rewind (infp); + + i = 0; + argv[i++] = "--null"; + argv[i++] = "--", + argv[i++] = opt.check_passphrase_pattern, + argv[i] = NULL; + assert (i < sizeof argv); + + if (gnupg_spawn_process_fd (pgmname, argv, fileno (infp), -1, -1, &pid)) + result = 1; /* Execute error - assume password should no be used. */ + else if (gnupg_wait_process (pgmname, pid)) + result = 1; /* Helper returned an error - probably a match. */ + else + result = 0; /* Success; i.e. no match. */ + + /* Overwrite our temporary file. */ + rewind (infp); + for (i=((strlen (pw)+99)/100)*100; i > 0; i--) + putc ('\xff', infp); + fflush (infp); + fclose (infp); + return result; +} + + +static int +take_this_one_anyway2 (ctrl_t ctrl, const char *desc, const char *anyway_btn) +{ + gpg_error_t err; + + if (opt.enforce_passphrase_constraints) + { + err = agent_show_message (ctrl, desc, _("Enter new passphrase")); + if (!err) + err = gpg_error (GPG_ERR_CANCELED); + } + else + err = agent_get_confirmation (ctrl, desc, + anyway_btn, _("Enter new passphrase")); + return err; +} + + +static int +take_this_one_anyway (ctrl_t ctrl, const char *desc) +{ + return take_this_one_anyway2 (ctrl, desc, _("Take this one anyway")); +} + + /* Check whether the passphrase PW is suitable. Returns 0 if the passphrase is suitable and true if it is not and the user should be asked to provide a different one. */ @@ -78,7 +174,8 @@ check_passphrase_constraints (ctrl_t ctrl, const char *pw) { gpg_error_t err; unsigned int minlen = opt.min_passphrase_len; - + unsigned int minnonalpha = opt.min_passphrase_nonalpha; + if (!pw) pw = ""; @@ -93,25 +190,60 @@ check_passphrase_constraints (ctrl_t ctrl, const char *pw) "be at least %u characters long.", minlen), minlen ); if (!desc) return gpg_error_from_syserror (); - - err = agent_get_confirmation (ctrl, desc, - _("Take this one anyway"), - _("Enter new passphrase")); + err = take_this_one_anyway (ctrl, desc); xfree (desc); if (err) return err; } + if (nonalpha_count (pw) < minnonalpha ) + { + char *desc = xtryasprintf + ( ngettext ("Warning: You have entered a passphrase that%%0A" + "is obviously not secure. A passphrase should%%0A" + "contain at least %u digit or special character.", + "Warning: You have entered a passphrase that%%0A" + "is obviously not secure. A passphrase should%%0A" + "contain at least %u digits or special characters.", + minnonalpha), minnonalpha ); + if (!desc) + return gpg_error_from_syserror (); + err = take_this_one_anyway (ctrl, desc); + xfree (desc); + if (err) + return err; + } + + /* If configured check the passphrase against a list of know words + and pattern. The actual test is done by an external program. + The warning message is generic to give the user no hint on how to + circumvent this list. */ + if (*pw && opt.check_passphrase_pattern && + check_passphrase_pattern (ctrl, pw)) + { + const char *desc = + /* */ _("Warning: You have entered a passphrase that%0A" + "is obviously not secure. A passphrase may not%0A" + "be a known term or match certain pattern."); + + err = take_this_one_anyway (ctrl, desc); + if (err) + return err; + } + + /* The final check is to warn about an empty passphrase. */ if (!*pw) { - const char *desc = _("You have not entered a passphrase - " - "this is in general a bad idea!%0A" - "Please confirm that you do not want to " - "have any protection on your key."); + const char *desc = (opt.enforce_passphrase_constraints? + _("You have not entered a passphrase!%0A" + "An empty passphrase is not allowed.") : + _("You have not entered a passphrase - " + "this is in general a bad idea!%0A" + "Please confirm that you do not want to " + "have any protection on your key.")); - err = agent_get_confirmation (ctrl, desc, - _("Yes, protection is not needed"), - _("Enter new passphrase")); + err = take_this_one_anyway2 (ctrl, desc, + _("Yes, protection is not needed")); if (err) return err; } diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c index cb0a67b73..453e9a9d5 100644 --- a/agent/gpg-agent.c +++ b/agent/gpg-agent.c @@ -88,7 +88,10 @@ enum cmd_and_opt_values oDefCacheTTLSSH, oMaxCacheTTL, oMaxCacheTTLSSH, + oEnforcePassphraseConstraints, oMinPassphraseLen, + oMinPassphraseNonalpha, + oCheckPassphrasePattern, oUseStandardSocket, oNoUseStandardSocket, @@ -149,7 +152,12 @@ static ARGPARSE_OPTS opts[] = { { oDefCacheTTLSSH, "default-cache-ttl-ssh", 4, "@" }, { oMaxCacheTTL, "max-cache-ttl", 4, "@" }, { oMaxCacheTTLSSH, "max-cache-ttl-ssh", 4, "@" }, + + { oEnforcePassphraseConstraints, "enforce-passphrase-constraints", 0, "@"}, { oMinPassphraseLen, "min-passphrase-len", 4, "@" }, + { oMinPassphraseNonalpha, "min-passphrase-nonalpha", 4, "@" }, + { oCheckPassphrasePattern, "check-passphrase-pattern", 2, "@" }, + { oIgnoreCacheForSigning, "ignore-cache-for-signing", 0, N_("do not use the PIN cache when signing")}, { oAllowMarkTrusted, "allow-mark-trusted", 0, @@ -168,6 +176,7 @@ static ARGPARSE_OPTS opts[] = { #define MAX_CACHE_TTL (120*60) /* 2 hours */ #define MAX_CACHE_TTL_SSH (120*60) /* 2 hours */ #define MIN_PASSPHRASE_LEN (8) +#define MIN_PASSPHRASE_NONALPHA (1) /* The timer tick used for housekeeping stuff. For Windows we use a longer period as the SetWaitableTimer seems to signal earlier than @@ -362,7 +371,10 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH; opt.max_cache_ttl = MAX_CACHE_TTL; opt.max_cache_ttl_ssh = MAX_CACHE_TTL_SSH; + opt.enforce_passphrase_constraints = 0; opt.min_passphrase_len = MIN_PASSPHRASE_LEN; + opt.min_passphrase_nonalpha = MIN_PASSPHRASE_NONALPHA; + opt.check_passphrase_pattern = NULL; opt.ignore_cache_for_signing = 0; opt.allow_mark_trusted = 0; opt.disable_scdaemon = 0; @@ -402,7 +414,16 @@ parse_rereadable_options (ARGPARSE_ARGS *pargs, int reread) case oMaxCacheTTL: opt.max_cache_ttl = pargs->r.ret_ulong; break; case oMaxCacheTTLSSH: opt.max_cache_ttl_ssh = pargs->r.ret_ulong; break; + case oEnforcePassphraseConstraints: + opt.enforce_passphrase_constraints=1; + break; case oMinPassphraseLen: opt.min_passphrase_len = pargs->r.ret_ulong; break; + case oMinPassphraseNonalpha: + opt.min_passphrase_nonalpha = pargs->r.ret_ulong; + break; + case oCheckPassphrasePattern: + opt.check_passphrase_pattern = pargs->r.ret_str; + break; case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break; @@ -723,8 +744,15 @@ main (int argc, char **argv ) GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL ); printf ("max-cache-ttl-ssh:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MAX_CACHE_TTL_SSH ); + printf ("enforce-passphrase-constraints:%lu:\n", + GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); printf ("min-passphrase-len:%lu:%d:\n", GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, MIN_PASSPHRASE_LEN ); + printf ("min-passphrase-nonalpha:%lu:%d:\n", + GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME, + MIN_PASSPHRASE_NONALPHA); + printf ("check-passphrase-pattern:%lu:\n", + GC_OPT_FLAG_DEFAULT|GC_OPT_FLAG_RUNTIME); printf ("no-grab:%lu:\n", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME); printf ("ignore-cache-for-signing:%lu:\n", diff --git a/agent/trustlist.c b/agent/trustlist.c index deb0d95cd..a154da899 100644 --- a/agent/trustlist.c +++ b/agent/trustlist.c @@ -455,13 +455,40 @@ agent_listtrusted (void *assuan_context) } +/* Create a copy of string with colons inserted after each two bytes. + Caller needs to release the string. In case of a memory failure, + NULL is returned. */ +static char * +insert_colons (const char *string) +{ + char *buffer, *p; + size_t n = strlen (string); + + p = buffer = xtrymalloc ( n + (n+2)/3 + 1 ); + if (!buffer) + return NULL; + while (*string) + { + *p++ = *string++; + if (*string) + { + *p++ = *string++; + if (*string) + *p++ = ':'; + } + } + *p = 0; + + return buffer; +} + + /* Insert the given fpr into our trustdb. We expect FPR to be an all uppercase hexstring of 40 characters. FLAG is either 'P' or 'C'. - This function does first check whether that key has alreay been put + This function does first check whether that key has already been put into the trustdb and returns success in this case. Before a FPR - actually gets inserted, the user is asked by means of the pin-entry - whether this is actual wants he want to do. -*/ + actually gets inserted, the user is asked by means of the Pinentry + whether this is actual wants he want to do. */ gpg_error_t agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) { @@ -469,6 +496,8 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) char *desc; char *fname; FILE *fp; + char *fprformatted; + /* Check whether we are at all allowed to modify the trustlist. This is useful so that the trustlist may be a symlink to a global @@ -494,6 +523,9 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) return gpg_error (GPG_ERR_NOT_SUPPORTED); /* Insert a new one. */ + fprformatted = insert_colons (fpr); + if (!fprformatted) + return out_of_core (); if (asprintf (&desc, /* TRANSLATORS: This prompt is shown by the Pinentry and has one special property: A "%%0A" is used by @@ -503,12 +535,15 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) plain % sign, you need to encode it as "%%25". The second "%s" gets replaced by a hexdecimal fingerprint string whereas the first one receives - the name as store in the certificate. */ + the name as stored in the certificate. */ _("Please verify that the certificate identified as:%%0A" " \"%s\"%%0A" "has the fingerprint:%%0A" - " %s"), name, fpr) < 0 ) - return out_of_core (); + " %s"), name, fprformatted) < 0 ) + { + xfree (fprformatted); + return out_of_core (); + } /* TRANSLATORS: "Correct" is the label of a button and intended to be hit if the fingerprint matches the one of the CA. The other @@ -519,8 +554,11 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) gpgsm may stop asking further questions. We won't do this for the second question of course. */ if (err) - return (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED ? - gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED) : err); + { + xfree (fprformatted); + return (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED ? + gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED) : err); + } @@ -537,12 +575,18 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) " \"%s\"%%0A" "to correctly certify user certificates?"), name) < 0 ) - return out_of_core (); + { + xfree (fprformatted); + return out_of_core (); + } err = agent_get_confirmation (ctrl, desc, _("Yes"), _("No")); free (desc); if (err) - return err; + { + xfree (fprformatted); + return err; + } /* Now check again to avoid duplicates. We take the lock to make sure that nobody else plays with our file. Frankly we don't work @@ -552,6 +596,7 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) if (!agent_istrusted (ctrl, fpr)) { unlock_trusttable (); + xfree (fprformatted); return 0; } @@ -566,6 +611,7 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) log_error ("can't create `%s': %s\n", fname, gpg_strerror (err)); xfree (fname); unlock_trusttable (); + xfree (fprformatted); return err; } fputs (headerblurb, fp); @@ -578,13 +624,14 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) log_error ("can't open `%s': %s\n", fname, gpg_strerror (err)); xfree (fname); unlock_trusttable (); + xfree (fprformatted); return err; } /* Append the key. */ fputs ("\n# ", fp); print_sanitized_string (fp, name, 0); - fprintf (fp, "\n%s %c\n", fpr, flag); + fprintf (fp, "\n%s %c\n", fprformatted, flag); if (ferror (fp)) err = gpg_error_from_syserror (); @@ -595,6 +642,7 @@ agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag) agent_reload_trustlist (); xfree (fname); unlock_trusttable (); + xfree (fprformatted); return err; } |