2007-04-26 Marcus Brinkmann <marcus@g10code.de>
[gnupg.git] / agent / minip12.c
index 912d387..6958e5e 100644 (file)
 #include <string.h>
 #include <assert.h>
 #include <gcrypt.h>
+#include <iconv.h>
+#include <errno.h>
 
 #ifdef TEST
 #include <sys/stat.h>
 #include <unistd.h>
-#include <errno.h>
 #endif
 
 #include "../jnlib/logging.h"
 #define DIM(v)              (sizeof(v)/sizeof((v)[0]))
 #endif
 
+#ifndef ICONV_CONST
+#define ICONV_CONST
+#endif
+
+
+
 enum
 {
   UNIVERSAL = 0,
@@ -88,6 +95,8 @@ static unsigned char const oid_data[9] = {
   0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01 };
 static unsigned char const oid_encryptedData[9] = {
   0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x06 };
+static unsigned char const oid_pkcs_12_keyBag[11] = {
+  0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x01 };
 static unsigned char const oid_pkcs_12_pkcs_8ShroudedKeyBag[11] = {
   0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x02 };
 static unsigned char const oid_pkcs_12_CertBag[11] = {
@@ -132,6 +141,23 @@ static unsigned char const data_mactemplate[51] = {
 #define DATA_MACTEMPLATE_MAC_OFF 17
 #define DATA_MACTEMPLATE_SALT_OFF 39
 
+static unsigned char const data_attrtemplate[106] = {
+  0x31, 0x7c, 0x30, 0x55, 0x06, 0x09, 0x2a, 0x86,
+  0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x14, 0x31,
+  0x48, 0x1e, 0x46, 0x00, 0x47, 0x00, 0x6e, 0x00,
+  0x75, 0x00, 0x50, 0x00, 0x47, 0x00, 0x20, 0x00,
+  0x65, 0x00, 0x78, 0x00, 0x70, 0x00, 0x6f, 0x00,
+  0x72, 0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00,
+  0x20, 0x00, 0x63, 0x00, 0x65, 0x00, 0x72, 0x00,
+  0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00,
+  0x63, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00,
+  0x20, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00,
+  0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00,
+  0x66, 0x30, 0x23, 0x06, 0x09, 0x2a, 0x86, 0x48,
+  0x86, 0xf7, 0x0d, 0x01, 0x09, 0x15, 0x31, 0x16,
+  0x04, 0x14 }; /* Need to append SHA-1 digest. */
+#define DATA_ATTRTEMPLATE_KEYID_OFF 73
+
 struct buffer_s 
 {
   unsigned char *buffer;
@@ -464,26 +490,151 @@ 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])
+        {
+          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 = iconv_open (charsets[charsetidx], "utf-8");
+          if (cd == (iconv_t)(-1))
+            continue;
+
+          inptr = pw;
+          inbytes = strlen (pw);
+          outptr = convertedpw;
+          outbytes = convertedpwsize - 1;
+          if ( iconv (cd, (ICONV_CONST char **)&inptr, &inbytes,
+                      &outptr, &outbytes) == (size_t)-1) 
+            {
+              iconv_close (cd);
+              continue;
+            }
+          *outptr = 0;
+          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. */
 static int
 parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
                           int startoffset, size_t *r_consumed, const char *pw,
                           void (*certcb)(void*, const unsigned char*, size_t),
-                          void *certcbarg)
+                          void *certcbarg, gcry_mpi_t **r_result)
 {
   struct tag_info ti;
   const unsigned char *p = buffer;
   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;
   int bad_pass = 0;
   unsigned char *cram_buffer = NULL;
   size_t consumed = 0; /* Number of bytes consumed from the orginal buffer. */
-  
+  int is_3des = 0;
+  gcry_mpi_t *result = NULL;
+  int result_count;
+
+  if (r_result)
+    *r_result = NULL;
   where = "start";
   if (parse_tag (&p, &n, &ti))
     goto bailout;
@@ -529,10 +680,19 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
       p += DIM(oid_pbeWithSHAAnd40BitRC2_CBC);
       n -= DIM(oid_pbeWithSHAAnd40BitRC2_CBC);
     }
+  else if (!ti.class && ti.tag == TAG_OBJECT_ID 
+      && ti.length == DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)
+      && !memcmp (p, oid_pbeWithSHAAnd3_KeyTripleDES_CBC,
+                  DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)))
+    {
+      p += DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC);
+      n -= DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC);
+      is_3des = 1;
+    }
   else
     goto bailout;
 
-  where = "rc2-params";
+  where = "rc2or3des-params";
   if (parse_tag (&p, &n, &ti))
     goto bailout;
   if (ti.class || ti.tag != TAG_SEQUENCE)
@@ -540,7 +700,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);
@@ -557,7 +717,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
       n--;
     }
   
-  where = "rc2-ciphertext";
+  where = "rc2or3des-ciphertext";
   if (parse_tag (&p, &n, &ti))
     goto bailout;
 
@@ -566,7 +726,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
     {
       /* Mozilla exported certs now come with single byte chunks of
          octect strings.  (Mozilla Firefox 1.0.4).  Arghh. */
-      where = "cram-rc2-ciphertext";
+      where = "cram-rc2or3des-ciphertext";
       cram_buffer = cram_octet_string ( p, &n, &consumed);
       if (!cram_buffer)
         goto bailout;
@@ -581,7 +741,7 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
   else
     goto bailout;
   
-  log_info ("%lu bytes of RC2 encrypted text\n", ti.length);
+  log_info ("%lu bytes of %s encrypted text\n",ti.length,is_3des?"3DES":"RC2");
 
   plain = gcry_malloc_secure (ti.length);
   if (!plain)
@@ -589,21 +749,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, 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))
     {
@@ -625,7 +777,8 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
   /* Loop over all certificates inside the bag. */
   while (n)
     {
-      int isbag = 0;
+      int iscrlbag = 0;
+      int iskeybag = 0;
 
       where = "certbag.nextcert";
       if (ti.class || ti.tag != TAG_SEQUENCE)
@@ -647,7 +800,17 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
         {
           p += DIM(oid_pkcs_12_CrlBag);
           n -= DIM(oid_pkcs_12_CrlBag);
-          isbag = 1;
+          iscrlbag = 1;
+        }
+      else if ( ti.length == DIM(oid_pkcs_12_keyBag)
+           && !memcmp (p, oid_pkcs_12_keyBag, DIM(oid_pkcs_12_keyBag)))
+        {
+          /* The TrustedMIME plugin for MS Outlook started to create
+             files with just one outer 3DES encrypted container and
+             inside the certificates as well as the key. */
+          p += DIM(oid_pkcs_12_keyBag);
+          n -= DIM(oid_pkcs_12_keyBag);
+          iskeybag = 1;
         }
       else
         goto bailout;
@@ -657,14 +820,106 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
         goto bailout;
       if (ti.class != CONTEXT || ti.tag)
         goto bailout;
-      if (isbag)
+      if (iscrlbag)
         {
           log_info ("skipping unsupported crlBag\n");
           p += ti.length;
           n -= ti.length;
         }
+      else if (iskeybag && (result || !r_result))
+        {
+          log_info ("one keyBag already processed; skipping this one\n");
+          p += ti.length;
+          n -= ti.length;
+        }
+      else if (iskeybag)
+        {
+          int len;
+
+          log_info ("processing simple keyBag\n");
+
+          /* Fixme: This code is duplicated from parse_bag_data.  */
+          if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
+            goto bailout;
+          if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER
+              || ti.length != 1 || *p)
+            goto bailout;
+          p++; n--;
+          if (parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
+            goto bailout;
+          len = ti.length;
+          if (parse_tag (&p, &n, &ti))
+            goto bailout;
+          if (len < ti.nhdr)
+            goto bailout;
+          len -= ti.nhdr;
+          if (ti.class || ti.tag != TAG_OBJECT_ID
+              || ti.length != DIM(oid_rsaEncryption)
+              || memcmp (p, oid_rsaEncryption,
+                         DIM(oid_rsaEncryption)))
+            goto bailout;
+          p += DIM (oid_rsaEncryption);
+          n -= DIM (oid_rsaEncryption);
+          if (len < ti.length)
+            goto bailout;
+          len -= ti.length;
+          if (n < len)
+            goto bailout;
+          p += len;
+          n -= len;
+          if ( parse_tag (&p, &n, &ti)
+               || ti.class || ti.tag != TAG_OCTET_STRING)
+            goto bailout;
+          if ( parse_tag (&p, &n, &ti)
+               || ti.class || ti.tag != TAG_SEQUENCE)
+            goto bailout;
+          len = ti.length;
+
+          result = gcry_calloc (10, sizeof *result);
+          if (!result)
+            {
+              log_error ( "error allocating result array\n");
+              goto bailout;
+            }
+          result_count = 0;
+
+          where = "reading.keybag.key-parameters";
+          for (result_count = 0; len && result_count < 9;)
+            {
+              if ( parse_tag (&p, &n, &ti)
+                   || ti.class || ti.tag != TAG_INTEGER)
+                goto bailout;
+              if (len < ti.nhdr)
+                goto bailout;
+              len -= ti.nhdr;
+              if (len < ti.length)
+                goto bailout;
+              len -= ti.length;
+              if (!result_count && ti.length == 1 && !*p)
+                ; /* ignore the very first one if it is a 0 */
+              else 
+                {
+                  int rc;
+
+                  rc = gcry_mpi_scan (result+result_count, GCRYMPI_FMT_USG, p,
+                                      ti.length, NULL);
+                  if (rc)
+                    {
+                      log_error ("error parsing key parameter: %s\n",
+                                 gpg_strerror (rc));
+                      goto bailout;
+                    }
+                  result_count++;
+                }
+              p += ti.length;
+              n -= ti.length;
+            }
+          if (len)
+            goto bailout;
+        }
       else
         {
+          log_info ("processing certBag\n");
           if (parse_tag (&p, &n, &ti))
             goto bailout;
           if (ti.class || ti.tag != TAG_SEQUENCE)
@@ -730,15 +985,25 @@ parse_bag_encrypted_data (const unsigned char *buffer, size_t length,
     *r_consumed = consumed;
   gcry_free (plain);
   gcry_free (cram_buffer);
+  if (r_result)
+    *r_result = result;
   return 0;
 
  bailout:
+  if (result)
+    {
+      int i;
+
+      for (i=0; result[i]; i++)
+        gcry_mpi_release (result[i]);
+      gcry_free (result);
+    }
   if (r_consumed)
     *r_consumed = consumed;
   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
@@ -749,6 +1014,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)
@@ -759,7 +1052,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;
@@ -847,7 +1140,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);
@@ -878,22 +1171,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;
@@ -983,7 +1268,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;
@@ -1066,7 +1351,7 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw,
   bagseqlength = ti.length;
   while (bagseqlength || bagseqndef)
     {
-      log_debug ( "at offset %u\n", (p - p_start));
+/*       log_debug ( "at offset %u\n", (p - p_start)); */
       where = "bag-sequence";
       if (parse_tag (&p, &n, &ti))
         goto bailout;
@@ -1105,7 +1390,8 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw,
             len -= DIM(oid_encryptedData);
           where = "bag.encryptedData";
           if (parse_bag_encrypted_data (p, n, (p - p_start), &consumed, pw,
-                                        certcb, certcbarg))
+                                        certcb, certcbarg,
+                                        result? NULL : &result))
             goto bailout;
           if (lenndef)
             len += consumed;
@@ -1115,7 +1401,7 @@ p12_parse (const unsigned char *buffer, size_t length, const char *pw,
         {
           if (result)
             {
-              log_info ("already got an data object, skipping next one\n");
+              log_info ("already got an key object, skipping this one\n");
               p += ti.length;
               n -= ti.length;
             }
@@ -1158,8 +1444,16 @@ 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));
-  /* fixme: need to release RESULT. */
+  log_error ("error at \"%s\", offset %u\n",
+             where, (unsigned int)(p - p_start));
+  if (result)
+    {
+      int i;
+
+      for (i=0; result[i]; i++)
+        gcry_mpi_release (result[i]);
+      gcry_free (result);
+    }
   gcry_free (cram_buffer);
   return NULL;
 }
@@ -1227,6 +1521,8 @@ create_final (struct buffer_s *sequences, const char *pw, size_t *r_length)
   unsigned char keybuf[20];
   gcry_md_hd_t md;
   int rc;
+  int with_mac = 1;
+
 
   /* 9 steps to create the pkcs#12 Krampf. */
 
@@ -1264,7 +1560,8 @@ create_final (struct buffer_s *sequences, const char *pw, size_t *r_length)
   needed += 3;
 
   /* 0. And the final outer sequence. */
-  needed += DIM (data_mactemplate);
+  if (with_mac)
+    needed += DIM (data_mactemplate);
   len[0] = needed;
   n = compute_tag_length (needed);
   needed += n;
@@ -1311,37 +1608,40 @@ create_final (struct buffer_s *sequences, const char *pw, size_t *r_length)
       p += sequences[i].length;
     }
 
-  /* Intermezzo to compute the MAC. */
-  maclen = p - macstart;
-  gcry_randomize (salt, 8, GCRY_STRONG_RANDOM);
-  if (string_to_key (3, salt, 8, 2048, pw, 20, keybuf))
+  if (with_mac)
     {
-      gcry_free (result);
-      return NULL;
-    }
-  rc = gcry_md_open (&md, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC);
-  if (rc)
-    {
-      log_error ("gcry_md_open failed: %s\n", gpg_strerror (rc));
-      gcry_free (result);
-      return NULL;
-    }
-  rc = gcry_md_setkey (md, keybuf, 20);
-  if (rc)
-    {
-      log_error ("gcry_md_setkey failed: %s\n", gpg_strerror (rc));
+      /* Intermezzo to compute the MAC. */
+      maclen = p - macstart;
+      gcry_randomize (salt, 8, GCRY_STRONG_RANDOM);
+      if (string_to_key (3, salt, 8, 2048, pw, 20, keybuf))
+        {
+          gcry_free (result);
+          return NULL;
+        }
+      rc = gcry_md_open (&md, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC);
+      if (rc)
+        {
+          log_error ("gcry_md_open failed: %s\n", gpg_strerror (rc));
+          gcry_free (result);
+          return NULL;
+        }
+      rc = gcry_md_setkey (md, keybuf, 20);
+      if (rc)
+        {
+          log_error ("gcry_md_setkey failed: %s\n", gpg_strerror (rc));
+          gcry_md_close (md);
+          gcry_free (result);
+          return NULL;
+        }
+      gcry_md_write (md, macstart, maclen);
+
+      /* 8. Append the MAC template and fix it up. */
+      memcpy (p, data_mactemplate, DIM (data_mactemplate));
+      memcpy (p + DATA_MACTEMPLATE_SALT_OFF, salt, 8);
+      memcpy (p + DATA_MACTEMPLATE_MAC_OFF, gcry_md_read (md, 0), 20);
+      p += DIM (data_mactemplate);
       gcry_md_close (md);
-      gcry_free (result);
-      return NULL;
     }
-  gcry_md_write (md, macstart, maclen);
-
-  /* 8. Append the MAC template and fix it up. */
-  memcpy (p, data_mactemplate, DIM (data_mactemplate));
-  memcpy (p + DATA_MACTEMPLATE_SALT_OFF, salt, 8);
-  memcpy (p + DATA_MACTEMPLATE_MAC_OFF, gcry_md_read (md, 0), 20);
-  p += DIM (data_mactemplate);
-  gcry_md_close (md);
 
   /* Ready. */
   resultlen = p - result;
@@ -1501,6 +1801,7 @@ build_key_sequence (gcry_mpi_t *kparms, size_t *r_length)
 
 static unsigned char *
 build_key_bag (unsigned char *buffer, size_t buflen, char *salt,
+               const unsigned char *sha1hash, const char *keyidstr,
                size_t *r_length)
 {
   size_t len[11], needed;
@@ -1524,6 +1825,10 @@ build_key_bag (unsigned char *buffer, size_t buflen, char *salt,
   len[7] = needed;
   needed += compute_tag_length (needed);
 
+  /* 6b. The attributes which are appended at the end. */
+  if (sha1hash)
+    needed += DIM (data_attrtemplate) + 20;
+
   /* 6. Prepend the shroudedKeyBag OID. */
   needed += 2 + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag);
 
@@ -1594,12 +1899,26 @@ build_key_bag (unsigned char *buffer, size_t buflen, char *salt,
   memcpy (p + DATA_3DESITER2048_SALT_OFF, salt, 8);
   p += DIM (data_3desiter2048);
 
-  /* 10. And finally the octet string with the encrypted data. */
+  /* 10. And the octet string with the encrypted data. */
   p = store_tag_length (p, TAG_OCTET_STRING, buflen);
   memcpy (p, buffer, buflen);
   p += buflen;
+
+  /* Append the attributes whose length we calculated at step 2b. */
+  if (sha1hash)
+    {
+      int i;
+
+      memcpy (p, data_attrtemplate, DIM (data_attrtemplate));
+      for (i=0; i < 8; i++)
+        p[DATA_ATTRTEMPLATE_KEYID_OFF+2*i+1] = keyidstr[i];
+      p += DIM (data_attrtemplate);
+      memcpy (p, sha1hash, 20);
+      p += 20;
+    }
+
+
   keybaglen = p - keybag;
-  
   if (needed != keybaglen)
     log_debug ("length mismatch: %lu, %lu\n",
                (unsigned long)needed, (unsigned long)keybaglen);
@@ -1709,13 +2028,17 @@ build_cert_bag (unsigned char *buffer, size_t buflen, char *salt,
 
 
 static unsigned char *
-build_cert_sequence (unsigned char *buffer, size_t buflen, size_t *r_length)
+build_cert_sequence (unsigned char *buffer, size_t buflen, 
+                     const unsigned char *sha1hash, const char *keyidstr,
+                     size_t *r_length)
 {
   size_t len[8], needed, n;
   unsigned char *p, *certseq;
   size_t certseqlen;
   int i;
 
+  assert (strlen (keyidstr) == 8);
+
   /* Walk 8 steps down to collect the info: */
 
   /* 7. The data goes into an octet string. */
@@ -1737,6 +2060,10 @@ build_cert_sequence (unsigned char *buffer, size_t buflen, size_t *r_length)
   len[3] = needed;
   needed += compute_tag_length (needed);
 
+  /* 2b. The attributes which are appended at the end. */
+  if (sha1hash)
+    needed += DIM (data_attrtemplate) + 20;
+
   /* 2. An OID. */
   needed += 2 + DIM (oid_pkcs_12_CertBag);
 
@@ -1785,16 +2112,27 @@ build_cert_sequence (unsigned char *buffer, size_t buflen, size_t *r_length)
   /* 6. Store a [0] tag. */
   p = store_tag_length (p, 0xa0, len[6]);
 
-  /* 7. And finally the octet string with the actual certificate. */
+  /* 7. And the octet string with the actual certificate. */
   p = store_tag_length (p, TAG_OCTET_STRING, buflen);
   memcpy (p, buffer, buflen);
   p += buflen;
-  certseqlen = p - certseq;
   
+  /* Append the attributes whose length we calculated at step 2b. */
+  if (sha1hash)
+    {
+      memcpy (p, data_attrtemplate, DIM (data_attrtemplate));
+      for (i=0; i < 8; i++)
+        p[DATA_ATTRTEMPLATE_KEYID_OFF+2*i+1] = keyidstr[i];
+      p += DIM (data_attrtemplate);
+      memcpy (p, sha1hash, 20);
+      p += 20;
+    }
+
+  certseqlen = p - certseq;
   if (needed != certseqlen)
     log_debug ("length mismatch: %lu, %lu\n",
                (unsigned long)needed, (unsigned long)certseqlen);
-  
+
   /* Append some pad characters; we already allocated extra space. */
   n = 8 - certseqlen % 8;
   for (i=0; i < n; i++, certseqlen++)
@@ -1805,25 +2143,85 @@ build_cert_sequence (unsigned char *buffer, size_t buflen, size_t *r_length)
 }
 
 
-/* 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)
+    {
+      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 = iconv_open (charset, "utf-8");
+      if (cd == (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 ( iconv (cd, (ICONV_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);
+          iconv_close (cd);
+          goto failure;
+        }
+      *outptr = 0;
+      iconv_close (cd);
+      pw = pwbuf;
+    }
+
 
   if (cert && certlen)
     {
+      /* Calculate the hash value we need for the bag attributes. */
+      gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash, cert, certlen);
+      sprintf (keyidstr, "%02x%02x%02x%02x",
+               sha1hash[16], sha1hash[17], sha1hash[18], sha1hash[19]);
+
       /* Encode the certificate. */
-      buffer = build_cert_sequence (cert, certlen, &buflen);
+      buffer = build_cert_sequence (cert, certlen, sha1hash, keyidstr,
+                                    &buflen);
       if (!buffer)
         goto failure;
 
@@ -1842,6 +2240,7 @@ p12_build (gcry_mpi_t *kparms, unsigned char *cert, size_t certlen,
       seqlistidx++;
     }
 
+
   if (kparms)
     {
       /* Encode the key. */
@@ -1854,7 +2253,12 @@ p12_build (gcry_mpi_t *kparms, unsigned char *cert, size_t certlen,
       crypt_block (buffer, buflen, salt, 8, 2048, pw, GCRY_CIPHER_3DES, 1);
 
       /* Encode the encrypted stuff into a bag. */
-      seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt, &n);
+      if (cert && certlen)
+        seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt, 
+                                                    sha1hash, keyidstr, &n);
+      else
+        seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt,
+                                                    NULL, NULL, &n);
       seqlist[seqlistidx].length = n;
       gcry_free (buffer);
       buffer = NULL;
@@ -1869,6 +2273,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);
 
@@ -1952,7 +2361,7 @@ main (int argc, char **argv)
 
 /*
 Local Variables:
-compile-command: "gcc -Wall -O -g -DTEST=1 -o minip12 minip12.c ../jnlib/libjnlib.a -L /usr/local/lib -lgcrypt -lgpg-error"
+compile-command: "gcc -Wall -O0 -g -DTEST=1 -o minip12 minip12.c ../jnlib/libjnlib.a -L /usr/local/lib -lgcrypt -lgpg-error"
 End:
 */
 #endif /* TEST */