Implement dynamic S2K count computation.
authorWerner Koch <wk@gnupg.org>
Mon, 14 Dec 2009 20:12:56 +0000 (20:12 +0000)
committerWerner Koch <wk@gnupg.org>
Mon, 14 Dec 2009 20:12:56 +0000 (20:12 +0000)
NEWS
agent/ChangeLog
agent/agent.h
agent/protect-tool.c
agent/protect.c
g10/server.c

diff --git a/NEWS b/NEWS
index d05571e..2fcf9fa 100644 (file)
--- 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)
 -------------------------------------------------
index ceb693f..600fd10 100644 (file)
@@ -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-08  Werner Koch  <wk@g10code.com>
 
        * protect.c (agent_unprotect): Avoid compiler warning.
index c7d8e02..ea0d494 100644 (file)
@@ -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,
index 78234d2..dc040f9 100644 (file)
@@ -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);
 
index 54f6bd3..6333a15 100644 (file)
 #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;
+}
+
+
 
 \f
 /* 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;
             }
index ee80894..d817f7f 100644 (file)
@@ -601,6 +601,24 @@ cmd_getinfo (assuan_context_t ctx, char *line)
   return rc;
 }
 
+static const char hlp_passwd[] =
+  "PASSWD <userID>\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;
+}
+
+
 
 \f
 /* 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;
     }