From 4d693033ab77e4e0f6349b712bcd2c0a9ac4e42c Mon Sep 17 00:00:00 2001 From: Werner Koch Date: Mon, 14 Dec 2009 20:12:56 +0000 Subject: [PATCH] Implement dynamic S2K count computation. --- NEWS | 3 + agent/ChangeLog | 9 +++ agent/agent.h | 1 + agent/protect-tool.c | 11 ++++ agent/protect.c | 148 +++++++++++++++++++++++++++++++++++++++++-- g10/server.c | 23 ++++++- 6 files changed, 190 insertions(+), 5 deletions(-) diff --git a/NEWS b/NEWS index d05571e54..2fcf9fa16 100644 --- a/NEWS +++ b/NEWS @@ -18,6 +18,9 @@ Noteworthy changes in version 2.1.x (under development) * New GPGSM option --ignore-cert-extension. + * New and changed passphrases 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 ceb693f5b..600fd10b8 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-08 Werner Koch * protect.c (agent_unprotect): Avoid compiler warning. 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 54f6bd38a..6333a154b 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 */ @@ -848,8 +989,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 @@ -891,7 +1031,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; } diff --git a/g10/server.c b/g10/server.c index ee8089491..d817f7f6c 100644 --- a/g10/server.c +++ b/g10/server.c @@ -601,6 +601,24 @@ cmd_getinfo (assuan_context_t ctx, char *line) return rc; } +static const char hlp_passwd[] = + "PASSWD \n" + "\n" + "Change the passphrase of the secret key for USERID."; +static gpg_error_t +cmd_passwd (assuan_context_t ctx, char *line) +{ + ctrl_t ctrl = assuan_get_pointer (ctx); + gpg_error_t err; + + line = skip_options (line); + + err = gpg_error (GPG_ERR_NOT_SUPPORTED); + + return err; +} + + /* Helper to register our commands with libassuan. */ @@ -611,6 +629,7 @@ register_commands (assuan_context_t ctx) { const char *name; assuan_handler_t handler; + assuan_handler_t help; } table[] = { { "RECIPIENT", cmd_recipient }, { "SIGNER", cmd_signer }, @@ -628,13 +647,15 @@ register_commands (assuan_context_t ctx) { "GENKEY", cmd_genkey }, { "DELKEYS", cmd_delkeys }, { "GETINFO", cmd_getinfo }, + { "PASSWD", cmd_passwd, hlp_passwd}, { NULL } }; int i, rc; for (i=0; table[i].name; i++) { - rc = assuan_register_command (ctx, table[i].name, table[i].handler, NULL); + rc = assuan_register_command (ctx, table[i].name, + table[i].handler, table[i].help); if (rc) return rc; }