View Javadoc

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 }