Exporting secret keys via gpg-agent is now basically supported.
[gnupg.git] / agent / protect.c
index ebb02ac..795d062 100644 (file)
@@ -1,6 +1,6 @@
 /* protect.c - Un/Protect a secret key
  * Copyright (C) 1998, 1999, 2000, 2001, 2002,
- *               2003, 2007 Free Software Foundation, Inc.
+ *               2003, 2007, 2009 Free Software Foundation, Inc.
  *
  * This file is part of GnuPG.
  *
 #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,6 +56,17 @@ 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,
@@ -59,14 +75,132 @@ hash_passphrase (const char *passphrase, int hashalgo,
 
 
 \f
-/* Calculate the MIC for a private key S-Exp. SHA1HASH should point to
-   a 20 byte buffer.  This function is suitable for any algorithms. */
+/* Get the process time and store it in DATA.  */
+static void
+calibrate_get_time (struct calibrate_time_s *data)
+{
+#ifdef HAVE_W32_SYSTEM
+# ifdef HAVE_W32CE_SYSTEM
+  GetThreadTimes (GetCurrentThread (),
+# else 
+  GetProcessTimes (GetCurrentProcess (),
+# endif
+                   &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 -> %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 or shared secret S-expression.
+   SHA1HASH should point to a 20 byte buffer.  This function is
+   suitable for all algorithms. */
 static int 
 calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash)
 {
   const unsigned char *hash_begin, *hash_end;
   const unsigned char *s;
   size_t n;
+  int is_shared_secret;
 
   s = plainkey;
   if (*s != '(')
@@ -75,16 +209,23 @@ calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash)
   n = snext (&s);
   if (!n)
     return gpg_error (GPG_ERR_INV_SEXP); 
-  if (!smatch (&s, n, "private-key"))
+  if (smatch (&s, n, "private-key"))
+    is_shared_secret = 0;
+  else if (smatch (&s, n, "shared-secret"))
+    is_shared_secret = 1;
+  else
     return gpg_error (GPG_ERR_UNKNOWN_SEXP); 
   if (*s != '(')
     return gpg_error (GPG_ERR_UNKNOWN_SEXP);
   hash_begin = s;
-  s++;
-  n = snext (&s);
-  if (!n)
-    return gpg_error (GPG_ERR_INV_SEXP); 
-  s += n; /* skip over the algorithm name */
+  if (!is_shared_secret)
+    {
+      s++;
+      n = snext (&s);
+      if (!n)
+        return gpg_error (GPG_ERR_INV_SEXP); 
+      s += n; /* Skip the algorithm name.  */
+    }
 
   while (*s == '(')
     {
@@ -176,8 +317,11 @@ do_encryption (const unsigned char *protbegin, size_t protlen,
       iv = xtrymalloc (blklen*2+8);
       if (!iv)
         rc = gpg_error (GPG_ERR_ENOMEM);
-      gcry_create_nonce (iv, blklen*2+8);
-      rc = gcry_cipher_setiv (hd, iv, blklen);
+      else
+        {
+          gcry_create_nonce (iv, blklen*2+8);
+          rc = gcry_cipher_setiv (hd, iv, blklen);
+        }
     }
   if (!rc)
     {
@@ -190,7 +334,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);
@@ -230,19 +375,25 @@ do_encryption (const unsigned char *protbegin, size_t protlen,
        
      in canoncical format of course.  We use asprintf and %n modifier
      and dummy values as placeholders.  */
-  p = xtryasprintf
-    ("(9:protected%d:%s((4:sha18:%n_8bytes_2:96)%d:%n%*s)%d:%n%*s)",
-     (int)strlen (modestr), modestr,
-     &saltpos, 
-     blklen, &ivpos, blklen, "",
-     enclen, &encpos, enclen, "");
-  if (!p)
-    {
-      gpg_error_t tmperr = out_of_core ();
-      xfree (iv);
-      xfree (outbuf);
-      return tmperr;
-    }
+  {
+    char countbuf[35];
+
+    snprintf (countbuf, sizeof countbuf, "%lu", get_standard_s2k_count ());
+    p = xtryasprintf
+      ("(9:protected%d:%s((4:sha18:%n_8bytes_%u:%s)%d:%n%*s)%d:%n%*s)",
+       (int)strlen (modestr), modestr,
+       &saltpos, 
+       (unsigned int)strlen (countbuf), countbuf,
+       blklen, &ivpos, blklen, "",
+       enclen, &encpos, enclen, "");
+    if (!p)
+      {
+        gpg_error_t tmperr = out_of_core ();
+        xfree (iv);
+        xfree (outbuf);
+        return tmperr;
+      }
+  }
   *resultlen = strlen (p);
   *result = (unsigned char*)p;
   memcpy (p+saltpos, iv+2*blklen, 8);
@@ -275,7 +426,7 @@ agent_protect (const unsigned char *plainkey, const char *passphrase,
   unsigned char *p;
   gcry_md_hd_t md;
 
-  /* Create an S-expression with the procted-at timestamp.  */
+  /* Create an S-expression with the protected-at timestamp.  */
   memcpy (timestamp_exp, "(12:protected-at15:", 19);
   gnupg_get_isotime (timestamp_exp+19);
   timestamp_exp[19+15] = ')';
@@ -754,9 +905,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 */
 
@@ -772,6 +937,7 @@ agent_unprotect (const unsigned char *protectedkey, const char *passphrase,
   if (!n)
     return gpg_error (GPG_ERR_INV_SEXP); 
   
+  cleartext = NULL; /* Avoid cc warning. */
   rc = do_decryption (s, n,
                       passphrase, s2ksalt, s2kcount,
                       iv, 16,
@@ -798,7 +964,7 @@ agent_unprotect (const unsigned char *protectedkey, const char *passphrase,
       xfree (final);
       return rc;
     }
-  /* Now remove tha part which is included in the MIC but should not
+  /* Now remove the part which is included in the MIC but should not
      go into the final thing.  */
   if (cutlen)
     {
@@ -844,8 +1010,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
@@ -887,7 +1052,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;
             }
@@ -922,6 +1087,19 @@ hash_passphrase (const char *passphrase, int hashalgo,
 }
 
 
+gpg_error_t
+s2k_hash_passphrase (const char *passphrase, int hashalgo,
+                     int s2kmode,
+                     const unsigned char *s2ksalt,
+                     unsigned int s2kcount,
+                     unsigned char *key, size_t keylen)
+{
+  return hash_passphrase (passphrase, hashalgo, s2kmode, s2ksalt, 
+                          (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6),
+                          key, keylen);
+}
+
+
 \f
 
 /* Create an canonical encoded S-expression with the shadow info from
@@ -1105,3 +1283,68 @@ agent_get_shadow_info (const unsigned char *shadowkey,
   return 0;
 }
 
+
+/* Parse the canonical encoded SHADOW_INFO S-expression.  On success
+   the hex encoded serial number is returned as a malloced strings at
+   R_HEXSN and the Id string as a malloced string at R_IDSTR.  On
+   error an error code is returned and NULL is stored at the result
+   parameters addresses.  If the serial number or the ID string is not
+   required, NULL may be passed for them.  */
+gpg_error_t
+parse_shadow_info (const unsigned char *shadow_info, 
+                   char **r_hexsn, char **r_idstr)
+{
+  const unsigned char *s;
+  size_t n;
+
+  if (r_hexsn)
+    *r_hexsn = NULL;
+  if (r_idstr)
+    *r_idstr = NULL;
+
+  s = shadow_info;
+  if (*s != '(')
+    return gpg_error (GPG_ERR_INV_SEXP);
+  s++;
+  n = snext (&s);
+  if (!n)
+    return gpg_error (GPG_ERR_INV_SEXP);
+
+  if (r_hexsn)
+    {
+      *r_hexsn = bin2hex (s, n, NULL);
+      if (!*r_hexsn)
+        return gpg_error_from_syserror ();
+    }
+  s += n;
+
+  n = snext (&s);
+  if (!n)
+    {
+      if (r_hexsn)
+        {
+          xfree (*r_hexsn);
+          *r_hexsn = NULL;
+        }
+      return gpg_error (GPG_ERR_INV_SEXP);
+    }
+  
+  if (r_idstr)
+    {
+      *r_idstr = xtrymalloc (n+1);
+      if (!*r_idstr)
+        {
+          if (r_hexsn)
+            {
+              xfree (*r_hexsn);
+              *r_hexsn = NULL;
+            }
+          return gpg_error_from_syserror ();
+        }
+      memcpy (*r_idstr, s, n);
+      (*r_idstr)[n] = 0;
+    }
+
+  return 0;
+}
+