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