001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.security;
019:
020: import java.io.BufferedInputStream;
021: import java.io.File;
022: import java.io.FileInputStream;
023: import java.io.IOException;
024: import java.security.GeneralSecurityException;
025: import java.security.InvalidAlgorithmParameterException;
026: import java.security.KeyStore;
027: import java.security.KeyStoreException;
028: import java.security.NoSuchAlgorithmException;
029: import java.security.NoSuchProviderException;
030: import java.security.cert.CertPath;
031: import java.security.cert.CertPathBuilder;
032: import java.security.cert.CertPathBuilderException;
033: import java.security.cert.CertPathBuilderResult;
034: import java.security.cert.CertStore;
035: import java.security.cert.CertificateException;
036: import java.security.cert.PKIXBuilderParameters;
037: import java.security.cert.X509CertSelector;
038: import java.security.cert.X509Certificate;
039: import java.util.ArrayList;
040: import java.util.Collection;
041: import java.util.Iterator;
042: import java.util.List;
043:
044: import javax.mail.MessagingException;
045:
046: import org.bouncycastle.cms.SignerInformation;
047: import org.bouncycastle.cms.SignerInformationStore;
048: import org.bouncycastle.mail.smime.SMIMESigned;
049:
050: /**
051: * This class is used to handle in a simple way a keystore that contains a set
052: * of trusted certificates. It loads the set from the specified keystore (type,
053: * location and password are supplied during the object's creation) and it is
054: * able to verify a s/mime signature, also checking if the signer's certificate
055: * is trusted or not.
056: *
057: */
058: public class KeyStoreHolder {
059:
060: protected KeyStore keyStore;
061:
062: public KeyStoreHolder() throws IOException,
063: GeneralSecurityException {
064: // this is the default password of the sun trusted certificate store.
065: this ("changeit");
066: }
067:
068: public KeyStoreHolder(String password) throws IOException,
069: GeneralSecurityException {
070: this (System.getProperty("java.home")
071: + "/lib/security/cacerts".replace('/',
072: File.separatorChar), password, KeyStore
073: .getDefaultType());
074: }
075:
076: public KeyStoreHolder(String keyStoreFileName,
077: String keyStorePassword, String keyStoreType)
078: throws KeyStoreException, NoSuchAlgorithmException,
079: CertificateException, NoSuchProviderException, IOException {
080:
081: if (keyStorePassword == null)
082: keyStorePassword = "";
083:
084: try {
085: InitJCE.init();
086: } catch (InstantiationException e) {
087: NoSuchProviderException ex = new NoSuchProviderException(
088: "Error during cryptography provider initialization. Has bcprov-jdkxx-yyy.jar been copied in the lib directory or installed in the system?");
089: ex.initCause(e);
090: throw ex;
091: } catch (IllegalAccessException e) {
092: NoSuchProviderException ex = new NoSuchProviderException(
093: "Error during cryptography provider initialization. Has bcprov-jdkxx-yyy.jar been copied in the lib directory or installed in the system?");
094: ex.initCause(e);
095: throw ex;
096: } catch (ClassNotFoundException e) {
097: NoSuchProviderException ex = new NoSuchProviderException(
098: "Error during cryptography provider initialization. Has bcprov-jdkxx-yyy.jar been copied in the lib directory or installed in the system?");
099: ex.initCause(e);
100: throw ex;
101: }
102:
103: if (keyStoreType == null) {
104: keyStoreType = KeyStore.getDefaultType();
105: }
106:
107: keyStore = KeyStore.getInstance(keyStoreType);
108: keyStore.load(new BufferedInputStream(new FileInputStream(
109: keyStoreFileName)), keyStorePassword.toCharArray());
110: if (keyStore.size() == 0)
111: throw new KeyStoreException(
112: "The keystore must be not empty");
113: }
114:
115: /**
116: * Verifies the signature of a SMIME message.
117: *
118: * It checks also if the signer's certificate is trusted using the loaded
119: * keystore as trusted certificate store.
120: *
121: * @param signed
122: * the signed mail to check.
123: * @return a list of SMIMESignerInfo which keeps the data of each mail
124: * signer.
125: * @throws Exception
126: * @throws MessagingException
127: */
128: public List verifySignatures(SMIMESigned signed) throws Exception,
129: MessagingException {
130: CertStore certs = signed.getCertificatesAndCRLs("Collection",
131: "BC");
132: SignerInformationStore siginfo = signed.getSignerInfos();
133: Collection sigCol = siginfo.getSigners();
134: Iterator sigIterator = sigCol.iterator();
135: List result = new ArrayList(sigCol.size());
136: // I iterate over the signer collection
137: // checking if the signatures put
138: // on the message are valid.
139: for (int i = 0; sigIterator.hasNext(); i++) {
140: SignerInformation info = (SignerInformation) sigIterator
141: .next();
142: // I get the signer's certificate
143: Collection certCollection = certs.getCertificates(info
144: .getSID());
145: Iterator certIter = certCollection.iterator();
146: if (certIter.hasNext()) {
147: X509Certificate signerCert = (X509Certificate) certIter
148: .next();
149: // The issuer's certifcate is searched in the list of trusted certificate.
150: CertPath path = verifyCertificate(signerCert, certs,
151: keyStore);
152:
153: try {
154: // if the signature is valid the SMIMESignedInfo is
155: // created using "true" as last argument. If it is
156: // invalid an exception is thrown by the "verify" method
157: // and the SMIMESignerInfo is created with "false".
158: //
159: // The second argument "path" is not null if the
160: // certificate can be trusted (it can be connected
161: // by a chain of trust to a trusted certificate), null
162: // otherwise.
163: if (info.verify(signerCert, "BC")) {
164: result.add(new SMIMESignerInfo(signerCert,
165: path, true));
166: }
167: } catch (Exception e) {
168: result.add(new SMIMESignerInfo(signerCert, path,
169: false));
170: }
171: }
172: }
173: return result;
174: }
175:
176: /**
177: * Verifies the validity of the given certificate, checking its signature
178: * against the issuer's certificate.
179: *
180: * @param cert
181: * the certificate to validate
182: * @param certStore
183: * other certificates that can be used to create a chain of trust
184: * to a known trusted certificate.
185: * @param trustedStore
186: * list of trusted (usually self-signed) certificates.
187: *
188: * @return true if the certificate's signature is valid and can be validated
189: * using a trustedCertficated, false otherwise.
190: */
191: private static CertPath verifyCertificate(X509Certificate cert,
192: CertStore store, KeyStore trustedStore)
193: throws InvalidAlgorithmParameterException,
194: KeyStoreException, MessagingException,
195: CertPathBuilderException {
196:
197: if (cert == null || store == null || trustedStore == null)
198: throw new IllegalArgumentException("cert == " + cert
199: + ", store == " + store + ", trustedStore == "
200: + trustedStore);
201:
202: CertPathBuilder pathBuilder;
203:
204: // I create the CertPathBuilder object. It will be used to find a
205: // certification path that starts from the signer's certificate and
206: // leads to a trusted root certificate.
207: try {
208: pathBuilder = CertPathBuilder.getInstance("PKIX", "BC");
209: } catch (Exception e) {
210: throw new MessagingException(
211: "Error during the creation of the certpathbuilder.",
212: e);
213: }
214:
215: X509CertSelector xcs = new X509CertSelector();
216: xcs.setCertificate(cert);
217: PKIXBuilderParameters params = new PKIXBuilderParameters(
218: trustedStore, xcs);
219: params.addCertStore(store);
220: params.setRevocationEnabled(false);
221:
222: try {
223: CertPathBuilderResult result = pathBuilder.build(params);
224: CertPath path = result.getCertPath();
225: return path;
226: } catch (CertPathBuilderException e) {
227: // A certification path is not found, so null is returned.
228: return null;
229: } catch (InvalidAlgorithmParameterException e) {
230: // If this exception is thrown an error has occured during
231: // certification path search.
232: throw new MessagingException(
233: "Error during the certification path search.", e);
234: }
235: }
236: }
|