View Javadoc

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         // I extract the MimeMessage from the mail object and I check if the
146         // mime type of the mail is one of the mime types that can contain a
147         // signature.
148         MimeMessage message = mail.getMessage();
149 
150         // strippedMessage will contain the signed content of the message 
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             // These errors are logged but they don't cause the 
169             // message to change its state. The message 
170             // is considered as not signed and the process will
171             // go on.
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         // If at least one mail signer is found 
188         // the mail attributes are set.
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                 // if no valid signers are found the message is not modified.
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 }