You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by ro...@apache.org on 2019/06/18 08:27:58 UTC

[james-project] 01/14: JAMES-1975 Move JDKIM mailet as a server mailet

This is an automated email from the ASF dual-hosted git repository.

rouazana pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 3c79a9fc019c8ffd5d5167d8daf60f6b22b272d6
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Fri Aug 31 17:39:28 2018 +0700

    JAMES-1975 Move JDKIM mailet as a server mailet
    
    We introduce it in a separate maven module.
    
    All code was just copied.
---
 server/mailet/dkim/pom.xml                         | 106 ++++++
 .../james/jdkim/mailets/CRLFOutputStream.java      | 161 +++++++++
 .../apache/james/jdkim/mailets/ConvertTo7Bit.java  |  77 +++++
 .../org/apache/james/jdkim/mailets/DKIMSign.java   | 173 ++++++++++
 .../org/apache/james/jdkim/mailets/DKIMVerify.java | 108 ++++++
 .../jdkim/mailets/HeaderSkippingOutputStream.java  |  71 ++++
 .../james/jdkim/mailets/MimeMessageHeaders.java    |  70 ++++
 .../apache/james/jdkim/mailets/DKIMSignTest.java   | 361 +++++++++++++++++++++
 .../apache/james/jdkim/mailets/DKIMVerifyTest.java | 127 ++++++++
 server/pom.xml                                     |   2 +
 10 files changed, 1256 insertions(+)

diff --git a/server/mailet/dkim/pom.xml b/server/mailet/dkim/pom.xml
new file mode 100644
index 0000000..4d4076a
--- /dev/null
+++ b/server/mailet/dkim/pom.xml
@@ -0,0 +1,106 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+    Licensed to the Apache Software Foundation (ASF) under one
+    or more contributor license agreements. See the NOTICE file
+    distributed with this work for additional information
+    regarding copyright ownership. The ASF licenses this file
+    to you under the Apache License, Version 2.0 (the
+    "License"); you may not use this file except in compliance
+    with the License. You may obtain a copy of the License at
+
+    http://www.apache.org/licenses/LICENSE-2.0
+
+    Unless required by applicable law or agreed to in writing,
+    software distributed under the License is distributed on an
+    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+    KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations
+    under the License.
+-->
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+    <modelVersion>4.0.0</modelVersion>
+
+    <parent>
+        <groupId>org.apache.james</groupId>
+        <artifactId>james-server</artifactId>
+        <version>3.4.0-SNAPSHOT</version>
+        <relativePath>../../pom.xml</relativePath>
+    </parent>
+
+    <artifactId>james-server-mailet-dkim</artifactId>
+    <name>Apache James :: Server :: Mailet :: DKIM</name>
+
+    <description>Mailet adapter for the JDKIM library</description>
+
+    <properties>
+        <jdkim.version>0.3-SNAPSHOT</jdkim.version>
+    </properties>
+
+    <dependencies>
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>apache-mailet-api</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>apache-mailet-base</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.james</groupId>
+            <artifactId>apache-mailet-test</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.james.jdkim</groupId>
+            <artifactId>apache-jdkim-library</artifactId>
+            <version>${jdkim.version}</version>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.james</groupId>
+                    <artifactId>apache-mime4j-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.james</groupId>
+                    <artifactId>apache-mime4j-dom</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>org.apache.james.jdkim</groupId>
+            <artifactId>apache-jdkim-library</artifactId>
+            <version>${jdkim.version}</version>
+            <type>test-jar</type>
+            <scope>test</scope>
+            <exclusions>
+                <exclusion>
+                    <groupId>org.apache.james</groupId>
+                    <artifactId>apache-mime4j-core</artifactId>
+                </exclusion>
+                <exclusion>
+                    <groupId>org.apache.james</groupId>
+                    <artifactId>apache-mime4j-dom</artifactId>
+                </exclusion>
+            </exclusions>
+        </dependency>
+        <dependency>
+            <groupId>commons-logging</groupId>
+            <artifactId>commons-logging</artifactId>
+        </dependency>
+        <dependency>
+            <groupId>junit</groupId>
+            <artifactId>junit</artifactId>
+            <scope>test</scope>
+        </dependency>
+        <dependency>
+            <groupId>ca.juliusdavies</groupId>
+            <artifactId>not-yet-commons-ssl</artifactId>
+            <version>0.3.11</version>
+        </dependency>
+        <dependency>
+            <groupId>commons-codec</groupId>
+            <artifactId>commons-codec</artifactId>
+            <version>1.7</version>
+        </dependency>
+    </dependencies>
+
+</project>
diff --git a/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/CRLFOutputStream.java b/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/CRLFOutputStream.java
new file mode 100644
index 0000000..b686b72
--- /dev/null
+++ b/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/CRLFOutputStream.java
@@ -0,0 +1,161 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim.mailets;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * A Filter for use with SMTP or other protocols in which lines must end with
+ * CRLF. Converts every "isolated" occourency of \r or \n with \r\n
+ * 
+ * RFC 2821 #2.3.7 mandates that line termination is CRLF, and that CR and LF
+ * must not be transmitted except in that pairing. If we get a naked LF, convert
+ * to CRLF.
+ * 
+ */
+public class CRLFOutputStream extends FilterOutputStream {
+
+    /**
+     * Counter for number of last (0A or 0D).
+     */
+    protected int statusLast;
+
+    protected final static int LAST_WAS_OTHER = 0;
+
+    protected final static int LAST_WAS_CR = 1;
+
+    protected final static int LAST_WAS_LF = 2;
+
+    protected boolean startOfLine = true;
+
+    /**
+     * Constructor that wraps an OutputStream.
+     * 
+     * @param out
+     *            the OutputStream to be wrapped
+     */
+    public CRLFOutputStream(OutputStream out) {
+        super(out);
+        statusLast = LAST_WAS_LF; // we already assume a CRLF at beginning
+        // (otherwise TOP would not work correctly
+        // !)
+    }
+
+    /**
+     * Writes a byte to the stream Fixes any naked CR or LF to the RFC 2821
+     * mandated CFLF pairing.
+     * 
+     * @param b
+     *            the byte to write
+     * 
+     * @throws IOException
+     *             if an error occurs writing the byte
+     */
+    public void write(int b) throws IOException {
+        switch (b) {
+            case '\r':
+                out.write('\r');
+                out.write('\n');
+                startOfLine = true;
+                statusLast = LAST_WAS_CR;
+                break;
+            case '\n':
+                if (statusLast != LAST_WAS_CR) {
+                    out.write('\r');
+                    out.write('\n');
+                    startOfLine = true;
+                }
+                statusLast = LAST_WAS_LF;
+                break;
+            default:
+                // we're no longer at the start of a line
+                out.write(b);
+                startOfLine = false;
+                statusLast = LAST_WAS_OTHER;
+                break;
+        }
+    }
+
+    /**
+     * Provides an extension point for ExtraDotOutputStream to be able to add
+     * dots at the beginning of new lines.
+     * 
+     * @see java.io.FilterOutputStream#write(byte[], int, int)
+     */
+    protected void writeChunk(byte buffer[], int offset, int length)
+            throws IOException {
+        out.write(buffer, offset, length);
+    }
+
+    /**
+     * @see java.io.FilterOutputStream#write(byte[], int, int)
+     */
+    public synchronized void write(byte buffer[], int offset, int length)
+            throws IOException {
+        /* optimized */
+        int lineStart = offset;
+        for (int i = offset; i < length + offset; i++) {
+            switch (buffer[i]) {
+                case '\r':
+                    // CR case. Write down the last line
+                    // and position the new lineStart at the next char
+                    writeChunk(buffer, lineStart, i - lineStart);
+                    out.write('\r');
+                    out.write('\n');
+                    startOfLine = true;
+                    lineStart = i + 1;
+                    statusLast = LAST_WAS_CR;
+                    break;
+                case '\n':
+                    if (statusLast != LAST_WAS_CR) {
+                        writeChunk(buffer, lineStart, i - lineStart);
+                        out.write('\r');
+                        out.write('\n');
+                        startOfLine = true;
+                    }
+                    lineStart = i + 1;
+                    statusLast = LAST_WAS_LF;
+                    break;
+                default:
+                    statusLast = LAST_WAS_OTHER;
+            }
+        }
+        if (length + offset > lineStart) {
+            writeChunk(buffer, lineStart, length + offset - lineStart);
+            startOfLine = false;
+        }
+    }
+
+    /**
+     * Ensure that the stream is CRLF terminated.
+     * 
+     * @throws IOException
+     *             if an error occurs writing the byte
+     */
+    public void checkCRLFTerminator() throws IOException {
+        if (statusLast == LAST_WAS_OTHER) {
+            out.write('\r');
+            out.write('\n');
+            statusLast = LAST_WAS_CR;
+        }
+    }
+}
diff --git a/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/ConvertTo7Bit.java b/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/ConvertTo7Bit.java
new file mode 100644
index 0000000..53a4e01
--- /dev/null
+++ b/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/ConvertTo7Bit.java
@@ -0,0 +1,77 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim.mailets;
+
+import java.io.IOException;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import javax.mail.internet.MimePart;
+
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMailet;
+
+/**
+ * Make sure the message stream is 7bit. Every 8bit part is encoded to
+ * quoted-printable or base64 and the message is saved.
+ */
+public class ConvertTo7Bit extends GenericMailet {
+
+    public void service(Mail mail) throws MessagingException {
+        MimeMessage message = mail.getMessage();
+        try {
+            convertTo7Bit(message);
+            message.saveChanges();
+        } catch (IOException e) {
+            throw new MessagingException("IOException converting message to 7bit: " + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * Converts a message to 7 bit.
+     */
+    private void convertTo7Bit(MimePart part) throws MessagingException, IOException {
+        if (part.isMimeType("multipart/*")) {
+            MimeMultipart parts = (MimeMultipart) part.getContent();
+            int count = parts.getCount();
+            for (int i = 0; i < count; i++) {
+                convertTo7Bit((MimePart) parts.getBodyPart(i));
+            }
+        } else if ("8bit".equals(part.getEncoding())) {
+            // The content may already be in encoded the form (likely with mail
+            // created from a stream). In that case, just changing the encoding
+            // to quoted-printable will mangle the result when this is
+            // transmitted.
+            // We must first convert the content into its native format, set it
+            // back, and only THEN set the transfer encoding to force the
+            // content to be encoded appropriately.
+
+            // if the part doesn't contain text it will be base64 encoded.
+            String contentTransferEncoding = part.isMimeType("text/*") ? "quoted-printable" : "base64";
+            part.setContent(part.getContent(), part.getContentType());
+            part.setHeader("Content-Transfer-Encoding", contentTransferEncoding);
+            part.addHeader("X-MIME-Autoconverted", "from 8bit to "
+                    + contentTransferEncoding + " by "
+                    + getMailetContext().getServerInfo());
+        }
+    }
+
+}
diff --git a/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/DKIMSign.java b/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/DKIMSign.java
new file mode 100644
index 0000000..57cc2b0
--- /dev/null
+++ b/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/DKIMSign.java
@@ -0,0 +1,173 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim.mailets;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.security.GeneralSecurityException;
+import java.security.NoSuchAlgorithmException;
+import java.security.PrivateKey;
+import java.security.spec.InvalidKeySpecException;
+import java.util.Enumeration;
+import java.util.LinkedList;
+import java.util.List;
+
+import javax.mail.Header;
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.commons.ssl.PKCS8Key;
+import org.apache.james.jdkim.DKIMSigner;
+import org.apache.james.jdkim.api.BodyHasher;
+import org.apache.james.jdkim.api.Headers;
+import org.apache.james.jdkim.api.SignatureRecord;
+import org.apache.james.jdkim.exceptions.PermFailException;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMailet;
+
+/**
+ * This mailet sign a message using the DKIM protocol
+ * If the privateKey is encoded using a password then you can pass
+ * the password as privateKeyPassword parameter.
+ * 
+ * Sample configuration:
+ * 
+ * <pre><code>
+ * &lt;mailet match=&quot;All&quot; class=&quot;DKIMSign&quot;&gt;
+ *   &lt;signatureTemplate&gt;v=1; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;&lt;/signatureTemplate&gt;
+ *   &lt;privateKey&gt;
+ *   -----BEGIN RSA PRIVATE KEY-----
+ *   MIICXAIBAAKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoT
+ *   M5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRH
+ *   r7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB
+ *   AoGBAI8XcwnZi0Sq5N89wF+gFNhnREFo3rsJDaCY8iqHdA5DDlnr3abb/yhipw0I
+ *   /1HlgC6fIG2oexXOXFWl+USgqRt1kTt9jXhVFExg8mNko2UelAwFtsl8CRjVcYQO
+ *   cedeH/WM/mXjg2wUqqZenBmlKlD6vNb70jFJeVaDJ/7n7j8BAkEA9NkH2D4Zgj/I
+ *   OAVYccZYH74+VgO0e7VkUjQk9wtJ2j6cGqJ6Pfj0roVIMUWzoBb8YfErR8l6JnVQ
+ *   bfy83gJeiQJBAOHk3ow7JjAn8XuOyZx24KcTaYWKUkAQfRWYDFFOYQF4KV9xLSEt
+ *   ycY0kjsdxGKDudWcsATllFzXDCQF6DTNIWECQEA52ePwTjKrVnLTfCLEG4OgHKvl
+ *   Zud4amthwDyJWoMEH2ChNB2je1N4JLrABOE+hk+OuoKnKAKEjWd8f3Jg/rkCQHj8
+ *   mQmogHqYWikgP/FSZl518jV48Tao3iXbqvU9Mo2T6yzYNCCqIoDLFWseNVnCTZ0Q
+ *   b+IfiEf1UeZVV5o4J+ECQDatNnS3V9qYUKjj/krNRD/U0+7eh8S2ylLqD3RlSn9K
+ *   tYGRMgAtUXtiOEizBH6bd/orzI9V9sw8yBz+ZqIH25Q=
+ *   -----END RSA PRIVATE KEY-----
+ *   &lt;/privateKey&gt;
+ * &lt;/mailet&gt;
+ * </code></pre>
+ * 
+ * By default the mailet assume that Javamail will convert LF to CRLF when sending 
+ * so will compute the hash using converted newlines. If you don't want this 
+ * behaviout then set forceCRLF attribute to false. 
+ */
+public class DKIMSign extends GenericMailet {
+
+    private String signatureTemplate;
+    private PrivateKey privateKey;
+    private boolean forceCRLF;
+
+	/**
+	 * @return the signatureTemplate
+	 */
+	protected String getSignatureTemplate() {
+		return signatureTemplate;
+	}
+
+	/**
+	 * @return the privateKey
+	 */
+	protected PrivateKey getPrivateKey() {
+		return privateKey;
+	}
+
+    public void init() throws MessagingException {
+        signatureTemplate = getInitParameter("signatureTemplate");
+        String privateKeyString = getInitParameter("privateKey");
+        String privateKeyPassword = getInitParameter("privateKeyPassword", null);
+        forceCRLF = getInitParameter("forceCRLF", true);
+        try {
+            PKCS8Key pkcs8 = new PKCS8Key(new ByteArrayInputStream(
+                    privateKeyString.getBytes()),
+                    privateKeyPassword != null ? privateKeyPassword
+                            .toCharArray() : null);
+            privateKey = pkcs8.getPrivateKey();
+        } catch (NoSuchAlgorithmException e) {
+            throw new MessagingException("Unknown private key algorythm: " + e.getMessage(), e);
+        } catch (InvalidKeySpecException e) {
+            throw new MessagingException("PrivateKey should be in base64 encoded PKCS8 (der) format: " + e.getMessage(), e);
+        } catch (GeneralSecurityException e) {
+            throw new MessagingException("General security exception: " + e.getMessage(), e);
+        }
+    }
+
+    public void service(Mail mail) throws MessagingException {
+        DKIMSigner signer = new DKIMSigner(getSignatureTemplate(), getPrivateKey());
+        SignatureRecord signRecord = signer
+                .newSignatureRecordTemplate(getSignatureTemplate());
+        try {
+            BodyHasher bhj = signer.newBodyHasher(signRecord);
+            MimeMessage message = mail.getMessage();
+            Headers headers = new MimeMessageHeaders(message);
+            try {
+            	OutputStream os = new HeaderSkippingOutputStream(bhj.getOutputStream());
+            	if (forceCRLF) {
+            	    os = new CRLFOutputStream(os);
+                }
+                message.writeTo(os);
+                bhj.getOutputStream().close();
+            } catch (IOException e) {
+                throw new MessagingException("Exception calculating bodyhash: "
+                        + e.getMessage(), e);
+            }
+            String signatureHeader = signer.sign(headers, bhj);
+            // Unfortunately JavaMail does not give us a method to add headers
+            // on top.
+            // message.addHeaderLine(signatureHeader);
+            prependHeader(message, signatureHeader);
+        } catch (PermFailException e) {
+            throw new MessagingException("PermFail while signing: "
+                    + e.getMessage(), e);
+        }
+
+    }
+
+    private void prependHeader(MimeMessage message, String signatureHeader)
+            throws MessagingException {
+        List<String> prevHeader = new LinkedList<String>();
+        // read all the headers
+        for (Enumeration<String> e = message.getAllHeaderLines(); e.hasMoreElements();) {
+            String headerLine = e.nextElement();
+            prevHeader.add(headerLine);
+        }
+        // remove all the headers
+        for (Enumeration<Header> e = message.getAllHeaders(); e.hasMoreElements();) {
+            Header header = e.nextElement();
+            message.removeHeader(header.getName());
+        }
+        // add our header
+        message.addHeaderLine(signatureHeader);
+        // add the remaining headers using "addHeaderLine" that won't alter the
+        // insertion order.
+        for (String header : prevHeader) {
+            message.addHeaderLine(header);
+        }
+    }
+
+}
diff --git a/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/DKIMVerify.java b/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/DKIMVerify.java
new file mode 100644
index 0000000..ca72876
--- /dev/null
+++ b/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/DKIMVerify.java
@@ -0,0 +1,108 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim.mailets;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.util.List;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.james.jdkim.DKIMVerifier;
+import org.apache.james.jdkim.api.BodyHasher;
+import org.apache.james.jdkim.api.Headers;
+import org.apache.james.jdkim.api.SignatureRecord;
+import org.apache.james.jdkim.exceptions.FailException;
+import org.apache.mailet.Mail;
+import org.apache.mailet.base.GenericMailet;
+
+/**
+ * This mailet verify a message using the DKIM protocol
+ * 
+ * Sample configuration:
+ * <pre><code>
+ * &lt;mailet match=&quot;All&quot; class=&quot;DKIMVerify&quot;&gt;
+ * &lt;/mailet&gt;
+ * </code></pre>
+ * 
+ * By default the mailet assume that Javamail will use LF instead of CRLF 
+ * so it will verify the hash using converted newlines. If you don't want this 
+ * behaviout then set forceCRLF attribute to false. 
+ */
+public class DKIMVerify extends GenericMailet {
+
+    public static final String DKIM_AUTH_RESULT_ATTRIBUTE = "jDKIM.AUTHRESULT";
+    
+    protected DKIMVerifier verifier = null;
+    private boolean forceCRLF;
+
+    @Override
+    public void init() throws MessagingException {
+        verifier = new DKIMVerifier();
+        forceCRLF = getInitParameter("forceCRLF", true);
+    }
+    
+    public void service(Mail mail) throws MessagingException {
+        try {
+            MimeMessage message = mail.getMessage();
+            List<SignatureRecord> res = verify(verifier, message, forceCRLF);
+            if (res == null || res.isEmpty()) {
+                // neutral
+                mail.setAttribute(DKIM_AUTH_RESULT_ATTRIBUTE, "neutral (no signatures)");
+            } else {
+                // pass
+                StringBuilder msg = new StringBuilder();
+                msg.append("pass");
+                for (SignatureRecord rec : res) {
+                    msg.append(" (");
+                    msg.append("identity ");
+                    msg.append(rec.getIdentity().toString());
+                    msg.append(")");
+                }
+                mail.setAttribute(DKIM_AUTH_RESULT_ATTRIBUTE, msg.toString());
+            }
+        } catch (FailException e) {
+            // fail
+            mail.setAttribute(DKIM_AUTH_RESULT_ATTRIBUTE, "fail ("+(e.getRelatedRecordIdentity() != null ? "identity "+ e.getRelatedRecordIdentity() + ": " : "")+e.getMessage()+")");
+        }
+        
+    }
+
+	protected static List<SignatureRecord> verify(DKIMVerifier verifier, MimeMessage message, boolean forceCRLF)
+			throws MessagingException, FailException {
+		Headers headers = new MimeMessageHeaders(message);
+		BodyHasher bh = verifier.newBodyHasher(headers);
+		try {
+		    if (bh != null) {
+		    	OutputStream os = new HeaderSkippingOutputStream(bh
+		                .getOutputStream());
+		    	if (forceCRLF) os = new CRLFOutputStream(os);
+		        message.writeTo(os);
+		        bh.getOutputStream().close();
+		    }
+		    
+		} catch (IOException e) {
+		    throw new MessagingException("Exception calculating bodyhash: "
+		            + e.getMessage(), e);
+		}
+		return verifier.verify(bh);
+	}
+}
diff --git a/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/HeaderSkippingOutputStream.java b/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/HeaderSkippingOutputStream.java
new file mode 100644
index 0000000..16e7f25
--- /dev/null
+++ b/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/HeaderSkippingOutputStream.java
@@ -0,0 +1,71 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim.mailets;
+
+import java.io.FilterOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+
+/**
+ * Ignore writes until the given sequence is found.
+ */
+public class HeaderSkippingOutputStream extends FilterOutputStream {
+
+    boolean inHeaders = true;
+    final byte[] skipTo = "\r\n\r\n".getBytes();
+    int pos = 0;
+
+    public HeaderSkippingOutputStream(OutputStream out) {
+        super(out);
+    }
+
+    public void write(byte[] b, int off, int len) throws IOException {
+        if (inHeaders) {
+            for (int i = off; i < off + len; i++) {
+                if (b[i] == skipTo[pos]) {
+                    pos++;
+                    if (pos == skipTo.length) {
+                        inHeaders = false;
+                        if (len - i - 1 > 0)
+                            out.write(b, i + 1, len - i - 1);
+                        break;
+                    }
+                } else
+                    pos = 0;
+            }
+        } else {
+            out.write(b, off, len);
+        }
+    }
+
+    public void write(int b) throws IOException {
+        if (inHeaders) {
+            if (skipTo[pos] == b) {
+                pos++;
+                if (pos == skipTo.length)
+                    inHeaders = false;
+            } else
+                pos = 0;
+        } else {
+            out.write(b);
+        }
+    }
+
+}
diff --git a/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/MimeMessageHeaders.java b/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/MimeMessageHeaders.java
new file mode 100644
index 0000000..3d2a639
--- /dev/null
+++ b/server/mailet/dkim/src/main/java/org/apache/james/jdkim/mailets/MimeMessageHeaders.java
@@ -0,0 +1,70 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim.mailets;
+
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+import javax.mail.MessagingException;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.james.jdkim.api.Headers;
+
+/**
+ * An adapter to let DKIMSigner read headers from MimeMessage
+ */
+final class MimeMessageHeaders implements Headers {
+
+    private final Map<String, List<String>> headers;
+    private final List<String> fields;
+
+    public MimeMessageHeaders(MimeMessage message)
+            throws MessagingException {
+        headers = new HashMap<String, List<String>>();
+        fields = new LinkedList<String>();
+        for (Enumeration<String> e = message.getAllHeaderLines(); e
+                .hasMoreElements();) {
+            String head = e.nextElement();
+            int p = head.indexOf(':');
+            if (p <= 0)
+                throw new MessagingException("Bad header line: " + head);
+            String headerName = head.substring(0, p).trim();
+            String headerNameLC = headerName.toLowerCase();
+            fields.add(headerName);
+            List<String> strings = headers.get(headerNameLC);
+            if (strings == null) {
+                strings = new LinkedList<String>();
+                headers.put(headerNameLC, strings);
+            }
+            strings.add(head);
+        }
+    }
+
+    public List<String> getFields() {
+        return fields;
+    }
+
+    public List<String> getFields(String name) {
+        return headers.get(name.toLowerCase());
+    }
+}
\ No newline at end of file
diff --git a/server/mailet/dkim/src/test/java/org/apache/james/jdkim/mailets/DKIMSignTest.java b/server/mailet/dkim/src/test/java/org/apache/james/jdkim/mailets/DKIMSignTest.java
new file mode 100644
index 0000000..83ea4d6
--- /dev/null
+++ b/server/mailet/dkim/src/test/java/org/apache/james/jdkim/mailets/DKIMSignTest.java
@@ -0,0 +1,361 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim.mailets;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.util.List;
+import java.util.Properties;
+
+import javax.mail.Address;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMessage.RecipientType;
+
+import org.apache.james.jdkim.DKIMVerifier;
+import org.apache.james.jdkim.MockPublicKeyRecordRetriever;
+import org.apache.james.jdkim.api.SignatureRecord;
+import org.apache.james.jdkim.exceptions.FailException;
+import org.apache.james.jdkim.exceptions.PermFailException;
+import org.apache.mailet.Mail;
+import org.apache.mailet.Mailet;
+import org.apache.mailet.base.test.FakeMail;
+import org.apache.mailet.base.test.FakeMailContext;
+import org.apache.mailet.base.test.FakeMailetConfig;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DKIMSignTest {
+
+    private static final String TESTING_PEM = "-----BEGIN RSA PRIVATE KEY-----\r\n" +
+            "MIICXAIBAAKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoT\r\n" +
+            "M5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRH\r\n" +
+            "r7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB\r\n" +
+            "AoGBAI8XcwnZi0Sq5N89wF+gFNhnREFo3rsJDaCY8iqHdA5DDlnr3abb/yhipw0I\r\n" +
+            "/1HlgC6fIG2oexXOXFWl+USgqRt1kTt9jXhVFExg8mNko2UelAwFtsl8CRjVcYQO\r\n" +
+            "cedeH/WM/mXjg2wUqqZenBmlKlD6vNb70jFJeVaDJ/7n7j8BAkEA9NkH2D4Zgj/I\r\n" +
+            "OAVYccZYH74+VgO0e7VkUjQk9wtJ2j6cGqJ6Pfj0roVIMUWzoBb8YfErR8l6JnVQ\r\n" +
+            "bfy83gJeiQJBAOHk3ow7JjAn8XuOyZx24KcTaYWKUkAQfRWYDFFOYQF4KV9xLSEt\r\n" +
+            "ycY0kjsdxGKDudWcsATllFzXDCQF6DTNIWECQEA52ePwTjKrVnLTfCLEG4OgHKvl\r\n" +
+            "Zud4amthwDyJWoMEH2ChNB2je1N4JLrABOE+hk+OuoKnKAKEjWd8f3Jg/rkCQHj8\r\n" +
+            "mQmogHqYWikgP/FSZl518jV48Tao3iXbqvU9Mo2T6yzYNCCqIoDLFWseNVnCTZ0Q\r\n" +
+            "b+IfiEf1UeZVV5o4J+ECQDatNnS3V9qYUKjj/krNRD/U0+7eh8S2ylLqD3RlSn9K\r\n" +
+            "tYGRMgAtUXtiOEizBH6bd/orzI9V9sw8yBz+ZqIH25Q=\r\n" +
+            "-----END RSA PRIVATE KEY-----\r\n";
+    private static final FakeMailContext FAKE_MAIL_CONTEXT = FakeMailContext.defaultContext();
+
+    @Test
+    public void testDKIMSign() throws MessagingException, IOException,
+            FailException {
+        String message = "Received: by 10.XX.XX.12 with SMTP id dfgskldjfhgkljsdfhgkljdhfg;\r\n\tTue, 06 Oct 2009 07:37:34 -0700 (PDT)\r\nReturn-Path: <bo...@example.com>\r\nReceived: from example.co.uk (example.co.uk [XX.XXX.125.19])\r\n\tby mx.example.com with ESMTP id dgdfgsdfgsd.97.2009.10.06.07.37.32;\r\n\tTue, 06 Oct 2009 07:37:32 -0700 (PDT)\r\nFrom: apache@bago.org\r\nTo: apache@bago.org\r\n\r\nbody\r\nprova\r\n";
+
+        Mailet mailet = new DKIMSign();
+
+        FakeMailetConfig mci = FakeMailetConfig.builder()
+                .mailetName("Test")
+                .mailetContext(FAKE_MAIL_CONTEXT)
+                .setProperty(
+                        "signatureTemplate",
+                        "v=1; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;")
+                .setProperty("privateKey", TESTING_PEM)
+                .build();
+
+        mailet.init(mci);
+
+        Mail mail = FakeMail.builder()
+            .name("test")
+            .mimeMessage(new MimeMessage(Session
+                .getDefaultInstance(new Properties()),
+                new ByteArrayInputStream(message.getBytes())))
+            .build();
+
+        mailet.service(mail);
+
+        Mailet m7bit = new ConvertTo7Bit();
+        m7bit.init(mci);
+        m7bit.service(mail);
+
+        ByteArrayOutputStream rawMessage = new ByteArrayOutputStream();
+        mail.getMessage().writeTo(rawMessage);
+
+        MockPublicKeyRecordRetriever mockPublicKeyRecordRetriever = new MockPublicKeyRecordRetriever(
+                "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoTM5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRHr7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB;",
+                "selector", "example.com");
+        verify(rawMessage, mockPublicKeyRecordRetriever);
+    }
+
+    private List<SignatureRecord> verify(ByteArrayOutputStream rawMessage,
+                                         MockPublicKeyRecordRetriever mockPublicKeyRecordRetriever)
+            throws MessagingException, FailException {
+        List<SignatureRecord> signs = DKIMVerify.verify(new DKIMVerifier(mockPublicKeyRecordRetriever), new MimeMessage(Session.getDefaultInstance(new Properties()), new ByteArrayInputStream(rawMessage.toByteArray())), true);
+        assertNotNull(signs);
+        assertEquals(1, signs.size());
+        return signs;
+    }
+
+    @Test
+    public void testDKIMSignFuture() throws MessagingException, IOException,
+            FailException {
+        String message = "Received: by 10.XX.XX.12 with SMTP id dfgskldjfhgkljsdfhgkljdhfg;\r\n\tTue, 06 Oct 2009 07:37:34 -0700 (PDT)\r\nReturn-Path: <bo...@example.com>\r\nReceived: from example.co.uk (example.co.uk [XX.XXX.125.19])\r\n\tby mx.example.com with ESMTP id dgdfgsdfgsd.97.2009.10.06.07.37.32;\r\n\tTue, 06 Oct 2009 07:37:32 -0700 (PDT)\r\nFrom: apache@bago.org\r\nTo: apache@bago.org\r\n\r\nbody\r\nprova\r\n";
+
+        Mailet mailet = new DKIMSign();
+
+        FakeMailetConfig mci = FakeMailetConfig.builder()
+                .mailetName("Test")
+                .mailetContext(FAKE_MAIL_CONTEXT)
+                .setProperty(
+                        "signatureTemplate",
+                        "v=1; t=" + ((System.currentTimeMillis() / 1000) + 1000) + "; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;")
+                .setProperty("privateKey", TESTING_PEM)
+                .build();
+
+        mailet.init(mci);
+
+        Mail mail = FakeMail.builder()
+            .name("test")
+            .mimeMessage(new MimeMessage(Session
+                .getDefaultInstance(new Properties()),
+                new ByteArrayInputStream(message.getBytes())))
+            .build();
+
+        mailet.service(mail);
+
+        Mailet m7bit = new ConvertTo7Bit();
+        m7bit.init(mci);
+        m7bit.service(mail);
+
+        ByteArrayOutputStream rawMessage = new ByteArrayOutputStream();
+        mail.getMessage().writeTo(rawMessage);
+
+        MockPublicKeyRecordRetriever mockPublicKeyRecordRetriever = new MockPublicKeyRecordRetriever(
+                "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoTM5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRHr7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB;",
+                "selector", "example.com");
+        try {
+            verify(rawMessage, mockPublicKeyRecordRetriever);
+            Assert.fail("Expecting signature to be ignored");
+        } catch (PermFailException e) {
+            // signature ignored, so fail for missing signatures.
+        }
+    }
+
+
+    @Test
+    public void testDKIMSignTime() throws MessagingException, IOException,
+            FailException {
+        String message = "Received: by 10.XX.XX.12 with SMTP id dfgskldjfhgkljsdfhgkljdhfg;\r\n\tTue, 06 Oct 2009 07:37:34 -0700 (PDT)\r\nReturn-Path: <bo...@example.com>\r\nReceived: from example.co.uk (example.co.uk [XX.XXX.125.19])\r\n\tby mx.example.com with ESMTP id dgdfgsdfgsd.97.2009.10.06.07.37.32;\r\n\tTue, 06 Oct 2009 07:37:32 -0700 (PDT)\r\nFrom: apache@bago.org\r\nTo: apache@bago.org\r\n\r\nbody\r\nprova\r\n";
+
+        Mailet mailet = new DKIMSign();
+
+        FakeMailetConfig mci = FakeMailetConfig.builder()
+                .mailetName("Test")
+                .mailetContext(FAKE_MAIL_CONTEXT)
+                .setProperty(
+                        "signatureTemplate",
+                        "v=1; t=; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;")
+                .setProperty("privateKey", TESTING_PEM)
+                .build();
+
+        mailet.init(mci);
+
+        Mail mail = FakeMail.builder()
+            .name("test")
+            .mimeMessage(new MimeMessage(Session
+                .getDefaultInstance(new Properties()),
+                new ByteArrayInputStream(message.getBytes())))
+            .build();
+
+        mailet.service(mail);
+
+        Mailet m7bit = new ConvertTo7Bit();
+        m7bit.init(mci);
+        m7bit.service(mail);
+
+        ByteArrayOutputStream rawMessage = new ByteArrayOutputStream();
+        mail.getMessage().writeTo(rawMessage);
+
+        MockPublicKeyRecordRetriever mockPublicKeyRecordRetriever = new MockPublicKeyRecordRetriever(
+                "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoTM5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRHr7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB;",
+                "selector", "example.com");
+        verify(rawMessage, mockPublicKeyRecordRetriever);
+
+        List<SignatureRecord> rs = verify(rawMessage, mockPublicKeyRecordRetriever);
+
+        // check we have a valued signatureTimestamp
+        Assert.assertNotNull(rs.get(0).getSignatureTimestamp());
+        long ref = System.currentTimeMillis() / 1000;
+        // Chech that the signature timestamp is in the past 60 seconds.
+        Assert.assertTrue(rs.get(0).getSignatureTimestamp() <= ref);
+        Assert.assertTrue(rs.get(0).getSignatureTimestamp() >= ref - 60);
+    }
+
+    @Test
+    public void testDKIMSignMessageAsText() throws MessagingException,
+            IOException, FailException {
+        MimeMessage mm = new MimeMessage(Session
+                .getDefaultInstance(new Properties()));
+        mm.addFrom(new Address[]{new InternetAddress("io@bago.org")});
+        mm.addRecipient(RecipientType.TO, new InternetAddress("io@bago.org"));
+        mm.setText("An 8bit encoded body with \u20ACuro symbol.", "ISO-8859-15");
+
+        Mailet mailet = new DKIMSign();
+
+        FakeMailetConfig mci = FakeMailetConfig.builder()
+                .mailetName("Test")
+                .mailetContext(FAKE_MAIL_CONTEXT)
+                .setProperty(
+                        "signatureTemplate",
+                        "v=1; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;")
+                .setProperty("privateKey", TESTING_PEM)
+                .build();
+
+        mailet.init(mci);
+
+        Mail mail = FakeMail.builder()
+            .name("test")
+            .mimeMessage(mm)
+            .build();
+
+        Mailet m7bit = new ConvertTo7Bit();
+        m7bit.init(mci);
+
+        mailet.service(mail);
+
+        m7bit.service(mail);
+
+        ByteArrayOutputStream rawMessage = new ByteArrayOutputStream();
+        mail.getMessage().writeTo(rawMessage);
+
+        MockPublicKeyRecordRetriever mockPublicKeyRecordRetriever = new MockPublicKeyRecordRetriever(
+                "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoTM5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRHr7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB;",
+                "selector", "example.com");
+
+        verify(rawMessage, mockPublicKeyRecordRetriever);
+    }
+
+    @Test
+    public void testDKIMSignMessageAsObjectConvertedTo7Bit()
+            throws MessagingException, IOException, FailException {
+        MimeMessage mm = new MimeMessage(Session
+                .getDefaultInstance(new Properties()));
+        mm.addFrom(new Address[]{new InternetAddress("io@bago.org")});
+        mm.addRecipient(RecipientType.TO, new InternetAddress("io@bago.org"));
+        mm.setContent("An 8bit encoded body with \u20ACuro symbol.",
+                "text/plain; charset=iso-8859-15");
+        mm.setHeader("Content-Transfer-Encoding", "8bit");
+        mm.saveChanges();
+
+        FakeMailContext FakeMailContext = FAKE_MAIL_CONTEXT;
+        FakeMailContext.getServerInfo();
+
+        FakeMailetConfig mci = FakeMailetConfig.builder()
+                .mailetName("Test")
+                .mailetContext(FAKE_MAIL_CONTEXT)
+                .setProperty(
+                        "signatureTemplate",
+                        "v=1; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;")
+                .setProperty("privateKey", TESTING_PEM)
+                .build();
+
+        Mail mail = FakeMail.builder()
+            .name("test")
+            .mimeMessage(mm)
+            .build();
+
+        Mailet mailet = new DKIMSign();
+        mailet.init(mci);
+
+        Mailet m7bit = new ConvertTo7Bit();
+        m7bit.init(mci);
+        m7bit.service(mail);
+
+        mailet.service(mail);
+
+        m7bit.service(mail);
+
+        ByteArrayOutputStream rawMessage = new ByteArrayOutputStream();
+        mail.getMessage().writeTo(rawMessage);
+
+        MockPublicKeyRecordRetriever mockPublicKeyRecordRetriever = new MockPublicKeyRecordRetriever(
+                "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoTM5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRHr7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB;",
+                "selector", "example.com");
+        verify(rawMessage, mockPublicKeyRecordRetriever);
+    }
+
+    @Test
+    public void testDKIMSignMessageAsObjectNotConverted()
+            throws MessagingException, IOException, FailException {
+        MimeMessage mm = new MimeMessage(Session
+                .getDefaultInstance(new Properties()));
+        mm.addFrom(new Address[]{new InternetAddress("io@bago.org")});
+        mm.addRecipient(RecipientType.TO, new InternetAddress("io@bago.org"));
+        mm.setContent("An 8bit encoded body with \u20ACuro symbol.",
+                "text/plain; charset=iso-8859-15");
+        mm.setHeader("Content-Transfer-Encoding", "8bit");
+        mm.saveChanges();
+
+        FakeMailContext FakeMailContext = FAKE_MAIL_CONTEXT;
+        FakeMailContext.getServerInfo();
+
+        FakeMailetConfig mci = FakeMailetConfig.builder()
+                .mailetName("Test")
+                .mailetContext(FAKE_MAIL_CONTEXT)
+                .setProperty(
+                        "signatureTemplate",
+                        "v=1; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;")
+                .setProperty("privateKey", TESTING_PEM)
+                .build();
+
+        Mail mail = FakeMail.builder()
+            .name("test")
+            .mimeMessage(mm)
+            .build();
+
+        Mailet mailet = new DKIMSign();
+        mailet.init(mci);
+
+        Mailet m7bit = new ConvertTo7Bit();
+        m7bit.init(mci);
+        // m7bit.service(mail);
+
+        mailet.service(mail);
+
+        m7bit.service(mail);
+
+        ByteArrayOutputStream rawMessage = new ByteArrayOutputStream();
+        mail.getMessage().writeTo(rawMessage);
+
+        MockPublicKeyRecordRetriever mockPublicKeyRecordRetriever = new MockPublicKeyRecordRetriever(
+                "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoTM5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRHr7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB;",
+                "selector", "example.com");
+        try {
+            verify(rawMessage, mockPublicKeyRecordRetriever);
+            Assert.fail("Expected PermFail");
+        } catch (PermFailException e) {
+
+        }
+    }
+
+}
diff --git a/server/mailet/dkim/src/test/java/org/apache/james/jdkim/mailets/DKIMVerifyTest.java b/server/mailet/dkim/src/test/java/org/apache/james/jdkim/mailets/DKIMVerifyTest.java
new file mode 100644
index 0000000..5ee901f
--- /dev/null
+++ b/server/mailet/dkim/src/test/java/org/apache/james/jdkim/mailets/DKIMVerifyTest.java
@@ -0,0 +1,127 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one   *
+ * or more contributor license agreements.  See the NOTICE file *
+ * distributed with this work for additional information        *
+ * regarding copyright ownership.  The ASF licenses this file   *
+ * to you under the Apache License, Version 2.0 (the            *
+ * "License"); you may not use this file except in compliance   *
+ * with the License.  You may obtain a copy of the License at   *
+ *                                                              *
+ *   http://www.apache.org/licenses/LICENSE-2.0                 *
+ *                                                              *
+ * Unless required by applicable law or agreed to in writing,   *
+ * software distributed under the License is distributed on an  *
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY       *
+ * KIND, either express or implied.  See the License for the    *
+ * specific language governing permissions and limitations      *
+ * under the License.                                           *
+ ****************************************************************/
+
+package org.apache.james.jdkim.mailets;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.util.Properties;
+
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.MimeMessage;
+
+import org.apache.james.jdkim.DKIMVerifier;
+import org.apache.james.jdkim.MockPublicKeyRecordRetriever;
+import org.apache.james.jdkim.exceptions.FailException;
+import org.apache.mailet.Mail;
+import org.apache.mailet.Mailet;
+import org.apache.mailet.base.test.FakeMail;
+import org.apache.mailet.base.test.FakeMailContext;
+import org.apache.mailet.base.test.FakeMailetConfig;
+import org.junit.Assert;
+import org.junit.Test;
+
+public class DKIMVerifyTest {
+
+    @Test
+    public void testDKIMVerifyPass() throws MessagingException, IOException,
+            FailException {
+        String message = "DKIM-Signature: v=1; d=example.com; t=1284762805; b=ZFfwSIzTQM7k9syRnl9VfQh0/dr99euvBe1gn/DiTrnEZjxyjzQBD2MMvowVdbHpPMtSjtCtehU9zZ3urXmj5iHKujpEkP92FEKinzElkQ2eT2zoxdg1zByPHsKPX+KjrBespAJcO2k052aOK5kIBFxpQumP4aiW7ZklBKSWMBk=; s=selector; a=rsa-sha256; bh=rHOD7fd9xnNxK7OSl5ellpQVF14NNFbOIizqtUMhnio=; h=from:to:received:received;\r\n"
+            + "Received: by 10.XX.XX.12 with SMTP id dfgskldjfhgkljsdfhgkljdhfg;\r\n\tTue, 06 Oct 2009 07:37:34 -0700 (PDT)\r\nReturn-Path: <bo...@example.com>\r\nReceived: from example.co.uk (example.co.uk [XX.XXX.125.19])\r\n\tby mx.example.com with ESMTP id dgdfgsdfgsd.97.2009.10.06.07.37.32;\r\n\tTue, 06 Oct 2009 07:37:32 -0700 (PDT)\r\nFrom: apache@bago.org\r\nTo: apache@bago.org\r\n\r\nbody\r\nprova\r\n";
+
+        Mail mail = process(message);
+        
+        String attr = (String) mail.getAttribute(DKIMVerify.DKIM_AUTH_RESULT_ATTRIBUTE);
+        Assert.assertNotNull(attr);
+        Assert.assertTrue(attr.startsWith("pass"));
+    }
+
+    @Test
+    public void testDKIMVerifyFail() throws MessagingException, IOException,
+            FailException {
+        // altered message body
+        String message = "DKIM-Signature: v=1; d=example.com; t=1284762805; b=ZFfwSIzTQM7k9syRnl9VfQh0/dr99euvBe1gn/DiTrnEZjxyjzQBD2MMvowVdbHpPMtSjtCtehU9zZ3urXmj5iHKujpEkP92FEKinzElkQ2eT2zoxdg1zByPHsKPX+KjrBespAJcO2k052aOK5kIBFxpQumP4aiW7ZklBKSWMBk=; s=selector; a=rsa-sha256; bh=rHOD7fd9xnNxK7OSl5ellpQVF14NNFbOIizqtUMhnio=; h=from:to:received:received;\r\n"
+            + "Received: by 10.XX.XX.12 with SMTP id dfgskldjfhgkljsdfhgkljdhfg;\r\n\tTue, 06 Oct 2009 07:37:34 -0700 (PDT)\r\nReturn-Path: <bo...@example.com>\r\nReceived: from example.co.uk (example.co.uk [XX.XXX.125.19])\r\n\tby mx.example.com with ESMTP id dgdfgsdfgsd.97.2009.10.06.07.37.32;\r\n\tTue, 06 Oct 2009 07:37:32 -0700 (PDT)\r\nFrom: apache@bago.org\r\nTo: apache@bago.org\r\n\r\nbody\r\nprova altered\r\n";
+
+        Mail mail = process(message);
+        
+        String attr = (String) mail.getAttribute(DKIMVerify.DKIM_AUTH_RESULT_ATTRIBUTE);
+        Assert.assertNotNull(attr);
+        Assert.assertTrue(attr.startsWith("fail"));
+    }
+
+    @Test
+    public void testDKIMVerifyFailInvalid() throws MessagingException, IOException,
+            FailException {
+        // invalid version v=2
+        String message = "DKIM-Signature: v=2; d=example.com; t=1284762805; b=ZFfwSIzTQM7k9syRnl9VfQh0/dr99euvBe1gn/DiTrnEZjxyjzQBD2MMvowVdbHpPMtSjtCtehU9zZ3urXmj5iHKujpEkP92FEKinzElkQ2eT2zoxdg1zByPHsKPX+KjrBespAJcO2k052aOK5kIBFxpQumP4aiW7ZklBKSWMBk=; s=selector; a=rsa-sha256; bh=rHOD7fd9xnNxK7OSl5ellpQVF14NNFbOIizqtUMhnio=; h=from:to:received:received;\r\n"
+            + "Received: by 10.XX.XX.12 with SMTP id dfgskldjfhgkljsdfhgkljdhfg;\r\n\tTue, 06 Oct 2009 07:37:34 -0700 (PDT)\r\nReturn-Path: <bo...@example.com>\r\nReceived: from example.co.uk (example.co.uk [XX.XXX.125.19])\r\n\tby mx.example.com with ESMTP id dgdfgsdfgsd.97.2009.10.06.07.37.32;\r\n\tTue, 06 Oct 2009 07:37:32 -0700 (PDT)\r\nFrom: apache@bago.org\r\nTo: apache@bago.org\r\n\r\nbody\r\nprova\r\n";
+
+        Mail mail = process(message);
+        
+        String attr = (String) mail.getAttribute(DKIMVerify.DKIM_AUTH_RESULT_ATTRIBUTE);
+        Assert.assertNotNull(attr);
+        Assert.assertTrue(attr.startsWith("fail"));
+    }
+
+    @Test
+    public void testDKIMVerifyNeutral() throws MessagingException, IOException,
+            FailException {
+        // no signatures!
+        String message = ""
+            + "Received: by 10.XX.XX.12 with SMTP id dfgskldjfhgkljsdfhgkljdhfg;\r\n\tTue, 06 Oct 2009 07:37:34 -0700 (PDT)\r\nReturn-Path: <bo...@example.com>\r\nReceived: from example.co.uk (example.co.uk [XX.XXX.125.19])\r\n\tby mx.example.com with ESMTP id dgdfgsdfgsd.97.2009.10.06.07.37.32;\r\n\tTue, 06 Oct 2009 07:37:32 -0700 (PDT)\r\nFrom: apache@bago.org\r\nTo: apache@bago.org\r\n\r\nbody\r\nprova altered\r\n";
+
+        Mail mail = process(message);
+        
+        String attr = (String) mail.getAttribute(DKIMVerify.DKIM_AUTH_RESULT_ATTRIBUTE);
+        Assert.assertNotNull(attr);
+        Assert.assertTrue(attr.startsWith("neutral"));
+    }
+
+    private Mail process(String message) throws MessagingException {
+        Mailet mailet = new DKIMVerify() {
+
+            @Override
+            public void init() throws MessagingException {
+                verifier = new DKIMVerifier(new MockPublicKeyRecordRetriever(
+                        "v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDYDaYKXzwVYwqWbLhmuJ66aTAN8wmDR+rfHE8HfnkSOax0oIoTM5zquZrTLo30870YMfYzxwfB6j/Nz3QdwrUD/t0YMYJiUKyWJnCKfZXHJBJ+yfRHr7oW+UW3cVo9CG2bBfIxsInwYe175g9UjyntJpWueqdEIo1c2bhv9Mp66QIDAQAB;",
+                        "selector", "example.com"));
+            }
+            
+        };
+
+        FakeMailetConfig mci = FakeMailetConfig.builder()
+                .mailetName("Test")
+                .mailetContext(FakeMailContext.defaultContext())
+                .build();
+
+        mailet.init(mci);
+
+        Mail mail = FakeMail.builder()
+            .name("test")
+            .mimeMessage(new MimeMessage(Session
+                .getDefaultInstance(new Properties()),
+                new ByteArrayInputStream(message.getBytes())))
+            .build();
+
+        mailet.service(mail);
+        return mail;
+    }
+}
diff --git a/server/pom.xml b/server/pom.xml
index 07e2ae2..aafbbb4 100644
--- a/server/pom.xml
+++ b/server/pom.xml
@@ -72,6 +72,8 @@
         <module>dns-service/dnsservice-test</module>
 
         <module>mailet/integration-testing</module>
+
+        <module>mailet/dkim</module>
         <module>mailet/mailetcontainer-api</module>
         <module>mailet/mailetcontainer-camel</module>
         <module>mailet/mailets</module>


---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org