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