workaround for CryptoEx wrongly marking signed messages as encrypted.
authorWerner Koch <wk@gnupg.org>
Mon, 11 Feb 2008 10:36:20 +0000 (10:36 +0000)
committerWerner Koch <wk@gnupg.org>
Mon, 11 Feb 2008 10:36:20 +0000 (10:36 +0000)
src/ChangeLog
src/common.c
src/common.h
src/mapihelp.cpp

index 783778d..0f11a6f 100644 (file)
@@ -1,3 +1,12 @@
+2008-02-11  Werner Koch  <wk@g10code.com>
+
+       * common.h (tlvinfo_t): New.
+       * common.c (parse_tlv): New.  Based on code from libksba.
+       * mapihelp.cpp (has_smime_filename): New.
+       (is_really_cms_encrypted): New.
+       (mapi_change_message_class): Use this here to work around a
+       CryptoEx bug.
+
 2008-02-08  Werner Koch  <wk@g10code.com>
 
        * mapihelp.cpp (mapi_change_message_class): Improve detecion of
index fd28bcf..c94dd8c 100644 (file)
@@ -1,5 +1,5 @@
 /* common.c - Common routines used by GpgOL
- *     Copyright (C) 2005, 2007 g10 Code GmbH
+ *     Copyright (C) 2005, 2007, 2008 g10 Code GmbH
  *
  * This file is part of GpgOL.
  *
@@ -788,3 +788,85 @@ gpgol_spawn_detached (const char *cmdline)
   CloseHandle (pi.hProcess);
   return 0;
 }
+
+
+
+
+/* Simple but pretty complete ASN.1 BER parser.  Parse the data at the
+   address of BUFFER with a length given at the address of SIZE.  On
+   success return 0 and update BUFFER and SIZE to point to the value.
+   Do not update them on error.  The information about the object are
+   stored in the caller allocated TI structure.  */
+int
+parse_tlv (char const **buffer, size_t *size, tlvinfo_t *ti)
+{
+  int c;
+  unsigned long tag;
+  const unsigned char *buf = (const unsigned char *)(*buffer);
+  size_t length = *size;
+
+  ti->cls = 0;
+  ti->tag = 0;
+  ti->is_cons = 0;
+  ti->is_ndef = 0;
+  ti->length = 0;
+  ti->nhdr = 0;
+
+  if (!length)
+    return -1;
+  c = *buf++; length--; ++ti->nhdr;
+
+  ti->cls = (c & 0xc0) >> 6;
+  ti->is_cons = !!(c & 0x20);
+  tag = c & 0x1f;
+
+  if (tag == 0x1f)
+    {
+      tag = 0;
+      do
+        {
+          tag <<= 7;
+          if (!length)
+            return -1;
+          c = *buf++; length--; ++ti->nhdr;
+          tag |= c & 0x7f;
+        }
+      while (c & 0x80);
+    }
+  ti->tag = tag;
+
+  if (!length)
+    return -1;
+  c = *buf++; length--; ++ti->nhdr;
+
+  if ( !(c & 0x80) )
+    ti->length = c;
+  else if (c == 0x80)
+    ti->is_ndef = 1;
+  else if (c == 0xff)
+    return -1;
+  else
+    {
+      unsigned long len = 0;
+      int count = (c & 0x7f);
+
+      if (count > sizeof (len) || count > sizeof (size_t))
+        return -1;
+
+      for (; count; count--)
+        {
+          len <<= 8;
+          if (!length)
+            return -1;
+          c = *buf++; length--; ++ti->nhdr;
+          len |= c & 0xff;
+        }
+      ti->length = len;
+    }
+  
+  *buffer = buf;
+  *size = length;
+  return 0;
+}
+
+
index d2c8b51..3f2375d 100644 (file)
@@ -1,6 +1,6 @@
 /* common.h - Common declarations for GpgOL
  *     Copyright (C) 2004 Timo Schulz
- *     Copyright (C) 2005, 2006, 2007 g10 Code GmbH
+ *     Copyright (C) 2005, 2006, 2007, 2008 g10 Code GmbH
  *
  * This file is part of GpgOL.
  *
@@ -151,6 +151,27 @@ typedef struct b64_state_s b64_state_t;
 #define DBG_FILTER_EXTRA   16 
 #define DBG_MEMORY         32
 
+
+/* Type and constants used with parse_tlv.  */
+struct tlvinfo_s
+{
+  int cls;            /* The class of the tag.  */             
+  int tag;            /* The tag.  */           
+  int is_cons;        /* True if it is a constructed object.  */
+  int is_ndef;        /* True if the object has an indefinite length.  */
+  size_t length;      /* The length of the value.  */
+  size_t nhdr;        /* The number of octets in the header (tag,length). */
+};
+typedef struct tlvinfo_s tlvinfo_t;
+#define MY_ASN_CLASS_UNIVERSAL   0
+#define MY_ASN_CLASS_APPLICATION 1
+#define MY_ASN_CLASS_CONTEXT     2
+#define MY_ASN_CLASS_PRIVATE     3
+#define MY_ASN_TAG_OBJECT_ID     6
+#define MY_ASN_TAG_SEQUENCE     16
+
+
+
 /*-- common.c --*/
 void set_global_hinstance (HINSTANCE hinst);
 void center_window (HWND childwnd, HWND style);
@@ -171,6 +192,8 @@ char *generate_boundary (char *buffer);
 
 int gpgol_spawn_detached (const char *cmdline);
 
+int parse_tlv (char const **buffer, size_t *size, tlvinfo_t *ti);
+
 
 /*-- recipient-dialog.c --*/
 unsigned int recipient_dialog_box (gpgme_key_t **ret_rset);
index 35e4c2e..25db89a 100644 (file)
                         } while (0)
 
 
+static int get_attach_method (LPATTACH obj);
+static int has_smime_filename (LPATTACH obj);
+
+
+\f
+
 /* Print a MAPI property to the log stream. */
 void
 log_mapi_property (LPMESSAGE message, ULONG prop, const char *propname)
@@ -483,6 +489,133 @@ get_msgcls_from_pgp_lines (LPMESSAGE message)
 }
 
 
+/* Check whether the message is really a CMS encrypted message.  This
+   function is required due to a bug in CryptoEx which sometimes
+   assignes the *.CexEnc message class to signed messages and only
+   updates the message class after accessing them.  Thus in old stores
+   there may be a lot of *.CexEnc message which are actually just
+   signed.  We check here whether such a message is really encrypted
+   by looking at the object identifier inside the CMS data.  Returns
+   true if the message is really encrypted.  */
+static int
+is_really_cms_encrypted (LPMESSAGE message)
+{    
+  HRESULT hr;
+  SizedSPropTagArray (1L, propAttNum) = { 1L, {PR_ATTACH_NUM} };
+  LPMAPITABLE mapitable;
+  LPSRowSet   mapirows;
+  unsigned int pos, n_attach;
+  int is_encrypted = 0;
+  LPATTACH att = NULL;
+  LPSTREAM stream = NULL;
+  char buffer[24];  /* 24 bytes are more than enough to peek at.
+                       Cf. ksba_cms_identify() from the libksba
+                       package.  */
+  const char *p;
+  ULONG nread;
+  size_t n;
+  tlvinfo_t ti;
+
+  hr = message->GetAttachmentTable (0, &mapitable);
+  if (FAILED (hr))
+    {
+      log_debug ("%s:%s: GetAttachmentTable failed: hr=%#lx",
+                 SRCNAME, __func__, hr);
+      return 0;
+    }
+      
+  hr = HrQueryAllRows (mapitable, (LPSPropTagArray)&propAttNum,
+                       NULL, NULL, 0, &mapirows);
+  if (FAILED (hr))
+    {
+      log_debug ("%s:%s: HrQueryAllRows failed: hr=%#lx",
+                 SRCNAME, __func__, hr);
+      mapitable->Release ();
+      return 0;
+    }
+  n_attach = mapirows->cRows > 0? mapirows->cRows : 0;
+  if (n_attach != 1)
+    {
+      FreeProws (mapirows);
+      mapitable->Release ();
+      log_debug ("%s:%s: not just one attachments", SRCNAME, __func__);
+      return 0;
+    }
+  pos = 0;
+
+  if (mapirows->aRow[pos].cValues < 1)
+    {
+      log_error ("%s:%s: invalid row at pos %d", SRCNAME, __func__, pos);
+      goto leave;
+    }
+  if (mapirows->aRow[pos].lpProps[0].ulPropTag != PR_ATTACH_NUM)
+    {
+      log_error ("%s:%s: invalid prop at pos %d", SRCNAME, __func__, pos);
+      goto leave;
+    }
+  hr = message->OpenAttach (mapirows->aRow[pos].lpProps[0].Value.l,
+                            NULL, MAPI_BEST_ACCESS, &att);     
+  if (FAILED (hr))
+    {
+      log_error ("%s:%s: can't open attachment %d (%ld): hr=%#lx",
+                 SRCNAME, __func__, pos, 
+                 mapirows->aRow[pos].lpProps[0].Value.l, hr);
+      goto leave;
+    }
+  if (!has_smime_filename (att))
+    goto leave;
+  if (get_attach_method (att) != ATTACH_BY_VALUE)
+    goto leave;
+  
+  hr = att->OpenProperty (PR_ATTACH_DATA_BIN, &IID_IStream, 
+                          0, 0, (LPUNKNOWN*) &stream);
+  if (FAILED (hr))
+    {
+      log_error ("%s:%s: can't open data stream of attachment: hr=%#lx",
+                 SRCNAME, __func__, hr);
+      goto leave;
+    }
+
+  hr = stream->Read (buffer, sizeof buffer, &nread);
+  if ( hr != S_OK )
+    {
+      log_error ("%s:%s: Read failed: hr=%#lx", SRCNAME, __func__, hr);
+      goto leave;
+    }
+  if (nread < sizeof buffer)
+    {
+      log_error ("%s:%s: not enough bytes returned\n", SRCNAME, __func__);
+      goto leave;
+    }
+
+  p = buffer;
+  n = nread;
+  if (parse_tlv (&p, &n, &ti))
+    goto leave;
+  if (!(ti.cls == MY_ASN_CLASS_UNIVERSAL && ti.tag == MY_ASN_TAG_SEQUENCE
+        && ti.is_cons) )
+    goto leave;
+  if (parse_tlv (&p, &n, &ti))
+    goto leave;
+  if (!(ti.cls == MY_ASN_CLASS_UNIVERSAL && ti.tag == MY_ASN_TAG_OBJECT_ID
+        && !ti.is_cons && ti.length) || ti.length > n)
+    goto leave;
+  /* Now is this enveloped data (1.2.840.113549.1.7.3)?  */
+  if (ti.length == 9 && !memcmp (p, "\x2A\x86\x48\x86\xF7\x0D\x01\x07\x03", 9))
+    is_encrypted = 1;
+
+
+ leave:
+  if (stream)
+    stream->Release ();
+  if (att)
+    att->Release ();
+  FreeProws (mapirows);
+  mapitable->Release ();
+  return !!is_encrypted;
+}
+
+
 
 /* This function checks whether MESSAGE requires processing by us and
    adjusts the message class to our own.  By passing true for
@@ -647,7 +780,12 @@ mapi_change_message_class (LPMESSAGE message, int sync_override)
               log_debug ("%s:%s: message has no content type", 
                          SRCNAME, __func__);
               if (cexenc)
-                newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
+                {
+                  if (is_really_cms_encrypted (message))
+                    newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
+                  else
+                    newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
+                }
             }
           else
             {
@@ -687,8 +825,13 @@ mapi_change_message_class (LPMESSAGE message, int sync_override)
                   newvalue = get_msgcls_from_pgp_lines (message);
                 }
 
-              if (!newvalue && cexenc)
-                newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
+              if (!newvalue)
+                {
+                  if (is_really_cms_encrypted (message))
+                    newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueEncrypted");
+                  else
+                    newvalue = xstrdup ("IPM.Note.GpgOL.OpaqueSigned");
+                }
 
               xfree (smtype);
               xfree (proto);
@@ -1366,7 +1509,7 @@ attach_to_buffer (LPATTACH att, size_t *r_nbytes, int unprotect,
 
 
 
-/* Return an attachment as a malloced buffer; The size of the buffer
+/* Return an attachment as a malloced buffer The size of the buffer
    will be stored at R_NBYTES.  Returns NULL on failure. */
 char *
 mapi_get_attach (LPMESSAGE message, mapi_attach_item_t *item, size_t *r_nbytes)
@@ -1865,6 +2008,33 @@ has_gpgol_body_name (LPATTACH obj)
   return yes;
 }
 
+/* Helper to check whether the file name of OBJ is "smime.p7m".
+   Returns on true if so.  */
+static int
+has_smime_filename (LPATTACH obj)
+{
+  HRESULT hr;
+  LPSPropValue propval;
+  int yes = 0;
+
+  hr = HrGetOneProp ((LPMAPIPROP)obj, PR_ATTACH_FILENAME, &propval);
+  if (FAILED(hr))
+    return 0;
+
+  if ( PROP_TYPE (propval->ulPropTag) == PT_UNICODE)
+    {
+      if (!wcscmp (propval->Value.lpszW, L"smime.p7m"))
+        yes = 1;
+    }
+  else if ( PROP_TYPE (propval->ulPropTag) == PT_STRING8)
+    {
+      if (!strcmp (propval->Value.lpszA, "smime.p7m"))
+        yes = 1;
+    }
+  MAPIFreeBuffer (propval);
+  return yes;
+}
+
 
 /* Return the content of the body attachment of MESSAGE.  The body
    attachment is a hidden attachment created by us for later display.