1 /****************************************************************
2 * Licensed to the Apache Software Foundation (ASF) under one *
3 * or more contributor license agreements. See the NOTICE file *
4 * distributed with this work for additional information *
5 * regarding copyright ownership. The ASF licenses this file *
6 * to you under the Apache License, Version 2.0 (the *
7 * "License"); you may not use this file except in compliance *
8 * with the License. You may obtain a copy of the License at *
9 * *
10 * http://www.apache.org/licenses/LICENSE-2.0 *
11 * *
12 * Unless required by applicable law or agreed to in writing, *
13 * software distributed under the License is distributed on an *
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
15 * KIND, either express or implied. See the License for the *
16 * specific language governing permissions and limitations *
17 * under the License. *
18 ****************************************************************/
19
20
21
22 package org.apache.james.mailet.crypto.mailet;
23
24 import java.io.IOException;
25 import java.security.cert.X509Certificate;
26 import java.util.ArrayList;
27 import java.util.Iterator;
28 import java.util.List;
29
30 import javax.mail.MessagingException;
31 import javax.mail.Multipart;
32 import javax.mail.internet.MimeBodyPart;
33 import javax.mail.internet.MimeMessage;
34 import javax.mail.internet.MimeMultipart;
35
36 import org.apache.james.mailet.crypto.KeyStoreHolder;
37 import org.apache.james.mailet.crypto.SMIMESignerInfo;
38 import org.apache.mailet.base.GenericMailet;
39 import org.apache.mailet.Mail;
40 import org.apache.mailet.MailetConfig;
41 import org.bouncycastle.cms.CMSException;
42 import org.bouncycastle.mail.smime.SMIMEException;
43 import org.bouncycastle.mail.smime.SMIMESigned;
44
45 /**
46 * <p>
47 * Verifies the s/mime signature of a message. The s/mime signing ensure that
48 * the private key owner is the real sender of the message. To be checked by
49 * this mailet the s/mime signature must contain the actual signature, the
50 * signer's certificate and optionally a set of certificate that can be used to
51 * create a chain of trust that starts from the signer's certificate and leads
52 * to a known trusted certificate.
53 * </p>
54 * <p>
55 * This check is composed by two steps: firstly it's ensured that the signature
56 * is valid, then it's checked if a chain of trust starting from the signer
57 * certificate and that leads to a trusted certificate can be created. The first
58 * check verifies that the the message has not been modified after the signature
59 * was put and that the signer's certificate was valid at the time of the
60 * signing. The latter should ensure that the signer is who he declare to be.
61 * </p>
62 * <p>
63 * The results of the checks perfomed by this mailet are wrote as a mail
64 * attribute which default name is org.apache.james.SMIMECheckSignature (it can
65 * be changed using the mailet parameter <code>mailAttribute</code>). After
66 * the check this attribute will contain a list of SMIMESignerInfo object, one
67 * for each message's signer. These objects contain the signer's certificate and
68 * the trust path.
69 * </p>
70 * <p>
71 * Optionally, specifying the parameter <code>strip</code>, the signature of
72 * the message can be stripped after the check. The message will become a
73 * standard message without an attached s/mime signature.
74 * </p>
75 * <p>
76 * The configuration parameter of this mailet are summerized below. The firsts
77 * defines the location, the format and the password of the keystore containing
78 * the certificates that are considered trusted. Note: only the trusted certificate
79 * entries are read, the key ones are not.
80 * <ul>
81 * <li>keyStoreType (default: jks): Certificate store format . "jks" is the
82 * standard java certificate store format, but pkcs12 is also quite common and
83 * compatible with standard email clients like Outlook Express and Thunderbird.
84 * <li>keyStoreFileName (default: JAVA_HOME/jre/lib/security/cacert): Certificate
85 * store path.
86 * <li>keyStorePassword (default: ""): Certificate store password.
87 * </ul>
88 * Other parameters configure the behavior of the mailet:
89 * <ul>
90 * <li>strip (default: false): Defines if the s/mime signature of the message
91 * have to be stripped after the check or not. Possible values are true and
92 * false.
93 * <li>mailAttribute (default: org.apache.james.SMIMECheckSignature):
94 * specifies in which attribute the check results will be written.
95 * <li>onlyTrusted (default: true): Usually a message signature to be
96 * considered by this mailet as authentic must be valid and trusted. Setting
97 * this mailet parameter to "false" the last condition is relaxed and also
98 * "untrusted" signature are considered will be considered as authentic.
99 * </ul>
100 * </p>
101 *
102 */
103 public class SMIMECheckSignature extends GenericMailet {
104
105 protected KeyStoreHolder trustedCertificateStore;
106
107 protected boolean stripSignature = false;
108 protected boolean onlyTrusted = true;
109
110 protected String mailAttribute = "org.apache.james.SMIMECheckSignature";
111
112 public SMIMECheckSignature() {
113 super();
114
115 }
116
117 public void init() throws MessagingException {
118 MailetConfig config = getMailetConfig();
119
120 String stripSignatureConf = config.getInitParameter("strip");
121 if (stripSignatureConf != null) stripSignature = Boolean.valueOf(stripSignatureConf).booleanValue();
122
123 String onlyTrustedConf = config.getInitParameter("onlyTrusted");
124 if (onlyTrustedConf != null) onlyTrusted = Boolean.valueOf(onlyTrustedConf).booleanValue();
125
126 String mailAttributeConf = config.getInitParameter("mailAttribute");
127 if (mailAttributeConf != null) mailAttribute = mailAttributeConf;
128
129
130 String type = config.getInitParameter("keyStoreType");
131 String file = config.getInitParameter("keyStoreFileName");
132 String password = config.getInitParameter("keyStorePassword");
133
134 try {
135 if (file != null) trustedCertificateStore = new KeyStoreHolder(file, password, type);
136 else {
137 log("No trusted store path specified, using default store.");
138 trustedCertificateStore = new KeyStoreHolder(password);
139 }
140 } catch (Exception e) {
141 throw new MessagingException("Error loading the trusted certificate store", e);
142 }
143
144 }
145 /**
146 * @see org.apache.mailet.Matcher#match(org.apache.mailet.Mail)
147 */
148 public void service(Mail mail) throws MessagingException {
149 // I extract the MimeMessage from the mail object and I check if the
150 // mime type of the mail is one of the mime types that can contain a
151 // signature.
152 MimeMessage message = mail.getMessage();
153
154 // strippedMessage will contain the signed content of the message
155 MimeBodyPart strippedMessage =null;
156
157 List signers=null;
158
159 try {
160 Object obj = message.getContent();
161 SMIMESigned signed;
162 if (obj instanceof MimeMultipart) signed = new SMIMESigned((MimeMultipart)message.getContent());
163 else if (obj instanceof SMIMESigned) signed = (SMIMESigned) obj;
164 else if (obj instanceof byte[]) signed = new SMIMESigned(message);
165 else signed = null;
166
167 if (signed != null) {
168 signers = trustedCertificateStore.verifySignatures(signed);
169 strippedMessage = signed.getContent();
170 } else log("Content not identified as signed");
171
172 // These errors are logged but they don't cause the
173 // message to change its state. The message
174 // is considered as not signed and the process will
175 // go on.
176 } catch (CMSException e) {
177 log("Error during the analysis of the signed message", e);
178 signers = null;
179 } catch (IOException e) {
180 log("IO error during the analysis of the signed message", e);
181 signers = null;
182 } catch (SMIMEException e) {
183 log("Error during the analysis of the signed message", e);
184 signers = null;
185 } catch (Exception e) {
186 e.printStackTrace();
187 log("Generic error occured during the analysis of the message", e);
188 signers = null;
189 }
190
191 // If at least one mail signer is found
192 // the mail attributes are set.
193 if (signers != null) {
194 ArrayList signerinfolist = new ArrayList();
195
196 for (Iterator iter = signers.iterator(); iter.hasNext();) {
197 SMIMESignerInfo info = (SMIMESignerInfo) iter.next();
198
199 if (info.isSignValid()
200 && (!onlyTrusted || info.getCertPath() != null)) {
201 signerinfolist.add((X509Certificate) info.getSignerCertificate());
202 }
203 }
204
205 if (signerinfolist.size() > 0) {
206 mail.setAttribute(mailAttribute, signerinfolist);
207 } else {
208 // if no valid signers are found the message is not modified.
209 strippedMessage = null;
210 }
211 }
212
213 if (stripSignature && strippedMessage != null) {
214 try {
215 Object obj = strippedMessage.getContent();
216 if (obj instanceof Multipart) {
217 message.setContent((Multipart) obj);
218 } else {
219 message.setContent(obj, strippedMessage.getContentType());
220 }
221 message.saveChanges();
222 mail.setMessage(message);
223 } catch (Exception e) {
224 throw new MessagingException(
225 "Error during the extraction of the signed content from the message.",
226 e);
227 }
228 }
229 }
230
231 }