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.security;
21  
22  import java.io.BufferedInputStream;
23  import java.io.File;
24  import java.io.FileInputStream;
25  import java.io.IOException;
26  import java.security.GeneralSecurityException;
27  import java.security.InvalidAlgorithmParameterException;
28  import java.security.KeyStore;
29  import java.security.KeyStoreException;
30  import java.security.NoSuchAlgorithmException;
31  import java.security.NoSuchProviderException;
32  import java.security.cert.CertPath;
33  import java.security.cert.CertPathBuilder;
34  import java.security.cert.CertPathBuilderException;
35  import java.security.cert.CertPathBuilderResult;
36  import java.security.cert.CertStore;
37  import java.security.cert.CertificateException;
38  import java.security.cert.PKIXBuilderParameters;
39  import java.security.cert.X509CertSelector;
40  import java.security.cert.X509Certificate;
41  import java.util.ArrayList;
42  import java.util.Collection;
43  import java.util.Iterator;
44  import java.util.List;
45  
46  import javax.mail.MessagingException;
47  
48  import org.bouncycastle.cms.SignerInformation;
49  import org.bouncycastle.cms.SignerInformationStore;
50  import org.bouncycastle.mail.smime.SMIMESigned;
51  
52  /***
53   * This class is used to handle in a simple way a keystore that contains a set
54   * of trusted certificates. It loads the set from the specified keystore (type,
55   * location and password are supplied during the object's creation) and it is
56   * able to verify a s/mime signature, also checking if the signer's certificate
57   * is trusted or not.
58   * 
59   */
60  public class KeyStoreHolder {
61      
62      protected KeyStore keyStore;
63      
64      public KeyStoreHolder () throws IOException, GeneralSecurityException {
65          // this is the default password of the sun trusted certificate store.
66          this("changeit");
67      }
68      
69      public KeyStoreHolder (String password) throws IOException, GeneralSecurityException {
70          this(System.getProperty("java.home")+"/lib/security/cacerts".replace('/', File.separatorChar), password, KeyStore.getDefaultType());
71      }
72      
73      public KeyStoreHolder(String keyStoreFileName, String keyStorePassword, String keyStoreType) 
74          throws KeyStoreException, NoSuchAlgorithmException, CertificateException, NoSuchProviderException, IOException {
75          
76          if (keyStorePassword == null) keyStorePassword = "";
77  
78          try {
79              InitJCE.init();
80          } catch (InstantiationException e) {
81              NoSuchProviderException ex = new NoSuchProviderException("Error during cryptography provider initialization. Has bcprov-jdkxx-yyy.jar been copied in the lib directory or installed in the system?");
82              ex.initCause(e);
83              throw ex;
84          } catch (IllegalAccessException e) {
85              NoSuchProviderException ex = new NoSuchProviderException("Error during cryptography provider initialization. Has bcprov-jdkxx-yyy.jar been copied in the lib directory or installed in the system?");
86              ex.initCause(e);
87              throw ex;
88          } catch (ClassNotFoundException e) {
89              NoSuchProviderException ex = new NoSuchProviderException("Error during cryptography provider initialization. Has bcprov-jdkxx-yyy.jar been copied in the lib directory or installed in the system?");
90              ex.initCause(e);
91              throw ex;
92          }
93          
94          if (keyStoreType == null) {
95              keyStoreType = KeyStore.getDefaultType();
96          }
97          
98          keyStore = KeyStore.getInstance(keyStoreType);        
99          keyStore.load(new BufferedInputStream(new FileInputStream(keyStoreFileName)), keyStorePassword.toCharArray());
100         if (keyStore.size() == 0) throw new KeyStoreException("The keystore must be not empty");
101     }
102     
103     /***
104      * Verifies the signature of a SMIME message.
105      * 
106      * It checks also if the signer's certificate is trusted using the loaded
107      * keystore as trusted certificate store.
108      * 
109      * @param signed
110      *            the signed mail to check.
111      * @return a list of SMIMESignerInfo which keeps the data of each mail
112      *         signer.
113      * @throws Exception
114      * @throws MessagingException
115      */
116     public List verifySignatures(SMIMESigned signed) throws Exception, MessagingException {
117         CertStore certs = signed.getCertificatesAndCRLs("Collection", "BC");
118         SignerInformationStore siginfo = signed.getSignerInfos();
119         Collection sigCol = siginfo.getSigners();
120         Iterator sigIterator = sigCol.iterator();
121         List result = new ArrayList(sigCol.size());
122         // I iterate over the signer collection 
123         // checking if the signatures put
124         // on the message are valid.
125         for (int i=0;sigIterator.hasNext();i++) {
126             SignerInformation info = (SignerInformation) sigIterator.next();
127             // I get the signer's certificate
128             Collection certCollection = certs.getCertificates(info.getSID());
129             Iterator certIter  =certCollection.iterator();
130             if (certIter.hasNext()) {
131                 X509Certificate signerCert = (X509Certificate) certIter.next();
132                 // The issuer's certifcate is searched in the list of trusted certificate.
133                 CertPath path = verifyCertificate(signerCert, certs, keyStore);
134 
135                 try {
136                     // if the signature is valid the SMIMESignedInfo is 
137                     // created using "true" as last argument. If it is  
138                     // invalid an exception is thrown by the "verify" method
139                     // and the SMIMESignerInfo is created with "false".
140                     //
141                     // The second argument "path" is not null if the 
142                     // certificate can be trusted (it can be connected 
143                     // by a chain of trust to a trusted certificate), null
144                     // otherwise.
145                     if (info.verify(signerCert, "BC")) {
146                         result.add(new SMIMESignerInfo(signerCert, path, true));
147                     }
148                 } catch (Exception e) { 
149                     result.add(new SMIMESignerInfo(signerCert,path, false)); 
150                 }
151             }
152         }
153         return result;
154     }
155 
156     /***
157      * Verifies the validity of the given certificate, checking its signature
158      * against the issuer's certificate.
159      * 
160      * @param cert
161      *            the certificate to validate
162      * @param certStore
163      *            other certificates that can be used to create a chain of trust
164      *            to a known trusted certificate.
165      * @param trustedStore
166      *            list of trusted (usually self-signed) certificates.
167      * 
168      * @return true if the certificate's signature is valid and can be validated
169      *         using a trustedCertficated, false otherwise.
170      */
171     private static CertPath verifyCertificate(X509Certificate cert, CertStore store, KeyStore trustedStore) 
172         throws InvalidAlgorithmParameterException, KeyStoreException, MessagingException, CertPathBuilderException {
173         
174         if (cert == null || store == null || trustedStore == null) throw new IllegalArgumentException("cert == "+cert+", store == "+store+", trustedStore == "+trustedStore);
175         
176         CertPathBuilder pathBuilder;
177         
178         // I create the CertPathBuilder object. It will be used to find a
179         // certification path that starts from the signer's certificate and
180         // leads to a trusted root certificate.
181         try {
182             pathBuilder = CertPathBuilder.getInstance("PKIX", "BC");
183         } catch (Exception e) {
184             throw new MessagingException("Error during the creation of the certpathbuilder.", e);
185         }
186         
187         X509CertSelector xcs = new X509CertSelector();
188         xcs.setCertificate(cert);
189         PKIXBuilderParameters params = new PKIXBuilderParameters(trustedStore, xcs);
190         params.addCertStore(store);
191         params.setRevocationEnabled(false);
192         
193         try {
194             CertPathBuilderResult result = pathBuilder.build(params);
195             CertPath path = result.getCertPath();
196             return path;
197         } catch (CertPathBuilderException e) {
198             // A certification path is not found, so null is returned.
199             return null;
200         } catch (InvalidAlgorithmParameterException e) {
201             // If this exception is thrown an error has occured during
202             // certification path search. 
203             throw new MessagingException("Error during the certification path search.", e);
204         }
205     }
206 }