http://git-wip-us.apache.org/repos/asf/james-project/blob/a72d89b4/mailet/crypto/src/main/java/org/apache/james/transport/mailets/AbstractSign.java ---------------------------------------------------------------------- diff --git a/mailet/crypto/src/main/java/org/apache/james/transport/mailets/AbstractSign.java b/mailet/crypto/src/main/java/org/apache/james/transport/mailets/AbstractSign.java new file mode 100644 index 0000000..8f310d1 --- /dev/null +++ b/mailet/crypto/src/main/java/org/apache/james/transport/mailets/AbstractSign.java @@ -0,0 +1,714 @@ +/**************************************************************** + * 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.transport.mailets; + +import org.apache.james.transport.KeyHolder; +import org.apache.james.transport.SMIMEAttributeNames; +import org.apache.mailet.base.GenericMailet; +import org.apache.mailet.Mail; +import org.apache.mailet.MailAddress; +import org.apache.mailet.base.RFC2822Headers; + +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; +import javax.mail.internet.ParseException; + +import java.io.IOException; +import java.util.Enumeration; +import java.lang.reflect.Constructor; + +/** + * <P>Abstract mailet providing common SMIME signature services. + * It can be subclassed to make authoring signing mailets simple. + * By extending it and overriding one or more of the following methods a new behaviour can + * be quickly created without the author having to address any issue other than + * the relevant one:</P> + * <ul> + * <li>{@link #initDebug}, {@link #setDebug} and {@link #isDebug} manage the debugging mode.</li> + * <li>{@link #initExplanationText}, {@link #setExplanationText} and {@link #getExplanationText} manage the text of + * an attachment that will be added to explain the meaning of this server-side signature.</li> + * <li>{@link #initKeyHolder}, {@link #setKeyHolder} and {@link #getKeyHolder} manage the {@link KeyHolder} object that will + * contain the keys and certificates and will do the crypto work.</li> + * <li>{@link #initPostmasterSigns}, {@link #setPostmasterSigns} and {@link #isPostmasterSigns} + * determines whether messages originated by the Postmaster will be signed or not.</li> + * <li>{@link #initRebuildFrom}, {@link #setRebuildFrom} and {@link #isRebuildFrom} + * determines whether the "From:" header will be rebuilt to neutralize the wrong behaviour of + * some MUAs like Microsoft Outlook Express.</li> + * <li>{@link #initSignerName}, {@link #setSignerName} and {@link #getSignerName} manage the name + * of the signer to be shown in the explanation text.</li> + * <li>{@link #isOkToSign} controls whether the mail can be signed or not.</li> + * <li>The abstract method {@link #getWrapperBodyPart} returns the massaged {@link javax.mail.internet.MimeBodyPart} + * that will be signed, or null if the message has to be signed "as is".</li> + * </ul> + * + * <P>Handles the following init parameters:</P> + * <ul> + * <li><keyHolderClass>: Sets the class of the KeyHolder object that will handle the cryptography functions, + * for example org.apache.james.security.SMIMEKeyHolder for SMIME.</li> + * <li><debug>: if <CODE>true</CODE> some useful information is logged. + * The default is <CODE>false</CODE>.</li> + * <li><keyStoreFileName>: the {@link java.security.KeyStore} full file name.</li> + * <li><keyStorePassword>: the <CODE>KeyStore</CODE> password. + * If given, it is used to check the integrity of the keystore data, + * otherwise, if null, the integrity of the keystore is not checked.</li> + * <li><keyAlias>: the alias name to use to search the Key using {@link java.security.KeyStore#getKey}. + * The default is to look for the first and only alias in the keystore; + * if zero or more than one is found a {@link java.security.KeyStoreException} is thrown.</li> + * <li><keyAliasPassword>: the alias password. The default is to use the <CODE>KeyStore</CODE> password. + * At least one of the passwords must be provided.</li> + * <li><keyStoreType>: the type of the keystore. The default will use {@link java.security.KeyStore#getDefaultType}.</li> + * <li><postmasterSigns>: if <CODE>true</CODE> the message will be signed even if the sender is the Postmaster. + * The default is <CODE>false</CODE>.</li></li> + * <li><rebuildFrom>: If <CODE>true</CODE> will modify the "From:" header. + * For more info see {@link #isRebuildFrom}. + * The default is <CODE>false</CODE>.</li> + * <li><signerName>: the name of the signer to be shown in the explanation text. + * The default is to use the "CN=" property of the signing certificate.</li> + * <li><explanationText>: the text of an explanation of the meaning of this server-side signature. + * May contain the following substitution patterns (see also {@link #getReplacedExplanationText}): + * <CODE>[signerName]</CODE>, <CODE>[signerAddress]</CODE>, <CODE>[reversePath]</CODE>, <CODE>[headers]</CODE>. + * It should be included in the signature. + * The actual presentation of the text depends on the specific concrete mailet subclass: + * see for example {@link SMIMESign}. + * The default is to not have any explanation text.</li> + * </ul> + * @version CVS $Revision$ $Date$ + * @since 2.2.1 + */ +public abstract class AbstractSign extends GenericMailet { + + private static final String HEADERS_PATTERN = "[headers]"; + + private static final String SIGNER_NAME_PATTERN = "[signerName]"; + + private static final String SIGNER_ADDRESS_PATTERN = "[signerAddress]"; + + private static final String REVERSE_PATH_PATTERN = "[reversePath]"; + + /** + * Holds value of property debug. + */ + private boolean debug; + + /** + * Holds value of property keyHolderClass. + */ + private Class<?> keyHolderClass; + + /** + * Holds value of property explanationText. + */ + private String explanationText; + + /** + * Holds value of property keyHolder. + */ + private KeyHolder keyHolder; + + /** + * Holds value of property postmasterSigns. + */ + private boolean postmasterSigns; + + /** + * Holds value of property rebuildFrom. + */ + private boolean rebuildFrom; + + /** + * Holds value of property signerName. + */ + private String signerName; + + /** + * Gets the expected init parameters. + * @return An array containing the parameter names allowed for this mailet. + */ + protected abstract String[] getAllowedInitParameters(); + + /* ******************************************************************** */ + /* ****************** Begin of setters and getters ******************** */ + /* ******************************************************************** */ + + /** + * Initializer for property debug. + */ + protected void initDebug() { + setDebug((getInitParameter("debug") == null) ? false : Boolean.valueOf(getInitParameter("debug"))); + } + + /** + * Getter for property debug. + * @return Value of property debug. + */ + public boolean isDebug() { + return this.debug; + } + + /** + * Setter for property debug. + * @param debug New value of property debug. + */ + public void setDebug(boolean debug) { + this.debug = debug; + } + + /** + * Initializer for property keyHolderClass. + */ + protected void initKeyHolderClass() throws MessagingException { + String keyHolderClassName = getInitParameter("keyHolderClass"); + if (keyHolderClassName == null) { + throw new MessagingException("<keyHolderClass> parameter missing."); + } + try { + setKeyHolderClass(Class.forName(keyHolderClassName)); + } catch (ClassNotFoundException cnfe) { + throw new MessagingException("The specified <keyHolderClass> does not exist: " + keyHolderClassName); + } + if (isDebug()) { + log("keyHolderClass: " + getKeyHolderClass()); + } + } + + /** + * Getter for property keyHolderClass. + * @return Value of property keyHolderClass. + */ + public Class<?> getKeyHolderClass() { + return this.keyHolderClass; + } + + /** + * Setter for property keyHolderClass. + * @param keyHolderClass New value of property keyHolderClass. + */ + public void setKeyHolderClass(Class<?> keyHolderClass) { + this.keyHolderClass = keyHolderClass; + } + + /** + * Initializer for property explanationText. + */ + protected void initExplanationText() { + setExplanationText(getInitParameter("explanationText")); + if (isDebug()) { + log("Explanation text:\r\n" + getExplanationText()); + } + } + + /** + * Getter for property explanationText. + * Text to be used in the SignatureExplanation.txt file. + * @return Value of property explanationText. + */ + public String getExplanationText() { + return this.explanationText; + } + + /** + * Setter for property explanationText. + * @param explanationText New value of property explanationText. + */ + public void setExplanationText(String explanationText) { + this.explanationText = explanationText; + } + + /** + * Initializer for property keyHolder. + */ + protected void initKeyHolder() throws Exception { + Constructor<?> keyHolderConstructor; + try { + keyHolderConstructor = keyHolderClass.getConstructor(new Class[] {String.class, String.class, String.class, String.class, String.class}); + } catch (NoSuchMethodException nsme) { + throw new MessagingException("The needed constructor does not exist: " + + keyHolderClass + "(String, String, String, String, String)"); + } + + + String keyStoreFileName = getInitParameter("keyStoreFileName"); + if (keyStoreFileName == null) { + throw new MessagingException("<keyStoreFileName> parameter missing."); + } + + String keyStorePassword = getInitParameter("keyStorePassword"); + if (keyStorePassword == null) { + throw new MessagingException("<keyStorePassword> parameter missing."); + } + String keyAliasPassword = getInitParameter("keyAliasPassword"); + if (keyAliasPassword == null) { + keyAliasPassword = keyStorePassword; + if (isDebug()) { + log("<keyAliasPassword> parameter not specified: will default to the <keyStorePassword> parameter."); + } + } + + String keyStoreType = getInitParameter("keyStoreType"); + if (keyStoreType == null) { + if (isDebug()) { + log("<keyStoreType> parameter not specified: the default will be as appropriate to the keyStore requested."); + } + } + + String keyAlias = getInitParameter("keyAlias"); + if (keyAlias == null) { + if (isDebug()) { + log("<keyAlias> parameter not specified: will look for the first one in the keystore."); + } + } + + if (isDebug()) { + StringBuilder logBuffer = + new StringBuilder(1024) + .append("KeyStore related parameters:") + .append(" keyStoreFileName=").append(keyStoreFileName) + .append(", keyStoreType=").append(keyStoreType) + .append(", keyAlias=").append(keyAlias) + .append(" "); + log(logBuffer.toString()); + } + + // Certificate preparation + Object[] parameters = {keyStoreFileName, keyStorePassword, keyAlias, keyAliasPassword, keyStoreType}; + setKeyHolder((KeyHolder)keyHolderConstructor.newInstance(parameters)); + + if (isDebug()) { + log("Subject Distinguished Name: " + getKeyHolder().getSignerDistinguishedName()); + } + + if (getKeyHolder().getSignerAddress() == null) { + throw new MessagingException("Signer address missing in the certificate."); + } + } + + /** + * Getter for property keyHolder. + * It is <CODE>protected</CODE> instead of <CODE>public</CODE> for security reasons. + * @return Value of property keyHolder. + */ + protected KeyHolder getKeyHolder() { + return this.keyHolder; + } + + /** + * Setter for property keyHolder. + * It is <CODE>protected</CODE> instead of <CODE>public</CODE> for security reasons. + * @param keyHolder New value of property keyHolder. + */ + protected void setKeyHolder(KeyHolder keyHolder) { + this.keyHolder = keyHolder; + } + + /** + * Initializer for property postmasterSigns. + */ + protected void initPostmasterSigns() { + setPostmasterSigns((getInitParameter("postmasterSigns") == null) ? false : Boolean.valueOf(getInitParameter("postmasterSigns"))); + } + + /** + * Getter for property postmasterSigns. + * If true will sign messages signed by the postmaster. + * @return Value of property postmasterSigns. + */ + public boolean isPostmasterSigns() { + return this.postmasterSigns; + } + + /** + * Setter for property postmasterSigns. + * @param postmasterSigns New value of property postmasterSigns. + */ + public void setPostmasterSigns(boolean postmasterSigns) { + this.postmasterSigns = postmasterSigns; + } + + /** + * Initializer for property rebuildFrom. + */ + protected void initRebuildFrom() throws MessagingException { + setRebuildFrom((getInitParameter("rebuildFrom") == null) ? false : Boolean.valueOf(getInitParameter("rebuildFrom"))); + if (isDebug()) { + if (isRebuildFrom()) { + log("Will modify the \"From:\" header."); + } else { + log("Will leave the \"From:\" header unchanged."); + } + } + } + + /** + * Getter for property rebuildFrom. + * If true will modify the "From:" header. + * <P>The modification is as follows: + * assuming that the signer mail address in the signer certificate is <I>[email protected]></I> + * and that <I>From: "John Smith" <[email protected]></I> + * we will get <I>From: "John Smith" <[email protected]>" <[email protected]></I>.</P> + * <P>If the "ReplyTo:" header is missing or empty it will be set to the original "From:" header.</P> + * <P>Such modification is necessary to achieve a correct behaviour + * with some mail clients (e.g. Microsoft Outlook Express).</P> + * @return Value of property rebuildFrom. + */ + public boolean isRebuildFrom() { + return this.rebuildFrom; + } + + /** + * Setter for property rebuildFrom. + * @param rebuildFrom New value of property rebuildFrom. + */ + public void setRebuildFrom(boolean rebuildFrom) { + this.rebuildFrom = rebuildFrom; + } + + /** + * Initializer for property signerName. + */ + protected void initSignerName() { + setSignerName(getInitParameter("signerName")); + if (getSignerName() == null) { + if (getKeyHolder() == null) { + throw new RuntimeException("initKeyHolder() must be invoked before initSignerName()"); + } + setSignerName(getKeyHolder().getSignerCN()); + if (isDebug()) { + log("<signerName> parameter not specified: will use the certificate signer \"CN=\" attribute."); + } + } + } + + /** + * Getter for property signerName. + * @return Value of property signerName. + */ + public String getSignerName() { + return this.signerName; + } + + /** + * Setter for property signerName. + * @param signerName New value of property signerName. + */ + public void setSignerName(String signerName) { + this.signerName = signerName; + } + + /* ******************************************************************** */ + /* ****************** End of setters and getters ********************** */ + /* ******************************************************************** */ + + /** + * Mailet initialization routine. + */ + public void init() throws MessagingException { + + // check that all init parameters have been declared in allowedInitParameters + checkInitParameters(getAllowedInitParameters()); + + try { + initDebug(); + if (isDebug()) { + log("Initializing"); + } + + initKeyHolderClass(); + initKeyHolder(); + initSignerName(); + initPostmasterSigns(); + initRebuildFrom(); + initExplanationText(); + + + } catch (MessagingException me) { + throw me; + } catch (Exception e) { + log("Exception thrown", e); + throw new MessagingException("Exception thrown", e); + } finally { + if (isDebug()) { + StringBuilder logBuffer = + new StringBuilder(1024) + .append("Other parameters:") + .append(", signerName=").append(getSignerName()) + .append(", postmasterSigns=").append(postmasterSigns) + .append(", rebuildFrom=").append(rebuildFrom) + .append(" "); + log(logBuffer.toString()); + } + } + + } + + /** + * Service does the hard work, and signs + * + * @param mail the mail to sign + * @throws MessagingException if a problem arises signing the mail + */ + public void service(Mail mail) throws MessagingException { + + try { + if (!isOkToSign(mail)) { + return; + } + + MimeBodyPart wrapperBodyPart = getWrapperBodyPart(mail); + + MimeMessage originalMessage = mail.getMessage(); + + // do it + MimeMultipart signedMimeMultipart; + if (wrapperBodyPart != null) { + signedMimeMultipart = getKeyHolder().generate(wrapperBodyPart); + } else { + signedMimeMultipart = getKeyHolder().generate(originalMessage); + } + + MimeMessage newMessage = new MimeMessage(Session.getDefaultInstance(System.getProperties(), + null)); + @SuppressWarnings("unchecked") + Enumeration<String> headerEnum = originalMessage.getAllHeaderLines(); + while (headerEnum.hasMoreElements()) { + newMessage.addHeaderLine((String) headerEnum.nextElement()); + } + + newMessage.setSender(new InternetAddress(getKeyHolder().getSignerAddress(), getSignerName())); + + if (isRebuildFrom()) { + // builds a new "mixed" "From:" header + InternetAddress modifiedFromIA = new InternetAddress(getKeyHolder().getSignerAddress(), mail.getSender().toString()); + newMessage.setFrom(modifiedFromIA); + + // if the original "ReplyTo:" header is missing sets it to the original "From:" header + newMessage.setReplyTo(originalMessage.getReplyTo()); + } + + newMessage.setContent(signedMimeMultipart, signedMimeMultipart.getContentType()); + String messageId = originalMessage.getMessageID(); + newMessage.saveChanges(); + if (messageId != null) { + newMessage.setHeader(RFC2822Headers.MESSAGE_ID, messageId); + } + + mail.setMessage(newMessage); + + // marks this mail as server-signed + mail.setAttribute(SMIMEAttributeNames.SMIME_SIGNING_MAILET, this.getClass().getName()); + // it is valid for us by definition (signed here by us) + mail.setAttribute(SMIMEAttributeNames.SMIME_SIGNATURE_VALIDITY, "valid"); + + // saves the trusted server signer address + // warning: should be same as the mail address in the certificate, but it is not guaranteed + mail.setAttribute(SMIMEAttributeNames.SMIME_SIGNER_ADDRESS, getKeyHolder().getSignerAddress()); + + if (isDebug()) { + log("Message signed, reverse-path: " + mail.getSender() + ", Id: " + messageId); + } + + } catch (MessagingException me) { + log("MessagingException found - could not sign!", me); + throw me; + } catch (Exception e) { + log("Exception found", e); + throw new MessagingException("Exception thrown - could not sign!", e); + } + + } + + + /** + * <P>Checks if the mail can be signed.</P> + * <P>Rules:</P> + * <OL> + * <LI>The reverse-path != null (it is not a bounce).</LI> + * <LI>The sender user must have been SMTP authenticated.</LI> + * <LI>Either:</LI> + * <UL> + * <LI>The reverse-path is the postmaster address and {@link #isPostmasterSigns} returns <I>true</I></LI> + * <LI>or the reverse-path == the authenticated user + * and there is at least one "From:" address == reverse-path.</LI>. + * </UL> + * <LI>The message has not already been signed (mimeType != <I>multipart/signed</I> + * and != <I>application/pkcs7-mime</I>).</LI> + * </OL> + * @param mail The mail object to check. + * @return True if can be signed. + */ + protected boolean isOkToSign(Mail mail) throws MessagingException { + + MailAddress reversePath = mail.getSender(); + + // Is it a bounce? + if (reversePath == null) { + return false; + } + + String authUser = (String) mail.getAttribute("org.apache.james.SMTPAuthUser"); + // was the sender user SMTP authorized? + if (authUser == null) { + return false; + } + + // The sender is the postmaster? + if (getMailetContext().getPostmaster().equals(reversePath)) { + // should not sign postmaster sent messages? + if (!isPostmasterSigns()) { + return false; + } + } else { + // is the reverse-path user different from the SMTP authorized user? + if (!reversePath.getLocalPart().equals(authUser)) { + return false; + } + // is there no "From:" address same as the reverse-path? + if (!fromAddressSameAsReverse(mail)) { + return false; + } + } + + + // if already signed return false + MimeMessage mimeMessage = mail.getMessage(); + return !(mimeMessage.isMimeType("multipart/signed") + || mimeMessage.isMimeType("application/pkcs7-mime")); + + } + + /** + * Creates the {@link javax.mail.internet.MimeBodyPart} that will be signed. + * For example, may attach a text file explaining the meaning of the signature, + * or an XML file containing information that can be checked by other MTAs. + * @param mail The mail to massage. + * @return The massaged MimeBodyPart to sign, or null to have the whole message signed "as is". + */ + protected abstract MimeBodyPart getWrapperBodyPart(Mail mail) throws MessagingException, IOException; + + /** + * Utility method that checks if there is at least one address in the "From:" header + * same as the <i>reverse-path</i>. + * @param mail The mail to check. + * @return True if an address is found, false otherwise. + */ + protected final boolean fromAddressSameAsReverse(Mail mail) { + + MailAddress reversePath = mail.getSender(); + + if (reversePath == null) { + return false; + } + + try { + InternetAddress[] fromArray = (InternetAddress[]) mail.getMessage().getFrom(); + if (fromArray != null) { + for (InternetAddress aFromArray : fromArray) { + MailAddress mailAddress; + try { + mailAddress = new MailAddress(aFromArray); + } catch (ParseException pe) { + log("Unable to parse a \"FROM\" header address: " + aFromArray.toString() + "; ignoring."); + continue; + } + if (mailAddress.equals(reversePath)) { + return true; + } + } + } + } catch (MessagingException me) { + log("Unable to parse the \"FROM\" header; ignoring."); + } + + return false; + + } + + /** + * Utility method for obtaining a string representation of the Message's headers + * @param message The message to extract the headers from. + * @return The string containing the headers. + */ + protected final String getMessageHeaders(MimeMessage message) throws MessagingException { + @SuppressWarnings("unchecked") + Enumeration<String> heads = message.getAllHeaderLines(); + StringBuilder headBuffer = new StringBuilder(1024); + while(heads.hasMoreElements()) { + headBuffer.append(heads.nextElement().toString()).append("\r\n"); + } + return headBuffer.toString(); + } + + /** + * Prepares the explanation text making substitutions in the <I>explanationText</I> template string. + * Utility method that searches for all occurrences of some pattern strings + * and substitute them with the appropriate params. + * @param explanationText The template string for the explanation text. + * @param signerName The string that will replace the <CODE>[signerName]</CODE> pattern. + * @param signerAddress The string that will replace the <CODE>[signerAddress]</CODE> pattern. + * @param reversePath The string that will replace the <CODE>[reversePath]</CODE> pattern. + * @param headers The string that will replace the <CODE>[headers]</CODE> pattern. + * @return The actual explanation text string with all replacements done. + */ + protected final String getReplacedExplanationText(String explanationText, String signerName, + String signerAddress, String reversePath, String headers) { + + String replacedExplanationText = explanationText; + + replacedExplanationText = getReplacedString(replacedExplanationText, SIGNER_NAME_PATTERN, signerName); + replacedExplanationText = getReplacedString(replacedExplanationText, SIGNER_ADDRESS_PATTERN, signerAddress); + replacedExplanationText = getReplacedString(replacedExplanationText, REVERSE_PATH_PATTERN, reversePath); + replacedExplanationText = getReplacedString(replacedExplanationText, HEADERS_PATTERN, headers); + + return replacedExplanationText; + } + + /** + * Searches the <I>template</I> String for all occurrences of the <I>pattern</I> string + * and creates a new String substituting them with the <I>actual</I> String. + * @param template The template String to work on. + * @param pattern The string to search for the replacement. + * @param actual The actual string to use for the replacement. + */ + private String getReplacedString(String template, String pattern, String actual) { + if (actual != null) { + StringBuilder sb = new StringBuilder(template.length()); + int fromIndex = 0; + int index; + while ((index = template.indexOf(pattern, fromIndex)) >= 0) { + sb.append(template.substring(fromIndex, index)); + sb.append(actual); + fromIndex = index + pattern.length(); + } + if (fromIndex < template.length()){ + sb.append(template.substring(fromIndex)); + } + return sb.toString(); + } else { + return template; + } + } + +} +
http://git-wip-us.apache.org/repos/asf/james-project/blob/a72d89b4/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMECheckSignature.java ---------------------------------------------------------------------- diff --git a/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMECheckSignature.java b/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMECheckSignature.java new file mode 100644 index 0000000..e016a60 --- /dev/null +++ b/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMECheckSignature.java @@ -0,0 +1,228 @@ +/**************************************************************** + * 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.transport.mailets; + +import java.io.IOException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.List; + +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; + +import org.apache.james.transport.KeyStoreHolder; +import org.apache.james.transport.SMIMESignerInfo; +import org.apache.mailet.base.GenericMailet; +import org.apache.mailet.Mail; +import org.apache.mailet.MailetConfig; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.mail.smime.SMIMEException; +import org.bouncycastle.mail.smime.SMIMESigned; + +/** + * <p> + * Verifies the s/mime signature of a message. The s/mime signing ensure that + * the private key owner is the real sender of the message. To be checked by + * this mailet the s/mime signature must contain the actual signature, the + * signer's certificate and optionally a set of certificate that can be used to + * create a chain of trust that starts from the signer's certificate and leads + * to a known trusted certificate. + * </p> + * <p> + * This check is composed by two steps: firstly it's ensured that the signature + * is valid, then it's checked if a chain of trust starting from the signer + * certificate and that leads to a trusted certificate can be created. The first + * check verifies that the the message has not been modified after the signature + * was put and that the signer's certificate was valid at the time of the + * signing. The latter should ensure that the signer is who he declare to be. + * </p> + * <p> + * The results of the checks perfomed by this mailet are wrote as a mail + * attribute which default name is org.apache.james.SMIMECheckSignature (it can + * be changed using the mailet parameter <code>mailAttribute</code>). After + * the check this attribute will contain a list of SMIMESignerInfo object, one + * for each message's signer. These objects contain the signer's certificate and + * the trust path. + * </p> + * <p> + * Optionally, specifying the parameter <code>strip</code>, the signature of + * the message can be stripped after the check. The message will become a + * standard message without an attached s/mime signature. + * </p> + * <p> + * The configuration parameter of this mailet are summerized below. The firsts + * defines the location, the format and the password of the keystore containing + * the certificates that are considered trusted. Note: only the trusted certificate + * entries are read, the key ones are not. + * <ul> + * <li>keyStoreType (default: jks): Certificate store format . "jks" is the + * standard java certificate store format, but pkcs12 is also quite common and + * compatible with standard email clients like Outlook Express and Thunderbird. + * <li>keyStoreFileName (default: JAVA_HOME/jre/lib/security/cacert): Certificate + * store path. + * <li>keyStorePassword (default: ""): Certificate store password. + * </ul> + * Other parameters configure the behavior of the mailet: + * <ul> + * <li>strip (default: false): Defines if the s/mime signature of the message + * have to be stripped after the check or not. Possible values are true and + * false. + * <li>mailAttribute (default: org.apache.james.SMIMECheckSignature): + * specifies in which attribute the check results will be written. + * <li>onlyTrusted (default: true): Usually a message signature to be + * considered by this mailet as authentic must be valid and trusted. Setting + * this mailet parameter to "false" the last condition is relaxed and also + * "untrusted" signature are considered will be considered as authentic. + * </ul> + * </p> + * + */ +public class SMIMECheckSignature extends GenericMailet { + + protected KeyStoreHolder trustedCertificateStore; + + protected boolean stripSignature = false; + protected boolean onlyTrusted = true; + + protected String mailAttribute = "org.apache.james.SMIMECheckSignature"; + + public SMIMECheckSignature() { + super(); + + } + + public void init() throws MessagingException { + MailetConfig config = getMailetConfig(); + + String stripSignatureConf = config.getInitParameter("strip"); + if (stripSignatureConf != null) stripSignature = Boolean.valueOf(stripSignatureConf); + + String onlyTrustedConf = config.getInitParameter("onlyTrusted"); + if (onlyTrustedConf != null) onlyTrusted = Boolean.valueOf(onlyTrustedConf); + + String mailAttributeConf = config.getInitParameter("mailAttribute"); + if (mailAttributeConf != null) mailAttribute = mailAttributeConf; + + + String type = config.getInitParameter("keyStoreType"); + String file = config.getInitParameter("keyStoreFileName"); + String password = config.getInitParameter("keyStorePassword"); + + try { + if (file != null) trustedCertificateStore = new KeyStoreHolder(file, password, type); + else { + log("No trusted store path specified, using default store."); + trustedCertificateStore = new KeyStoreHolder(password); + } + } catch (Exception e) { + throw new MessagingException("Error loading the trusted certificate store", e); + } + + } + /** + * @see org.apache.mailet.Matcher#match(org.apache.mailet.Mail) + */ + public void service(Mail mail) throws MessagingException { + // I extract the MimeMessage from the mail object and I check if the + // mime type of the mail is one of the mime types that can contain a + // signature. + MimeMessage message = mail.getMessage(); + + // strippedMessage will contain the signed content of the message + MimeBodyPart strippedMessage =null; + + List<SMIMESignerInfo> signers=null; + + try { + Object obj = message.getContent(); + SMIMESigned signed; + if (obj instanceof MimeMultipart) signed = new SMIMESigned((MimeMultipart)message.getContent()); + else if (obj instanceof SMIMESigned) signed = (SMIMESigned) obj; + else if (obj instanceof byte[]) signed = new SMIMESigned(message); + else signed = null; + + if (signed != null) { + signers = trustedCertificateStore.verifySignatures(signed); + strippedMessage = signed.getContent(); + } else log("Content not identified as signed"); + + // These errors are logged but they don't cause the + // message to change its state. The message + // is considered as not signed and the process will + // go on. + } catch (CMSException e) { + log("Error during the analysis of the signed message", e); + signers = null; + } catch (IOException e) { + log("IO error during the analysis of the signed message", e); + signers = null; + } catch (SMIMEException e) { + log("Error during the analysis of the signed message", e); + signers = null; + } catch (Exception e) { + e.printStackTrace(); + log("Generic error occured during the analysis of the message", e); + signers = null; + } + + // If at least one mail signer is found + // the mail attributes are set. + if (signers != null) { + ArrayList<X509Certificate> signerinfolist = new ArrayList<X509Certificate>(); + + for (SMIMESignerInfo info : signers) { + if (info.isSignValid() + && (!onlyTrusted || info.getCertPath() != null)) { + signerinfolist.add(info.getSignerCertificate()); + } + } + + if (signerinfolist.size() > 0) { + mail.setAttribute(mailAttribute, signerinfolist); + } else { + // if no valid signers are found the message is not modified. + strippedMessage = null; + } + } + + if (stripSignature && strippedMessage != null) { + try { + Object obj = strippedMessage.getContent(); + if (obj instanceof Multipart) { + message.setContent((Multipart) obj); + } else { + message.setContent(obj, strippedMessage.getContentType()); + } + message.saveChanges(); + mail.setMessage(message); + } catch (Exception e) { + throw new MessagingException( + "Error during the extraction of the signed content from the message.", + e); + } + } + } + +} http://git-wip-us.apache.org/repos/asf/james-project/blob/a72d89b4/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMEDecrypt.java ---------------------------------------------------------------------- diff --git a/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMEDecrypt.java b/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMEDecrypt.java new file mode 100644 index 0000000..88b4890 --- /dev/null +++ b/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMEDecrypt.java @@ -0,0 +1,169 @@ +/**************************************************************** + * 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.transport.mailets; + +import java.io.IOException; +import java.security.GeneralSecurityException; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; + +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.Part; +import javax.mail.internet.MimeMessage; + +import org.apache.james.transport.SMIMEKeyHolder; +import org.apache.mailet.Mail; +import org.apache.mailet.MailetConfig; +import org.apache.mailet.base.GenericMailet; +import org.bouncycastle.cms.CMSException; +import org.bouncycastle.cms.RecipientId; +import org.bouncycastle.cms.RecipientInformation; +import org.bouncycastle.cms.RecipientInformationStore; +import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient; +import org.bouncycastle.mail.smime.SMIMEEnveloped; +import org.bouncycastle.mail.smime.SMIMEUtil; + +/** + * This mailet decrypts a s/mime encrypted message. It takes as input an + * encrypted message and it tries to dechiper it using the key specified in its + * configuration. If the decryption is successful the mail will be changed and + * it will contain the decrypted message. The mail attribute + * <code>org.apache.james.SMIMEDecrypt</code> will contain the public + * certificate of the key used in the process. + * + * The configuration parameters of this mailet are summarized below. The firsts + * define the keystore where the key that will be used to decrypt messages is + * saved. + * <ul> + * <li>keyStoreType (default: system dependent): defines the type of the store. + * Usually jks, pkcs12 or pkcs7</li> + * <li>keyStoreFileName (mandatory): private key store path.</li> + * <li>keyStorePassword (default: ""): private key store password</li> + * </ul> + * The other parameters define which private key have to be used. (if the store + * contains more than one key). + * <ul> + * <li>keyAlias: private key alias.</li> + * <li>keyPass: private key password</li> + * </ul> + * + */ +public class SMIMEDecrypt extends GenericMailet { + + private SMIMEKeyHolder keyHolder; + protected String mailAttribute = "org.apache.james.SMIMEDecrypt"; + + public void init() throws MessagingException { + super.init(); + + MailetConfig config = getMailetConfig(); + + String privateStoreType = config.getInitParameter("keyStoreType"); + + String privateStoreFile = config.getInitParameter("keyStoreFileName"); + if (privateStoreFile == null) throw new MessagingException("No keyStoreFileName specified"); + + String privateStorePass = config.getInitParameter("keyStorePassword"); + + String keyAlias= config.getInitParameter("keyAlias"); + String keyPass = config.getInitParameter("keyAliasPassword"); + + String mailAttributeConf = config.getInitParameter("mailAttribute"); + if (mailAttributeConf != null) mailAttribute = mailAttributeConf; + + try { + keyHolder = new SMIMEKeyHolder(privateStoreFile, privateStorePass, keyAlias, keyPass, privateStoreType); + } catch (IOException e) { + throw new MessagingException("Error loading keystore", e); + } catch (GeneralSecurityException e) { + throw new MessagingException("Error loading keystore", e); + } + + + } + + /** + * @see org.apache.mailet.Mailet#service(org.apache.mailet.Mail) + */ + public void service(Mail mail) throws MessagingException { + MimeMessage message = mail.getMessage(); + Part strippedMessage = null; + log("Starting message decryption.."); + if (message.isMimeType("application/x-pkcs7-mime") || message.isMimeType("application/pkcs7-mime")) { + try { + SMIMEEnveloped env = new SMIMEEnveloped(message); + RecipientInformationStore informationStore = env.getRecipientInfos(); + @SuppressWarnings("unchecked") + Collection<RecipientInformation> recipients = informationStore.getRecipients(); + for (RecipientInformation info : recipients) { + RecipientId id = info.getRID(); + if (id.match(keyHolder.getCertificate())) { + try { + JceKeyTransEnvelopedRecipient recipient = new JceKeyTransEnvelopedRecipient(keyHolder.getPrivateKey()); + // strippedMessage contains the decrypted message. + strippedMessage = SMIMEUtil.toMimeBodyPart(info.getContent(recipient)); + log("Encrypted message decrypted"); + } catch (Exception e) { + throw new MessagingException("Error during the decryption of the message", e); + } + } else { + log("Found an encrypted message but it isn't encrypted for the supplied key"); + } + } + } catch (CMSException e) { + throw new MessagingException("Error during the decryption of the message",e); + } + } + + // if the decryption has been successful.. + if (strippedMessage != null) { + // I put the private key's public certificate as a mailattribute. + // I create a list of certificate because I want to minic the + // behavior of the SMIMEVerifySignature mailet. In that way + // it is possible to reuse the same matchers to analyze + // the result of the operation. + ArrayList<X509Certificate> list = new ArrayList<X509Certificate>(1); + list.add(keyHolder.getCertificate()); + mail.setAttribute(mailAttribute, list); + + // I start the message stripping. + try { + MimeMessage newmex = new MimeMessage(message); + Object obj = strippedMessage.getContent(); + if (obj instanceof Multipart) { + log("The message is multipart, content type "+((Multipart)obj).getContentType()); + newmex.setContent((Multipart)obj); + } else { + newmex.setContent(obj, strippedMessage.getContentType()); + newmex.setDisposition(null); + } + newmex.saveChanges(); + mail.setMessage(newmex); + } catch (IOException e) { + log("Error during the strip of the encrypted message"); + throw new MessagingException("Error during the stripping of the encrypted message",e); + } + } + } +} http://git-wip-us.apache.org/repos/asf/james-project/blob/a72d89b4/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMESign.java ---------------------------------------------------------------------- diff --git a/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMESign.java b/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMESign.java new file mode 100644 index 0000000..e875ab9 --- /dev/null +++ b/mailet/crypto/src/main/java/org/apache/james/transport/mailets/SMIMESign.java @@ -0,0 +1,216 @@ +/**************************************************************** + * 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.transport.mailets; + +import org.apache.mailet.Mail; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; + +import java.io.IOException; + +/** + * <p>Puts a <I>server-side</I> SMIME signature on a message. + * It is a concrete subclass of {@link Sign}, with very few modifications to it, + * to specialize for SMIME.</p> + * + * <P>Handles the following init parameters (will comment only the differences from {@link AbstractSign}):</P> + * <ul> + * <li><debug>.</li> + * <li><keyStoreFileName>.</li> + * <li><keyStorePassword>.</li> + * <li><keyAlias>.</li> + * <li><keyAliasPassword>.</li> + * <li><keyStoreType>.</li> + * <li><postmasterSigns>. The default is <CODE>true</CODE>.</li> + * <li><rebuildFrom>. The default is <CODE>true</CODE>.</li> + * <li><signerName>.</li> + * <li><explanationText>. There is a default explanation string template in English, + * displaying also all the headers of the original message (see {@link #getExplanationText}).</li> + * </ul> + * @version CVS $Revision$ $Date$ + * @since 2.3.0 + */ +public class SMIMESign extends Sign { + + /** + * Return a string describing this mailet. + * + * @return a string describing this mailet + */ + public String getMailetInfo() { + return "SMIME Signature Mailet"; + } + + /** + * + */ + protected String[] getAllowedInitParameters() { + return new String[]{ + "debug", + "keyStoreFileName", + "keyStorePassword", + "keyStoreType", + "keyAlias", + "keyAliasPassword", + "signerName", + "postmasterSigns", + "rebuildFrom", + "explanationText" + }; + } + + /* ******************************************************************** */ + /* ****************** Begin of setters and getters ******************** */ + /* ******************************************************************** */ + + /** + * Gets text offering an explanation. + * If the <CODE><explanationText></CODE> init parameter is missing + * returns the following default explanation template string: + * <pre><code> + * The message this file is attached to has been signed on the server by + * "[signerName]" <[signerAddress]> + * to certify that the sender is known and truly has the following address (reverse-path): + * [reversePath] + * and that the original message has the following message headers: + * + * [headers] + * + * The signature envelopes this attachment too. + * Please check the signature integrity. + * + * "[signerName]" <[signerAddress]> + * </code></pre> + */ + public String getExplanationText() { + String explanationText = super.getExplanationText(); + if (explanationText == null) { + explanationText = "The message this file is attached to has been signed on the server by\r\n" + + "\t\"[signerName]\" <[signerAddress]>" + + "\r\nto certify that the sender is known and truly has the following address (reverse-path):\r\n" + + "\t[reversePath]" + + "\r\nand that the original message has the following message headers:\r\n" + + "\r\n[headers]" + + "\r\n\r\nThe signature envelopes this attachment too." + + "\r\nPlease check the signature integrity." + + "\r\n\r\n" + + "\t\"[signerName]\" <[signerAddress]>"; + } + + return explanationText; + } + + /** + * Initializer for property keyHolderClass. + * Hardcodes it to {@link org.apache.james.transport.SMIMEKeyHolder}. + */ + protected void initKeyHolderClass() throws MessagingException { + String keyHolderClassName = "org.apache.james.security.SMIMEKeyHolder"; + try { + setKeyHolderClass(Class.forName(keyHolderClassName)); + } catch (ClassNotFoundException cnfe) { + throw new MessagingException(keyHolderClassName + "does not exist."); + } + if (isDebug()) { + log("keyHolderClass: " + getKeyHolderClass()); + } + } + + /** + * If the <CODE><postmasterSigns></CODE> init parameter is missing sets it to <I>true</I>. + */ + protected void initPostmasterSigns() { + setPostmasterSigns((getInitParameter("postmasterSigns") == null) ? true : Boolean.valueOf(getInitParameter("postmasterSigns"))); + } + + /** + * If the <CODE><rebuildFrom></CODE> init parameter is missing sets it to <I>true</I>. + */ + protected void initRebuildFrom() throws MessagingException { + setRebuildFrom((getInitParameter("rebuildFrom") == null) ? true : Boolean.valueOf(getInitParameter("rebuildFrom"))); + if (isDebug()) { + if (isRebuildFrom()) { + log("Will modify the \"From:\" header."); + } else { + log("Will leave the \"From:\" header unchanged."); + } + } + } + + /* ******************************************************************** */ + /* ****************** End of setters and getters ********************** */ + /* ******************************************************************** */ + + /** + * A text file with the massaged contents of {@link #getExplanationText} + * is attached to the original message. + */ + protected MimeBodyPart getWrapperBodyPart(Mail mail) throws MessagingException, IOException { + + String explanationText = getExplanationText(); + + // if there is no explanation text there should be no wrapping + if (explanationText == null) { + return null; + } + + MimeMessage originalMessage = mail.getMessage(); + + MimeBodyPart messagePart = new MimeBodyPart(); + MimeBodyPart signatureReason = new MimeBodyPart(); + + String contentType = originalMessage.getContentType(); + Object content = originalMessage.getContent(); + + if (contentType != null && content != null) { + messagePart.setContent(content, contentType); + } else { + throw new MessagingException("Either the content type or the content is null"); + } + + String headers = getMessageHeaders(originalMessage); + + signatureReason.setText(getReplacedExplanationText(getExplanationText(), + getSignerName(), + getKeyHolder().getSignerAddress(), + mail.getSender().toString(), + headers)); + + signatureReason.setFileName("SignatureExplanation.txt"); + + MimeMultipart wrapperMultiPart = new MimeMultipart(); + + wrapperMultiPart.addBodyPart(messagePart); + wrapperMultiPart.addBodyPart(signatureReason); + + MimeBodyPart wrapperBodyPart = new MimeBodyPart(); + + wrapperBodyPart.setContent(wrapperMultiPart); + + return wrapperBodyPart; + } + +} + http://git-wip-us.apache.org/repos/asf/james-project/blob/a72d89b4/mailet/crypto/src/main/java/org/apache/james/transport/mailets/Sign.java ---------------------------------------------------------------------- diff --git a/mailet/crypto/src/main/java/org/apache/james/transport/mailets/Sign.java b/mailet/crypto/src/main/java/org/apache/james/transport/mailets/Sign.java new file mode 100644 index 0000000..66cf4c9 --- /dev/null +++ b/mailet/crypto/src/main/java/org/apache/james/transport/mailets/Sign.java @@ -0,0 +1,207 @@ +/**************************************************************** + * 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.transport.mailets; + +import org.apache.mailet.Mail; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; + +import java.io.IOException; + +/** + * <p>Puts a <I>server-side</I> signature on a message. + * It is a concrete subclass of {@link AbstractSign}, with very few modifications to it.</p> + * <p>A text file with an explanation text is attached to the original message, + * and the resulting message with all its attachments is signed. + * The resulting appearence of the message is almost unchanged: only an extra attachment + * and the signature are added.</p> + *<p>The kind of signuture depends on the value of the <keyHolderClass> init parameter. + * + * <P>Handles the following init parameters (will comment only the differences from {@link AbstractSign}):</P> + * <ul> + * <li><keyHolderClass>: Sets the class of the KeyHolder object that will handle the cryptography functions, + * for example org.apache.james.security.SMIMEKeyHolder for SMIME.</li> + * <li><debug>.</li> + * <li><keyStoreFileName>.</li> + * <li><keyStorePassword>.</li> + * <li><keyAlias>.</li> + * <li><keyAliasPassword>.</li> + * <li><keyStoreType>.</li> + * <li><postmasterSigns>. The default is <CODE>true</CODE>.</li> + * <li><rebuildFrom>. The default is <CODE>true</CODE>.</li> + * <li><signerName>.</li> + * <li><explanationText>. There is a default explanation string template in English, + * displaying also all the headers of the original message (see {@link #getExplanationText}).</li> + * </ul> + * @version CVS $Revision$ $Date$ + * @since 2.2.1 + */ +public class Sign extends AbstractSign { + + /** + * Return a string describing this mailet. + * + * @return a string describing this mailet + */ + public String getMailetInfo() { + return "Signature Mailet"; + } + + /** + * + */ + protected String[] getAllowedInitParameters() { + return new String[]{ + "keyHolderClass", + "debug", + "keyStoreFileName", + "keyStorePassword", + "keyStoreType", + "keyAlias", + "keyAliasPassword", + "signerName", + "postmasterSigns", + "rebuildFrom", + "explanationText" + }; + } + + /* ******************************************************************** */ + /* ****************** Begin of setters and getters ******************** */ + /* ******************************************************************** */ + + /** + * Gets text offering an explanation. + * If the <CODE><explanationText></CODE> init parameter is missing + * returns the following default explanation template string: + * <pre><code> + * The message this file is attached to has been signed on the server by + * "[signerName]" <[signerAddress]> + * to certify that the sender is known and truly has the following address (reverse-path): + * [reversePath] + * and that the original message has the following message headers: + * + * [headers] + * + * The signature envelopes this attachment too. + * Please check the signature integrity. + * + * "[signerName]" <[signerAddress]> + * </code></pre> + */ + public String getExplanationText() { + String explanationText = super.getExplanationText(); + if (explanationText == null) { + explanationText = "The message this file is attached to has been signed on the server by\r\n" + + "\t\"[signerName]\" <[signerAddress]>" + + "\r\nto certify that the sender is known and truly has the following address (reverse-path):\r\n" + + "\t[reversePath]" + + "\r\nand that the original message has the following message headers:\r\n" + + "\r\n[headers]" + + "\r\n\r\nThe signature envelopes this attachment too." + + "\r\nPlease check the signature integrity." + + "\r\n\r\n" + + "\t\"[signerName]\" <[signerAddress]>"; + } + + return explanationText; + } + + /** + * If the <CODE><postmasterSigns></CODE> init parameter is missing sets it to <I>true</I>. + */ + protected void initPostmasterSigns() { + setPostmasterSigns((getInitParameter("postmasterSigns") == null) ? true : Boolean.valueOf(getInitParameter("postmasterSigns"))); + } + + /** + * If the <CODE><rebuildFrom></CODE> init parameter is missing sets it to <I>true</I>. + */ + protected void initRebuildFrom() throws MessagingException { + setRebuildFrom((getInitParameter("rebuildFrom") == null) ? true : Boolean.valueOf(getInitParameter("rebuildFrom"))); + if (isDebug()) { + if (isRebuildFrom()) { + log("Will modify the \"From:\" header."); + } else { + log("Will leave the \"From:\" header unchanged."); + } + } + } + + /* ******************************************************************** */ + /* ****************** End of setters and getters ********************** */ + /* ******************************************************************** */ + + /** + * A text file with the massaged contents of {@link #getExplanationText} + * is attached to the original message. + */ + protected MimeBodyPart getWrapperBodyPart(Mail mail) throws MessagingException, IOException { + + String explanationText = getExplanationText(); + + // if there is no explanation text there should be no wrapping + if (explanationText == null) { + return null; + } + + MimeMessage originalMessage = mail.getMessage(); + + MimeBodyPart messagePart = new MimeBodyPart(); + MimeBodyPart signatureReason = new MimeBodyPart(); + + String contentType = originalMessage.getContentType(); + Object content = originalMessage.getContent(); + + if (contentType != null && content != null) { + messagePart.setContent(content, contentType); + } else { + throw new MessagingException("Either the content type or the content is null"); + } + + String headers = getMessageHeaders(originalMessage); + + signatureReason.setText(getReplacedExplanationText(getExplanationText(), + getSignerName(), + getKeyHolder().getSignerAddress(), + mail.getSender().toString(), + headers)); + + signatureReason.setFileName("SignatureExplanation.txt"); + + MimeMultipart wrapperMultiPart = new MimeMultipart(); + + wrapperMultiPart.addBodyPart(messagePart); + wrapperMultiPart.addBodyPart(signatureReason); + + MimeBodyPart wrapperBodyPart = new MimeBodyPart(); + + wrapperBodyPart.setContent(wrapperMultiPart); + + return wrapperBodyPart; + } + +} + http://git-wip-us.apache.org/repos/asf/james-project/blob/a72d89b4/mailet/crypto/src/main/java/org/apache/james/transport/mailets/package.html ---------------------------------------------------------------------- diff --git a/mailet/crypto/src/main/java/org/apache/james/transport/mailets/package.html b/mailet/crypto/src/main/java/org/apache/james/transport/mailets/package.html new file mode 100644 index 0000000..2dcfc17 --- /dev/null +++ b/mailet/crypto/src/main/java/org/apache/james/transport/mailets/package.html @@ -0,0 +1,21 @@ +<!-- + 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. +--> +<body> +<p>Cryptographic mail processing agents.</p> +</body> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
