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