Fix possible system freeze on Mac OS X.
[gnupg.git] / agent / minip12.c
index 5361708..6b65c8c 100644 (file)
@@ -5,7 +5,7 @@
  *
  * GnuPG is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
+ * the Free Software Foundation; either version 3 of the License, or
  * (at your option) any later version.
  *
  * GnuPG is distributed in the hope that it will be useful,
@@ -14,9 +14,7 @@
  * GNU General Public License for more details.
  *
  * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
- * USA.
+ * along with this program; if not, see <http://www.gnu.org/licenses/>.
  */
 
 #ifdef HAVE_CONFIG_H
 #include <string.h>
 #include <assert.h>
 #include <gcrypt.h>
+#include <errno.h>
 
 #ifdef TEST
 #include <sys/stat.h>
 #include <unistd.h>
-#include <errno.h>
 #endif
 
 #include "../jnlib/logging.h"
+#include "../jnlib/utf8conv.h"
 #include "minip12.h"
 
 #ifndef DIM
 #define DIM(v)              (sizeof(v)/sizeof((v)[0]))
 #endif
 
+
 enum
 {
   UNIVERSAL = 0,
   APPLICATION = 1,
-  CONTEXT = 2,
+  ASNCONTEXT = 2,
   PRIVATE = 3
 };
 
@@ -483,6 +483,124 @@ crypt_block (unsigned char *buffer, size_t length, char *salt, size_t saltlen,
 }
   
 
+/* Decrypt a block of data and try several encodings of the key.
+   CIPHERTEXT is the encrypted data of size LENGTH bytes; PLAINTEXT is
+   a buffer of the same size to receive the decryption result. SALT,
+   SALTLEN, ITER and PW are the information required for decryption
+   and CIPHER_ALGO is the algorithm id to use.  CHECK_FNC is a
+   function called with the plaintext and used to check whether the
+   decryption succeeded; i.e. that a correct passphrase has been
+   given.  That function shall return true if the decryption has likely
+   succeeded. */
+static void
+decrypt_block (const void *ciphertext, unsigned char *plaintext, size_t length,
+               char *salt, size_t saltlen,
+               int iter, const char *pw, int cipher_algo,
+               int (*check_fnc) (const void *, size_t))
+{
+  static const char * const charsets[] = {
+    "",   /* No conversion - use the UTF-8 passphrase direct.  */
+    "ISO-8859-1",
+    "ISO-8859-15",
+    "ISO-8859-2",
+    "ISO-8859-3",
+    "ISO-8859-4",
+    "ISO-8859-5",
+    "ISO-8859-6",
+    "ISO-8859-7",
+    "ISO-8859-8",
+    "ISO-8859-9",
+    "KOI8-R",
+    "IBM437",
+    "IBM850",
+    "EUC-JP",
+    "BIG5",
+    NULL
+  };
+  int charsetidx = 0;
+  char *convertedpw = NULL;   /* Malloced and converted password or NULL.  */
+  size_t convertedpwsize = 0; /* Allocated length.  */
+
+  for (charsetidx=0; charsets[charsetidx]; charsetidx++)
+    {
+      if (*charsets[charsetidx])
+        {
+          jnlib_iconv_t cd;
+          const char *inptr;
+          char *outptr;
+          size_t inbytes, outbytes;
+
+          if (!convertedpw)
+            {
+              /* We assume one byte encodings.  Thus we can allocate
+                 the buffer of the same size as the original
+                 passphrase; the result will actually be shorter
+                 then.  */
+              convertedpwsize = strlen (pw) + 1;
+              convertedpw = gcry_malloc_secure (convertedpwsize);
+              if (!convertedpw)
+                {
+                  log_info ("out of secure memory while"
+                            " converting passphrase\n");
+                  break; /* Give up.  */
+                }
+            }
+
+          cd = jnlib_iconv_open (charsets[charsetidx], "utf-8");
+          if (cd == (jnlib_iconv_t)(-1))
+            continue;
+
+          inptr = pw;
+          inbytes = strlen (pw);
+          outptr = convertedpw;
+          outbytes = convertedpwsize - 1;
+          if ( jnlib_iconv (cd, (const char **)&inptr, &inbytes,
+                      &outptr, &outbytes) == (size_t)-1) 
+            {
+              jnlib_iconv_close (cd);
+              continue;
+            }
+          *outptr = 0;
+          jnlib_iconv_close (cd);
+          log_info ("decryption failed; trying charset `%s'\n",
+                    charsets[charsetidx]);
+        }
+      memcpy (plaintext, ciphertext, length);
+      crypt_block (plaintext, length, salt, saltlen, iter,
+                   convertedpw? convertedpw:pw, cipher_algo, 0);
+      if (check_fnc (plaintext, length))
+        break; /* Decryption succeeded. */
+    }
+  gcry_free (convertedpw);
+}
+
+
+/* Return true if the decryption of an bag_encrypted_data object has
+   likely succeeded.  */
+static int
+bag_decrypted_data_p (const void *plaintext, size_t length)
+{
+  struct tag_info ti;
+  const unsigned char *p = plaintext;
+  size_t n = length;
+
+  /*   { */
+  /* #  warning debug code is enabled */
+  /*     FILE *fp = fopen ("tmp-rc2-plain.der", "wb"); */
+  /*     if (!fp || fwrite (p, n, 1, fp) != 1) */
+  /*       exit (2); */
+  /*     fclose (fp); */
+  /*   } */
+  
+  if (parse_tag (&p, &n, &ti))
+    return 0;
+  if (ti.class || ti.tag != TAG_SEQUENCE)
+    return 0;
+  if (parse_tag (&p, &n, &ti))
+    return 0;
+
+  return 1; 
+}
 
 /* Note: If R_RESULT is passed as NULL, a key object as already be
    processed and thus we need to skip it here. */
@@ -497,7 +615,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
   const unsigned char *p_start = buffer;
   size_t n = length;
   const char *where;
-  char salt[16];
+  char salt[20];
   size_t saltlen;
   unsigned int iter;
   unsigned char *plain = NULL;
@@ -513,7 +631,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
   where = "start";
   if (parse_tag (&p, &n, &ti))
     goto bailout;
-  if (ti.class != CONTEXT || ti.tag)
+  if (ti.class != ASNCONTEXT || ti.tag)
     goto bailout;
   if (parse_tag (&p, &n, &ti))
     goto bailout;
@@ -575,7 +693,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
   if (parse_tag (&p, &n, &ti))
     goto bailout;
   if (ti.class || ti.tag != TAG_OCTET_STRING
-      || ti.length < 8 || ti.length > 16 )
+      || ti.length < 8 || ti.length > 20 )
     goto bailout;
   saltlen = ti.length;
   memcpy (salt, p, saltlen);
@@ -597,7 +715,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
     goto bailout;
 
   consumed = p - p_start;
-  if (ti.class == CONTEXT && ti.tag == 0 && ti.is_constructed && ti.ndef)
+  if (ti.class == ASNCONTEXT && ti.tag == 0 && ti.is_constructed && ti.ndef)
     {
       /* Mozilla exported certs now come with single byte chunks of
          octect strings.  (Mozilla Firefox 1.0.4).  Arghh. */
@@ -611,7 +729,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
       r_consumed = NULL; /* Ugly hack to not update that value any further. */
       ti.length = n;
     }
-  else if (ti.class == CONTEXT && ti.tag == 0 && ti.length )
+  else if (ti.class == ASNCONTEXT && ti.tag == 0 && ti.length )
     ;
   else
     goto bailout;
@@ -624,23 +742,13 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
       log_error ("error allocating decryption buffer\n");
       goto bailout;
     }
-  memcpy (plain, p, ti.length);
-  crypt_block (plain, ti.length, salt, saltlen,
-               iter, pw, 
-               is_3des? GCRY_CIPHER_3DES : GCRY_CIPHER_RFC2268_40, 
-               0);
+  decrypt_block (p, plain, ti.length, salt, saltlen, iter, pw, 
+                 is_3des? GCRY_CIPHER_3DES : GCRY_CIPHER_RFC2268_40, 
+                 bag_decrypted_data_p);
   n = ti.length;
   startoffset = 0;
   p_start = p = plain;
 
-/*   { */
-/* #  warning debug code is enabled */
-/*     FILE *fp = fopen ("tmp-rc2-plain.der", "wb"); */
-/*     if (!fp || fwrite (p, n, 1, fp) != 1) */
-/*       exit (2); */
-/*     fclose (fp); */
-/*   } */
-
   where = "outer.outer.seq";
   if (parse_tag (&p, &n, &ti))
     {
@@ -703,7 +811,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
       where = "certbag.before.certheader";
       if (parse_tag (&p, &n, &ti))
         goto bailout;
-      if (ti.class != CONTEXT || ti.tag)
+      if (ti.class != ASNCONTEXT || ti.tag)
         goto bailout;
       if (iscrlbag)
         {
@@ -822,7 +930,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
           where = "certbag.before.octetstring";
           if (parse_tag (&p, &n, &ti))
             goto bailout;
-          if (ti.class != CONTEXT || ti.tag)
+          if (ti.class != ASNCONTEXT || ti.tag)
             goto bailout;
           if (parse_tag (&p, &n, &ti))
             goto bailout;
@@ -888,7 +996,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
   gcry_free (plain);
   gcry_free (cram_buffer);
   log_error ("encryptedData error at \"%s\", offset %u\n",
-             where, (p - p_start)+startoffset);
+             where, (unsigned int)((p - p_start)+startoffset));
   if (bad_pass)
     {
       /* Note, that the following string might be used by other programs
@@ -899,6 +1007,34 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
   return -1;
 }
 
+
+/* Return true if the decryption of a bag_data object has likely
+   succeeded.  */
+static int
+bag_data_p (const void *plaintext, size_t length)
+{
+  struct tag_info ti;
+  const unsigned char *p = plaintext;
+  size_t n = length;
+
+/*   { */
+/* #  warning debug code is enabled */
+/*     FILE *fp = fopen ("tmp-3des-plain-key.der", "wb"); */
+/*     if (!fp || fwrite (p, n, 1, fp) != 1) */
+/*       exit (2); */
+/*     fclose (fp); */
+/*   } */
+
+  if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
+    return 0;
+  if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER
+      || ti.length != 1 || *p)
+    return 0;
+
+  return 1; 
+}
+
+
 static gcry_mpi_t *
 parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
                 size_t *r_consumed, const char *pw)
@@ -909,7 +1045,7 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
   const unsigned char *p_start = buffer;
   size_t n = length;
   const char *where;
-  char salt[16];
+  char salt[20];
   size_t saltlen;
   unsigned int iter;
   int len;
@@ -922,7 +1058,7 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
   where = "start";
   if (parse_tag (&p, &n, &ti))
     goto bailout;
-  if (ti.class != CONTEXT || ti.tag)
+  if (ti.class != ASNCONTEXT || ti.tag)
     goto bailout;
   if (parse_tag (&p, &n, &ti))
     goto bailout;
@@ -969,7 +1105,7 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
   where = "shrouded,outerseqs";
   if (parse_tag (&p, &n, &ti))
     goto bailout;
-  if (ti.class != CONTEXT || ti.tag)
+  if (ti.class != ASNCONTEXT || ti.tag)
     goto bailout;
   if (parse_tag (&p, &n, &ti))
     goto bailout;
@@ -997,7 +1133,7 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
   if (parse_tag (&p, &n, &ti))
     goto bailout;
   if (ti.class || ti.tag != TAG_OCTET_STRING
-      || ti.length < 8 || ti.length > 16)
+      || ti.length < 8 || ti.length > 20)
     goto bailout;
   saltlen = ti.length;
   memcpy (salt, p, saltlen);
@@ -1028,22 +1164,14 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
       log_error ("error allocating decryption buffer\n");
       goto bailout;
     }
-  memcpy (plain, p, ti.length);
   consumed += p - p_start + ti.length;
-  crypt_block (plain, ti.length, salt, saltlen, iter, pw, GCRY_CIPHER_3DES, 0);
+  decrypt_block (p, plain, ti.length, salt, saltlen, iter, pw, 
+                 GCRY_CIPHER_3DES, 
+                 bag_data_p);
   n = ti.length;
   startoffset = 0;
   p_start = p = plain;
 
-/*   { */
-/* #  warning debug code is enabled */
-/*     FILE *fp = fopen ("tmp-rc2-plain-key.der", "wb"); */
-/*     if (!fp || fwrite (p, n, 1, fp) != 1) */
-/*       exit (2); */
-/*     fclose (fp); */
-/*   } */
-
-
   where = "decrypted-text";
   if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
     goto bailout;
@@ -1133,7 +1261,7 @@ parse_bag_data (const unsigned char *buffer, size_t length, int startoffset,
     }
   gcry_free (cram_buffer);
   log_error ( "data error at \"%s\", offset %u\n",
-              where, (p - buffer) + startoffset);
+              where, (unsigned int)((p - buffer) + startoffset));
   if (r_consumed)
     *r_consumed = consumed;
   return NULL;
@@ -1189,7 +1317,7 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw,
 
   if (parse_tag (&p, &n, &ti))
     goto bailout;
-  if (ti.class != CONTEXT || ti.tag)
+  if (ti.class != ASNCONTEXT || ti.tag)
     goto bailout;
   if (parse_tag (&p, &n, &ti))
     goto bailout;
@@ -1309,7 +1437,8 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw,
   gcry_free (cram_buffer);
   return result;
  bailout:
-  log_error ("error at \"%s\", offset %u\n", where, (p - p_start));
+  log_error ("error at \"%s\", offset %u\n",
+             where, (unsigned int)(p - p_start));
   if (result)
     {
       int i;
@@ -2007,25 +2136,75 @@ build_cert_sequence (unsigned char *buffer, size_t buflen,
 }
 
 
-/* Expect the RSA key parameters in KPARMS and a password in
-   PW. Create a PKCS structure from it and return it as well as the
-   length in R_LENGTH; return NULL in case of an error. */
+/* Expect the RSA key parameters in KPARMS and a password in PW.
+   Create a PKCS structure from it and return it as well as the length
+   in R_LENGTH; return NULL in case of an error.  If CHARSET is not
+   NULL, re-encode PW to that character set. */
 unsigned char * 
 p12_build (gcry_mpi_t *kparms, unsigned char *cert, size_t certlen,
-           const char *pw, size_t *r_length)
+           const char *pw, const char *charset, size_t *r_length)
 {
-  unsigned char *buffer;
+  unsigned char *buffer = NULL;
   size_t n, buflen;
   char salt[8];
   struct buffer_s seqlist[3];
   int seqlistidx = 0;
   unsigned char sha1hash[20];
   char keyidstr[8+1];
+  char *pwbuf = NULL;
+  size_t pwbufsize = 0;
 
   n = buflen = 0; /* (avoid compiler warning). */
   memset (sha1hash, 0, 20);
   *keyidstr = 0;
 
+  if (charset && pw && *pw)
+    {
+      jnlib_iconv_t cd;
+      const char *inptr;
+      char *outptr;
+      size_t inbytes, outbytes;
+
+      /* We assume that the converted passphrase is at max 2 times
+         longer than its utf-8 encoding. */
+      pwbufsize = strlen (pw)*2 + 1;
+      pwbuf = gcry_malloc_secure (pwbufsize);
+      if (!pwbuf)
+        {
+          log_error ("out of secure memory while converting passphrase\n");
+          goto failure;
+        }
+
+      cd = jnlib_iconv_open (charset, "utf-8");
+      if (cd == (jnlib_iconv_t)(-1))
+        {
+          log_error ("can't convert passphrase to"
+                     " requested charset `%s': %s\n",
+                     charset, strerror (errno));
+          gcry_free (pwbuf);
+          goto failure;
+        }
+
+      inptr = pw;
+      inbytes = strlen (pw);
+      outptr = pwbuf;
+      outbytes = pwbufsize - 1;
+      if ( jnlib_iconv (cd, (const char **)&inptr, &inbytes,
+                      &outptr, &outbytes) == (size_t)-1) 
+        {
+          log_error ("error converting passphrase to"
+                     " requested charset `%s': %s\n",
+                     charset, strerror (errno));
+          gcry_free (pwbuf);
+          jnlib_iconv_close (cd);
+          goto failure;
+        }
+      *outptr = 0;
+      jnlib_iconv_close (cd);
+      pw = pwbuf;
+    }
+
+
   if (cert && certlen)
     {
       /* Calculate the hash value we need for the bag attributes. */
@@ -2087,6 +2266,11 @@ p12_build (gcry_mpi_t *kparms, unsigned char *cert, size_t certlen,
   buffer = create_final (seqlist, pw, &buflen);
 
  failure:
+  if (pwbuf)
+    {
+      wipememory (pwbuf, pwbufsize);
+      gcry_free (pwbuf);
+    }
   for ( ; seqlistidx; seqlistidx--)
     gcry_free (seqlist[seqlistidx].buffer);