add org.ibex.mail.VERP
[org.ibex.mail.git] / src / org / ibex / mail / VERP.java
diff --git a/src/org/ibex/mail/VERP.java b/src/org/ibex/mail/VERP.java
new file mode 100644 (file)
index 0000000..19b47db
--- /dev/null
@@ -0,0 +1,92 @@
+// Copyright 2000-2008 the Contributors, as shown in the revision logs.
+// Licensed under the Apache Public Source License 2.0 ("the License").
+// You may not use this file except in compliance with the License.
+
+package org.ibex.mail;
+import org.ibex.crypto.*;
+import org.ibex.js.*;
+import org.ibex.util.*;
+import org.ibex.mail.protocol.*;
+import java.util.*;
+import java.net.*;
+import java.io.*;
+
+// http://cr.yp.to/proto/verp.txt
+public class VERP {
+
+    private static final Random random = new Random();
+
+    public static Address verpSign(Address a, byte[] secret) {
+        return verpSign(a, secret, System.currentTimeMillis());
+    }
+
+    // format:
+    //   VERP--<when>-<salt>-<sig>-<user>@domain.com
+    //     <when> = 64-bit hexadecimal integer
+    //     <salt> = 16-bit hexadecimal integer
+    //     <user> = original username (ie local-part)
+    //     <sig>  = base64(sha1(VERP--<when>-<salt>-<user><secret>))
+    /** converts an Address into a VERP-signed address with signature time "when" and extra payload "extra" */
+    public static Address verpSign(Address a, byte[] secret, long when) {
+        if (a.user==null || "".equals(a.user)) {
+            Log.warn(VERP.class, "note: not VERP-signing the null address: "+a);
+            return a;
+        }
+
+        int salt = Math.abs(random.nextInt()) & 0xffff;
+
+        StringBuffer ret = new StringBuffer();
+        ret.append("VERP--");
+        ret.append(Long.toString(when, 16));
+        ret.append('-');
+        ret.append(Integer.toString(salt, 16));
+        ret.append('-');
+
+        byte[] b = (ret.toString()+a.user).getBytes();
+        SHA1 sha1 = new SHA1();
+        sha1.update(b, 0, b.length);
+        sha1.update(secret, 0, secret.length);
+        b = new byte[sha1.getDigestSize()];
+        sha1.doFinal(b, 0);
+
+        ret.append(new String(Encode.toBase64(b)));
+        ret.append('-');
+        ret.append(a.user);
+        // FIXME: encode a.host in here
+        return new Address(ret.toString(), SMTP.localHostIsMXFor, a.description);
+    }
+
+    // FIXME: return unverpified address
+    /** returns the field passed as "extra" when signing, or null if signature invalid */
+    public static String verpVerify(Address a, byte[] secret, long noEarlierThan) {
+        String s = a.user;
+        int i = 0;
+        i = s.indexOf("--", i); if (i==-1) return null; i += 2;
+        int first = i;
+        i = s.indexOf('-', i);  if (i==-1) return null; i++;
+        int second = i;
+        i = s.indexOf('-', i);  if (i==-1) return null; i++;
+        int third = i;
+        i = s.indexOf('-', i);  if (i==-1) return null; i++;
+        int fourth = i;
+        String verify = s.substring(0,third)+s.substring(fourth);
+        Log.error("VERP", "verify=\""+verify+"\"");
+
+        byte[] b = verify.getBytes();
+        SHA1 sha1 = new SHA1();
+        sha1.update(b, 0, b.length);
+        sha1.update(secret, 0, secret.length);
+        b = new byte[sha1.getDigestSize()];
+        sha1.doFinal(b, 0);
+        if (!new String(Encode.toBase64(b)).equals(s.substring(third,fourth-1))) {
+            Log.error("VERP", "decrypt failed\n"+new String(Encode.toBase64(b))+"\n"+s.substring(third,fourth-1));
+            return null;
+        }
+        // FIXME: check the host on the address
+        long when = Long.parseLong(s.substring(first, second-1), 16);
+        Log.error("VERP", "when="+when);
+        if (when < noEarlierThan) return null;
+        return s.substring(fourth);
+    }
+
+}