Java tutorial
/**************************************************************** * 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.Iterator; 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> * <mailet match="All" class="DKIMSign"> * <signatureTemplate>v=1; s=selector; d=example.com; h=from:to:received:received; a=rsa-sha256; bh=; b=;</signatureTemplate> * <privateKey> * -----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----- * </privateKey> * </mailet> * </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(); // privateKey = DKIMSigner.getPrivateKey(privateKeyString); } 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); } } @SuppressWarnings("unchecked") 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 (Iterator<String> i = prevHeader.iterator(); i.hasNext();) { String header = i.next(); message.addHeaderLine(header); } } }