1
0
mirror of git://git.gnupg.org/gnupg.git synced 2025-01-09 12:54:23 +01:00

Implement dynamic S2K count computation for GPGSM

This commit is contained in:
Werner Koch 2009-12-14 20:18:53 +00:00
parent 4135599f7c
commit 47791192db
5 changed files with 169 additions and 5 deletions

3
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)
-------------------------------------------------

@ -1,3 +1,12 @@
2009-12-14 Werner Koch <wk@g10code.com>
* 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 <wk@g10code.com>
* 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 <wk@g10code.com>
* call-pinentry.c (agent_get_confirmation): Add arg WITH_CANCEL.

@ -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,

@ -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);

@ -27,6 +27,11 @@
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#ifdef HAVE_W32_SYSTEM
# include <windows.h>
#else
# include <sys/times.h>
#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;
}