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