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
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
121
122
123 for (int i=0;sigIterator.hasNext();i++) {
124 SignerInformation info = (SignerInformation) sigIterator.next();
125
126 Collection certCollection = certs.getCertificates(info.getSID());
127 Iterator certIter =certCollection.iterator();
128 if (certIter.hasNext()) {
129 X509Certificate signerCert = (X509Certificate) certIter.next();
130
131 CertPath path = verifyCertificate(signerCert, certs, keyStore);
132
133 try {
134
135
136
137
138
139
140
141
142
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
177
178
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
197 return null;
198 } catch (InvalidAlgorithmParameterException e) {
199
200
201 throw new MessagingException("Error during the certification path search.", e);
202 }
203 }
204 }