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