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