<<< Date Index >>>     <<< Thread Index >>>

Re: a Message-ID proposal



Here's a patch that implements a simplified version of my proposal. I
don't suppose this is of great general interest, but perhaps someone
else might want to use it.

Encrypting the process ID is an interesting exercise in cryptography,
but of dubious utility. Consider the case where a long-running
interactive mutt sends a message just before exiting, and a
command-line mutt sends a message half a second later; I would guess
the two could have the same process ID. So I'm just using a random
"uniquifier".

The patch does nothing unless you configure with --enable-macmid, in
which case it still does nothing unless you set the variable
msgid_key.

Here's a Perl script for authenticating the Message-IDs. I currently
have my msgid_key set to "secret", so with any luck the script should
say "Good MAC" if you invoke it with "secret" and the Message-ID of
this message as arguments.

I'm quite happy with my current approach of automatically whitelisting
the envelope senders of messages which have one of my Message-IDs in
the header.

----------------------------------------

#!/usr/bin/perl -w

use Digest::MD5 qw(md5), qw(md5_base64);
use MIME::Base64;

my ($mackey, $msgid) = @ARGV;

$mackey = md5($mackey) . "\0" x 48;

if ($msgid =~
    /^(\d{14})\.([A-Za-z0-9\+\$]+)\.([A-Za-z0-9\+\$]+)\@([^@]+)$/) {
    my ($time, $uniq, $mac, $domain) = ($1, $2, $3, $4);
    my $mac1 = substr(&hmac($mackey, "$time.$uniq\@$domain"),
                      0, length($mac));
    if ($mac eq $mac1) {
        print "Good MAC\n";
    }
    else {
        print "Bad MAC\n";
    }
}
else {
    print "Ill-formed Message-ID\n";
}

# Test vectors from RFC 2104.
&test("\xb" x 16, "Hi There",
      "9294727a3638bb1c13f48ef8158bfc9d");
&test("Jefe", "what do ya want for nothing?",
      "750c783e6ab0b503eaa86e310a5db738");
&test("\xaa" x 16, "\xdd" x 50,
      "56be34521d144c88dbb8c733f0e8b3f6");

sub test {
    my ($key, $data, $digest) = @_;
    my $mac = &hmac(substr($key . "\0" x 64, 0, 64), $data);
    $mac =~ s!$!/!g;
    $mac = decode_base64($mac . "=");
    my $digest1 = "";
    foreach my $i (0..15) {
        $digest1 .= sprintf("%02x", ord(substr($mac, $i, 1)));
    }
    die unless $digest1 eq $digest;
}

# HMAC from RFC 2104.

sub hmac {
    my ($key, $text) = @_;
    die unless length($key) == 64;
    my $mac = md5_base64(($key ^ ("\x5c" x 64)) .
                         md5(($key ^ ("\x36" x 64)) . $text));
    $mac =~ tr!/!$!;
    return $mac;
}

----------------------------------------

Index: configure.in
===================================================================
RCS file: /home/roessler/cvs/mutt/configure.in,v
retrieving revision 3.14
diff -u -r3.14 configure.in
--- configure.in        17 Jun 2004 20:36:13 -0000      3.14
+++ configure.in        6 Jul 2004 20:06:03 -0000
@@ -692,6 +692,13 @@
    AC_CHECK_FUNCS(idna_to_ascii_lz idna_to_ascii_from_locale)
 fi
 
+AC_ARG_ENABLE(macmid, [  --enable-macmid            Enable MAC-authenticated 
Message-IDs],
+       [if test $enableval = yes ; then
+               AC_DEFINE(USE_MACMID, 1,
+                         [Define to enable generation of MAC-authenticated 
Message-IDs])
+               need_md5="yes"
+       fi])    
+
 if test "$need_md5" = "yes"
 then
         MUTT_LIB_OBJECTS="$MUTT_LIB_OBJECTS md5c.o"
Index: globals.h
===================================================================
RCS file: /home/roessler/cvs/mutt/globals.h,v
retrieving revision 3.8
diff -u -r3.8 globals.h
--- globals.h   12 Apr 2004 19:42:10 -0000      3.8
+++ globals.h   6 Jul 2004 20:06:04 -0000
@@ -68,6 +68,9 @@
 WHERE char *MhReplied;
 WHERE char *MhUnseen;
 WHERE char *MsgFmt;
+#ifdef USE_MACMID
+WHERE char *MsgidKey;
+#endif
 
 #ifdef USE_SOCKET
 WHERE char *Preconnect INITVAL (NULL);
Index: init.h
===================================================================
RCS file: /home/roessler/cvs/mutt/init.h,v
retrieving revision 3.48
diff -u -r3.48 init.h
--- init.h      17 Jun 2004 20:36:13 -0000      3.48
+++ init.h      6 Jul 2004 20:06:20 -0000
@@ -2703,6 +2703,16 @@
   ** Controls whether mutt writes out the Bcc header when preparing
   ** messages to be sent.  Exim users may wish to use this.
   */
+#ifdef USE_MACMID
+  { "msgid_key",               DT_STR,  R_NONE, UL &MsgidKey, UL 0 },
+  /*
+  ** .pp
+  ** Secret key for MAC in Message-ID. If you set this to a secret and
+  ** unguessable value then you can reliably recognise your own Message-IDs,
+  ** which can be useful in some circumstances, such as spam filtering.
+  ** If you leave this unset, the old style of Message-IDs will be used.
+  */
+#endif
   /*--*/
   { NULL }
 };
Index: main.c
===================================================================
RCS file: /home/roessler/cvs/mutt/main.c,v
retrieving revision 3.16
diff -u -r3.16 main.c
--- main.c      17 Jun 2004 20:36:13 -0000      3.16
+++ main.c      6 Jul 2004 20:06:23 -0000
@@ -266,6 +266,12 @@
        "-USE_GNU_REGEX  "
 #endif
 
+#ifdef USE_MACMID
+       "+USE_MACMID  "
+#else
+       "-USE_MACMID  "
+#endif
+
        "\n"
        
 #ifdef HAVE_COLOR
Index: sendlib.c
===================================================================
RCS file: /home/roessler/cvs/mutt/sendlib.c,v
retrieving revision 3.25
diff -u -r3.25 sendlib.c
--- sendlib.c   12 Apr 2004 21:19:27 -0000      3.25
+++ sendlib.c   6 Jul 2004 20:06:32 -0000
@@ -30,6 +30,7 @@
 #include "charset.h"
 #include "mutt_crypt.h"
 #include "mutt_idna.h"
+#include "md5.h"
 
 #include <string.h>
 #include <stdlib.h>
@@ -1758,7 +1759,13 @@
   return p;
 }
 
-char *mutt_gen_msgid (void)
+/*
+ * Generate a Message-ID of the form YYYYMMDDHHMMSS.Gxn@domain,
+ * where YYYYMMDDHHMMSS is the date and time, 'G' denotes GMT, x is an
+ * upper-case letter, incremented each time, and n is the process ID.
+ */
+
+static char *mutt_gen_msgid_old ()
 {
   char buf[SHORT_STRING];
   time_t now;
@@ -1776,6 +1783,115 @@
   MsgIdPfx = (MsgIdPfx == 'Z') ? 'A' : MsgIdPfx + 1;
   return (safe_strdup (buf));
 }
+
+#ifdef USE_MACMID
+
+/*
+ * Generate a Message-ID of the form YYYYMMDDHHMMSS.xxxxxx.yyyyyy@domain,
+ * where YYYYMMDDHHMMSS is the date and time, xxxxxx is some random base64,
+ * and yyyyyy is a prefix of a base64 HMAC calculated according to RFC 2104
+ * from MsgidKey and the string "YYYYMMDDHHMMSS.xxxxxx@domain", that is,
+ * H(K XOR opad, H(K XOR ipad, text)).
+ * We use '$' instead of '/' in the base64 encoding here.
+ */
+
+char *mutt_gen_msgid ()
+{
+  static char *saved_key = 0;
+  static MD5_CTX msgid_mdContext1, msgid_mdContext2;
+
+  MD5_CTX mdContext;
+  unsigned char digest[16];
+  char time_rand[32];
+  const char *fqdn;
+  int i;
+
+  if (!MsgidKey || !*MsgidKey)
+    return mutt_gen_msgid_old ();
+
+  /*
+   * Since msgid_mdContext1 and msgid_mdContext2 depend only on MsgidKey
+   * we cache the previous values to avoid unnecessary recomputation.
+   */
+  if (mutt_strcmp (MsgidKey, saved_key))
+  {
+    unsigned char block[64];
+    char *key;
+
+    mutt_str_replace (&saved_key, MsgidKey);
+    key = NONULL (saved_key);
+    MD5Init (&mdContext);
+    MD5Update (&mdContext, (unsigned char *)key, strlen (key));
+    MD5Final (digest, &mdContext);
+    memset (block, 0, 64);
+    memcpy (block, digest, 16);
+    for (i = 0; i < 64; i++)
+      block[i] ^= 0x36;
+    MD5Init (&msgid_mdContext1);
+    MD5Update (&msgid_mdContext1, block, 64);
+    for (i = 0; i < 64; i++)
+      block[i] ^= 0x36 ^ 0x5c;
+    MD5Init (&msgid_mdContext2);
+    MD5Update (&msgid_mdContext2, block, 64);
+  }
+
+  {
+    time_t now = time (NULL);
+    struct tm *tm = gmtime (&now);
+    char *p;
+
+    p = time_rand;
+    p += sprintf (p, "%d%02d%02d%02d%02d%02d.",
+                 tm->tm_year + 1900, tm->tm_mon + 1, tm->tm_mday,
+                 tm->tm_hour, tm->tm_min, tm->tm_sec);
+    for (i = 0; i < 6; i++)
+    {
+      char c = B64Chars[LRAND() % sizeof (B64Chars)];
+      *p++ = (c == '/') ? '$' : c;
+    }
+    *p = '\0';
+  }
+
+  if (!(fqdn = mutt_fqdn (0)))
+    fqdn = NONULL (Hostname);
+
+  {
+    char *s = safe_malloc (strlen(time_rand) + strlen(fqdn) + 2);
+
+    sprintf (s, "%s@%s", time_rand, fqdn);
+    mdContext = msgid_mdContext1;
+    MD5Update (&mdContext, (unsigned char *)s, strlen (s));
+    MD5Final (digest, &mdContext);
+    mdContext = msgid_mdContext2;
+    MD5Update (&mdContext, digest, 16);
+    MD5Final (digest, &mdContext);
+    free (s);
+  }
+
+  {
+    char mac[16];
+    char *s, *p;
+
+    mutt_to_base64 ((unsigned char *)mac, digest, 6, sizeof (mac));
+    mac[6] = '\0';
+    for (p = mac; *p; p++)
+      if (*p == '/')
+       *p = '$';
+
+    s = safe_malloc (strlen (time_rand) + strlen (mac) + strlen (fqdn) + 5);
+    sprintf (s, "<%s.%s@%s>", time_rand, mac, fqdn);
+    return s;
+  }
+}
+
+#else /* USE_MACMID */
+
+char *mutt_gen_msgid ()
+{
+  return mutt_gen_msgid_old ();
+}
+
+#endif /* USE_MACMID */
 
 static RETSIGTYPE alarm_handler (int sig)
 {