diff --git a/NEWS b/NEWS index e2e11d48c..e2b827fb0 100644 --- a/NEWS +++ b/NEWS @@ -13,6 +13,9 @@ Noteworthy changes in version 2.0.14 * New GPGSM option --ignore-cert-extension. + * New and changed passphrases for gpg-agent protected keys are now + created with an iteration count requiring about 100ms of CPU work. + Noteworthy changes in version 2.0.13 (2009-09-04) ------------------------------------------------- diff --git a/agent/ChangeLog b/agent/ChangeLog index ee3516043..03150e543 100644 --- a/agent/ChangeLog +++ b/agent/ChangeLog @@ -1,3 +1,12 @@ +2009-12-14 Werner Koch + + * protect.c (agent_unprotect): Decode the S2K count here and take + care of the new unencoded values. Add a lower limit sanity check. + (hash_passphrase): Do not decode here. + (get_standard_s2k_count, calibrate_s2k_count): New. + (calibrate_get_time, calibrate_elapsed_time): New. + (do_encryption): Use get_standard_s2k_count. + 2009-12-03 Werner Koch * gpg-agent.c (set_debug): Allow for numerical debug leveles. Print @@ -42,7 +51,7 @@ * genkey.c (agent_protect_and_store): Return RC and not 0. * protect.c (do_encryption): Fix ignored error code from malloc. Reported by Fabian Keil. - + 2009-06-17 Werner Koch * call-pinentry.c (agent_get_confirmation): Add arg WITH_CANCEL. diff --git a/agent/agent.h b/agent/agent.h index c7d8e02dd..ea0d49465 100644 --- a/agent/agent.h +++ b/agent/agent.h @@ -285,6 +285,7 @@ int agent_genkey (ctrl_t ctrl, int agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey); /*-- protect.c --*/ +unsigned long get_standard_s2k_count (void); int agent_protect (const unsigned char *plainkey, const char *passphrase, unsigned char **result, size_t *resultlen); int agent_unprotect (const unsigned char *protectedkey, const char *passphrase, diff --git a/agent/protect-tool.c b/agent/protect-tool.c index 78234d22d..dc040f9d8 100644 --- a/agent/protect-tool.c +++ b/agent/protect-tool.c @@ -61,6 +61,7 @@ enum cmd_and_opt_values oShadow, oShowShadowInfo, oShowKeygrip, + oS2Kcalibration, oCanonical, oP12Import, @@ -120,6 +121,8 @@ static ARGPARSE_OPTS opts[] = { "import a pkcs#12 encoded private key"), ARGPARSE_c (oP12Export, "p12-export", "export a private key pkcs#12 encoded"), + + ARGPARSE_c (oS2Kcalibration, "s2k-calibration", "@"), ARGPARSE_group (301, N_("@\nOptions:\n ")), @@ -1061,6 +1064,8 @@ main (int argc, char **argv ) case oP12Export: cmd = oP12Export; break; case oP12Charset: opt_p12_charset = pargs.r.ret_str; break; + case oS2Kcalibration: cmd = oS2Kcalibration; break; + case oPassphrase: opt_passphrase = pargs.r.ret_str; break; case oStore: opt_store = 1; break; case oForce: opt_force = 1; break; @@ -1105,6 +1110,12 @@ main (int argc, char **argv ) import_p12_file (fname); else if (cmd == oP12Export) export_p12_file (fname); + else if (cmd == oS2Kcalibration) + { + if (!opt.verbose) + opt.verbose++; /* We need to see something. */ + get_standard_s2k_count (); + } else show_file (fname); diff --git a/agent/protect.c b/agent/protect.c index d6457ad2a..accb0ca1e 100644 --- a/agent/protect.c +++ b/agent/protect.c @@ -27,6 +27,11 @@ #include #include #include +#ifdef HAVE_W32_SYSTEM +# include +#else +# include +#endif #include "agent.h" @@ -51,12 +56,133 @@ static struct { }; +/* A helper object for time measurement. */ +struct calibrate_time_s +{ +#ifdef HAVE_W32_SYSTEM + FILETIME creation_time, exit_time, kernel_time, user_time; +#else + clock_t ticks; +#endif +}; + + static int hash_passphrase (const char *passphrase, int hashalgo, int s2kmode, const unsigned char *s2ksalt, unsigned long s2kcount, unsigned char *key, size_t keylen); +/* Get the process time and store it in DATA. */ +static void +calibrate_get_time (struct calibrate_time_s *data) +{ +#ifdef HAVE_W32_SYSTEM + GetProcessTimes (GetCurrentProcess (), + &data->creation_time, &data->exit_time, + &data->kernel_time, &data->user_time); +#else + struct tms tmp; + + times (&tmp); + data->ticks = tmp.tms_utime; +#endif +} + + +static unsigned long +calibrate_elapsed_time (struct calibrate_time_s *starttime) +{ + struct calibrate_time_s stoptime; + + calibrate_get_time (&stoptime); +#ifdef HAVE_W32_SYSTEM + { + unsigned long long t1, t2; + + t1 = (((unsigned long long)starttime->kernel_time.dwHighDateTime << 32) + + starttime->kernel_time.dwLowDateTime); + t1 += (((unsigned long long)starttime->user_time.dwHighDateTime << 32) + + starttime->user_time.dwLowDateTime); + t2 = (((unsigned long long)stoptime.kernel_time.dwHighDateTime << 32) + + stoptime.kernel_time.dwLowDateTime); + t2 += (((unsigned long long)stoptime.user_time.dwHighDateTime << 32) + + stoptime.user_time.dwLowDateTime); + return (unsigned long)((t2 - t1)/10000); + } +#else + return (unsigned long)((((double) (stoptime.ticks - starttime->ticks)) + /CLOCKS_PER_SEC)*10000000); +#endif +} + + +/* Run a test hashing for COUNT and return the time required in + milliseconds. */ +static unsigned long +calibrate_s2k_count_one (unsigned long count) +{ + int rc; + char keybuf[PROT_CIPHER_KEYLEN]; + struct calibrate_time_s starttime; + + calibrate_get_time (&starttime); + rc = hash_passphrase ("123456789abcdef0", GCRY_MD_SHA1, + 3, "saltsalt", count, keybuf, sizeof keybuf); + if (rc) + BUG (); + return calibrate_elapsed_time (&starttime); +} + + +/* Measure the time we need to do the hash operations and deduce an + S2K count which requires about 100ms of time. */ +static unsigned long +calibrate_s2k_count (void) +{ + unsigned long count; + unsigned long ms; + + for (count = 65536; count; count *= 2) + { + ms = calibrate_s2k_count_one (count); + if (opt.verbose > 1) + log_info ("S2K calibration: %lu -> %lums\n", count, ms); + if (ms > 100) + break; + } + + count = (unsigned long)(((double)count / ms) * 100); + count /= 1024; + count *= 1024; + if (count < 65536) + count = 65536; + + if (opt.verbose) + { + ms = calibrate_s2k_count_one (count); + log_info ("S2K calibration: %lu iterations for %lums\n", count, ms); + } + + return count; +} + + + +/* Return the standard S2K count. */ +unsigned long +get_standard_s2k_count (void) +{ + static unsigned long count; + + if (!count) + count = calibrate_s2k_count (); + + /* Enforce a lower limit. */ + return count < 65536 ? 65536 : count; +} + + /* Calculate the MIC for a private key S-Exp. SHA1HASH should point to @@ -193,7 +319,8 @@ do_encryption (const unsigned char *protbegin, size_t protlen, else { rc = hash_passphrase (passphrase, GCRY_MD_SHA1, - 3, iv+2*blklen, 96, key, keylen); + 3, iv+2*blklen, + get_standard_s2k_count (), key, keylen); if (!rc) rc = gcry_cipher_setkey (hd, key, keylen); xfree (key); @@ -757,9 +884,23 @@ agent_unprotect (const unsigned char *protectedkey, const char *passphrase, is nothing we should worry about */ if (s[n] != ')' ) return gpg_error (GPG_ERR_INV_SEXP); + + /* Old versions of gpg-agent used the funny floating point number in + a byte encoding as specified by OpenPGP. However this is not + needed and thus we now store it as a plain unsigned integer. We + can easily distinguish the old format by looking at its value: + Less than 256 is an old-style encoded number; other values are + plain integers. In any case we check that they are at least + 65536 because we never used a lower value in the past and we + should have a lower limit. */ s2kcount = strtoul ((const char*)s, NULL, 10); if (!s2kcount) return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + if (s2kcount < 256) + s2kcount = (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6); + if (s2kcount < 65536) + return gpg_error (GPG_ERR_CORRUPTED_PROTECTION); + s += n; s++; /* skip list end */ @@ -847,8 +988,7 @@ agent_private_key_type (const unsigned char *privatekey) /* Transform a passphrase into a suitable key of length KEYLEN and store this key in the caller provided buffer KEY. The caller must provide an HASHALGO, a valid S2KMODE (see rfc-2440) and depending on - that mode an S2KSALT of 8 random bytes and an S2KCOUNT (a suitable - value is 96). + that mode an S2KSALT of 8 random bytes and an S2KCOUNT. Returns an error code on failure. */ static int @@ -890,7 +1030,7 @@ hash_passphrase (const char *passphrase, int hashalgo, if (s2kmode == 3) { - count = (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6); + count = s2kcount; if (count < len2) count = len2; }