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 ad...@apache.org on 2016/11/17 09:38:34 UTC
[4/9] james-project git commit: JAMES-1856 Rename crypto mailet
package to mailets for name resolution
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>trusted-server@xxx.com></I>
+ * and that <I>From: "John Smith" <jo...@xxx.com></I>
+ * we will get <I>From: "John Smith" <jo...@xxx.com>" <trusted-server@xxx.com></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: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org