001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.transport.mailets.smime;
019:
020: import java.io.IOException;
021: import java.security.cert.X509Certificate;
022: import java.util.ArrayList;
023: import java.util.Iterator;
024: import java.util.List;
025:
026: import javax.mail.MessagingException;
027: import javax.mail.Multipart;
028: import javax.mail.internet.MimeBodyPart;
029: import javax.mail.internet.MimeMessage;
030: import javax.mail.internet.MimeMultipart;
031:
032: import org.apache.james.security.KeyStoreHolder;
033: import org.apache.james.security.SMIMESignerInfo;
034: import org.apache.mailet.GenericMailet;
035: import org.apache.mailet.Mail;
036: import org.apache.mailet.MailetConfig;
037: import org.bouncycastle.cms.CMSException;
038: import org.bouncycastle.mail.smime.SMIMEException;
039: import org.bouncycastle.mail.smime.SMIMESigned;
040:
041: /**
042: * <p>
043: * Verifies the s/mime signature of a message. The s/mime signing ensure that
044: * the private key owner is the real sender of the message. To be checked by
045: * this mailet the s/mime signature must contain the actual signature, the
046: * signer's certificate and optionally a set of certificate that can be used to
047: * create a chain of trust that starts from the signer's certificate and leads
048: * to a known trusted certificate.
049: * </p>
050: * <p>
051: * This check is composed by two steps: firstly it's ensured that the signature
052: * is valid, then it's checked if a chain of trust starting from the signer
053: * certificate and that leads to a trusted certificate can be created. The first
054: * check verifies that the the message has not been modified after the signature
055: * was put and that the signer's certificate was valid at the time of the
056: * signing. The latter should ensure that the signer is who he declare to be.
057: * </p>
058: * <p>
059: * The results of the checks perfomed by this mailet are wrote as a mail
060: * attribute which default name is org.apache.james.SMIMECheckSignature (it can
061: * be changed using the mailet parameter <code>mailAttribute</code>). After
062: * the check this attribute will contain a list of SMIMESignerInfo object, one
063: * for each message's signer. These objects contain the signer's certificate and
064: * the trust path.
065: * </p>
066: * <p>
067: * Optionally, specifying the parameter <code>strip</code>, the signature of
068: * the message can be stripped after the check. The message will become a
069: * standard message without an attached s/mime signature.
070: * </p>
071: * <p>
072: * The configuration parameter of this mailet are summerized below. The firsts
073: * defines the location, the format and the password of the keystore containing
074: * the certificates that are considered trusted. Note: only the trusted certificate
075: * entries are read, the key ones are not.
076: * <ul>
077: * <li>keyStoreType (default: jks): Certificate store format . "jks" is the
078: * standard java certificate store format, but pkcs12 is also quite common and
079: * compatible with standard email clients like Outlook Express and Thunderbird.
080: * <li>keyStoreFileName (default: JAVA_HOME/jre/lib/security/cacert): Certificate
081: * store path.
082: * <li>keyStorePassword (default: ""): Certificate store password.
083: * </ul>
084: * Other parameters configure the behavior of the mailet:
085: * <ul>
086: * <li>strip (default: false): Defines if the s/mime signature of the message
087: * have to be stripped after the check or not. Possible values are true and
088: * false.
089: * <li>mailAttribute (default: org.apache.james.SMIMECheckSignature):
090: * specifies in which attribute the check results will be written.
091: * <li>onlyTrusted (default: true): Usually a message signature to be
092: * considered by this mailet as authentic must be valid and trusted. Setting
093: * this mailet parameter to "false" the last condition is relaxed and also
094: * "untrusted" signature are considered will be considered as authentic.
095: * </ul>
096: * </p>
097: *
098: */
099: public class SMIMECheckSignature extends GenericMailet {
100:
101: protected KeyStoreHolder trustedCertificateStore;
102:
103: protected boolean stripSignature = false;
104: protected boolean onlyTrusted = true;
105:
106: protected String mailAttribute = "org.apache.james.SMIMECheckSignature";
107:
108: public SMIMECheckSignature() {
109: super ();
110:
111: }
112:
113: public void init() throws MessagingException {
114: MailetConfig config = getMailetConfig();
115:
116: String stripSignatureConf = config.getInitParameter("strip");
117: if (stripSignatureConf != null)
118: stripSignature = Boolean.valueOf(stripSignatureConf)
119: .booleanValue();
120:
121: String onlyTrustedConf = config.getInitParameter("onlyTrusted");
122: if (onlyTrustedConf != null)
123: onlyTrusted = Boolean.valueOf(onlyTrustedConf)
124: .booleanValue();
125:
126: String mailAttributeConf = config
127: .getInitParameter("mailAttribute");
128: if (mailAttributeConf != null)
129: mailAttribute = mailAttributeConf;
130:
131: String type = config.getInitParameter("keyStoreType");
132: String file = config.getInitParameter("keyStoreFileName");
133: String password = config.getInitParameter("keyStorePassword");
134:
135: try {
136: if (file != null)
137: trustedCertificateStore = new KeyStoreHolder(file,
138: password, type);
139: else {
140: log("No trusted store path specified, using default store.");
141: trustedCertificateStore = new KeyStoreHolder(password);
142: }
143: } catch (Exception e) {
144: throw new MessagingException(
145: "Error loading the trusted certificate store", e);
146: }
147:
148: }
149:
150: /**
151: * @see org.apache.mailet.Matcher#match(org.apache.mailet.Mail)
152: */
153: public void service(Mail mail) throws MessagingException {
154: // I extract the MimeMessage from the mail object and I check if the
155: // mime type of the mail is one of the mime types that can contain a
156: // signature.
157: MimeMessage message = mail.getMessage();
158:
159: // strippedMessage will contain the signed content of the message
160: MimeBodyPart strippedMessage = null;
161:
162: List signers = null;
163:
164: try {
165: Object obj = message.getContent();
166: SMIMESigned signed;
167: if (obj instanceof MimeMultipart)
168: signed = new SMIMESigned((MimeMultipart) message
169: .getContent());
170: else if (obj instanceof SMIMESigned)
171: signed = (SMIMESigned) obj;
172: else if (obj instanceof byte[])
173: signed = new SMIMESigned(message);
174: else
175: signed = null;
176:
177: if (signed != null) {
178: signers = trustedCertificateStore
179: .verifySignatures(signed);
180: strippedMessage = signed.getContent();
181: } else
182: log("Content not identified as signed");
183:
184: // These errors are logged but they don't cause the
185: // message to change its state. The message
186: // is considered as not signed and the process will
187: // go on.
188: } catch (CMSException e) {
189: log("Error during the analysis of the signed message", e);
190: signers = null;
191: } catch (IOException e) {
192: log("IO error during the analysis of the signed message", e);
193: signers = null;
194: } catch (SMIMEException e) {
195: log("Error during the analysis of the signed message", e);
196: signers = null;
197: } catch (Exception e) {
198: e.printStackTrace();
199: log(
200: "Generic error occured during the analysis of the message",
201: e);
202: signers = null;
203: }
204:
205: // If at least one mail signer is found
206: // the mail attributes are set.
207: if (signers != null) {
208: ArrayList signerinfolist = new ArrayList();
209:
210: for (Iterator iter = signers.iterator(); iter.hasNext();) {
211: SMIMESignerInfo info = (SMIMESignerInfo) iter.next();
212:
213: if (info.isSignValid()
214: && (!onlyTrusted || info.getCertPath() != null)) {
215: signerinfolist.add((X509Certificate) info
216: .getSignerCertificate());
217: }
218: }
219:
220: if (signerinfolist.size() > 0) {
221: mail.setAttribute(mailAttribute, signerinfolist);
222: } else {
223: // if no valid signers are found the message is not modified.
224: strippedMessage = null;
225: }
226: }
227:
228: if (stripSignature && strippedMessage != null) {
229: try {
230: Object obj = strippedMessage.getContent();
231: if (obj instanceof Multipart) {
232: message.setContent((Multipart) obj);
233: } else {
234: message.setContent(obj, strippedMessage
235: .getContentType());
236: }
237: message.saveChanges();
238: mail.setMessage(message);
239: } catch (Exception e) {
240: throw new MessagingException(
241: "Error during the extraction of the signed content from the message.",
242: e);
243: }
244: }
245: }
246:
247: }
|