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.FileInputStream;
22 import java.io.FileNotFoundException;
23 import java.io.IOException;
24 import java.security.InvalidAlgorithmParameterException;
25 import java.security.KeyStore;
26 import java.security.KeyStoreException;
27 import java.security.NoSuchAlgorithmException;
28 import java.security.NoSuchProviderException;
29 import java.security.PrivateKey;
30 import java.security.UnrecoverableKeyException;
31 import java.security.cert.CertStore;
32 import java.security.cert.CertStoreException;
33 import java.security.cert.CertificateException;
34 import java.security.cert.CollectionCertStoreParameters;
35 import java.security.cert.X509Certificate;
36 import java.util.ArrayList;
37 import java.util.Enumeration;
38
39 import javax.mail.internet.MimeBodyPart;
40 import javax.mail.internet.MimeMessage;
41 import javax.mail.internet.MimeMultipart;
42
43 import org.bouncycastle.mail.smime.SMIMEException;
44 import org.bouncycastle.mail.smime.SMIMESignedGenerator;
45
46 /***
47 * <p>Loads a {@link java.security.KeyStore} in memory and keeps it ready for the
48 * cryptographic activity.</p>
49 * <p>It has the role of being a simpler intermediate to the crypto libraries.
50 * Uses specifically the <a href="http://www.bouncycastle.org/">Legion of the Bouncy Castle</a>
51 * libraries, particularly for the SMIME activity.</p>
52 * @version CVS $Revision: 365582 $ $Date: 2006-01-03 08:51:21 +0000 (mar, 03 gen 2006) $
53 * @since 2.2.1
54 */
55 public class KeyHolder {
56
57 /***
58 * Returns the default keystore type as specified in the Java security properties file,
59 * or the string "jks" (acronym for "Java keystore") if no such property exists.
60 * @return The defaultType, issuing a <CODE>KeyStore.getDefaultType()</CODE>.
61 */
62 public static String getDefaultType() {
63 return KeyStore.getDefaultType();
64 }
65
66 /***
67 * Holds value of property privateKey.
68 */
69 private PrivateKey privateKey;
70
71 /***
72 * Holds value of property certificate.
73 */
74 private X509Certificate certificate;
75
76 /***
77 * Holds value of property certStore.
78 */
79 private CertStore certStore;
80
81 /*** Creates a new instance of KeyHolder */
82 private KeyHolder() {
83 }
84
85 /***
86 * Creates a new instance of <CODE>KeyHolder</CODE> using {@link java.security.KeyStore} related parameters.
87 * @param keyStoreFileName The (absolute) file name of the .keystore file to load the keystore from.
88 * @param keyStorePassword The (optional) password used to check the integrity of the keystore.
89 * If given, it is used to check the integrity of the keystore data,
90 * otherwise, if null, the integrity of the keystore is not checked.
91 * @param keyAlias The alias name of the key.
92 * If missing (is null) and if there is only one key in the keystore, will default to it.
93 * @param keyAliasPassword The password of the alias for recovering the key.
94 * If missing (is null) will default to <I>keyStorePassword</I>. At least one of the passwords must be provided.
95 * @param keyStoreType The type of keystore.
96 * If missing (is null) will default to the keystore type as specified in the Java security properties file,
97 * or the string "jks" (acronym for "Java keystore") if no such property exists.
98 * @throws java.security.KeyStoreException Thrown when the <I>keyAlias</I> is specified and not found,
99 * or is not specified and either no alias is found or more than one is found.
100 * @see java.security.KeyStore#getDefaultType
101 * @see java.security.KeyStore#getInstance(String)
102 * @see java.security.KeyStore#load
103 * @see java.security.KeyStore#getKey
104 * @see java.security.KeyStore#getCertificate
105 */
106 public KeyHolder(String keyStoreFileName, String keyStorePassword, String keyAlias, String keyAliasPassword, String keyStoreType)
107 throws KeyStoreException, FileNotFoundException, IOException, NoSuchAlgorithmException, InvalidAlgorithmParameterException,
108 CertificateException, UnrecoverableKeyException, NoSuchProviderException {
109
110 try {
111 InitJCE.init();
112 } catch (InstantiationException e) {
113 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?");
114 ex.initCause(e);
115 throw ex;
116 } catch (IllegalAccessException e) {
117 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?");
118 ex.initCause(e);
119 throw ex;
120 } catch (ClassNotFoundException e) {
121 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?");
122 ex.initCause(e);
123 throw ex;
124 }
125
126 if (keyStoreType == null) {
127 keyStoreType = KeyStore.getDefaultType();
128 }
129
130 KeyStore keyStore = KeyStore.getInstance(keyStoreType);
131 keyStore.load(new BufferedInputStream(new FileInputStream(keyStoreFileName)), keyStorePassword.toCharArray());
132
133 Enumeration aliases = keyStore.aliases();
134 if (keyAlias == null) {
135 if(aliases.hasMoreElements()) {
136 keyAlias = (String) aliases.nextElement();
137 } else {
138 throw new KeyStoreException("No alias was found in keystore.");
139 }
140 if (aliases.hasMoreElements()) {
141 throw new KeyStoreException("No <keyAlias> was given and more than one alias was found in keystore.");
142
143 }
144 }
145
146 if (keyAliasPassword == null) {
147 keyAliasPassword = keyStorePassword;
148 }
149
150 this.privateKey = (PrivateKey) keyStore.getKey(keyAlias, keyAliasPassword.toCharArray());
151 if (this.privateKey == null) {
152 throw new KeyStoreException("The \"" + keyAlias + "\" PrivateKey alias was not found in keystore.");
153 }
154
155 this.certificate = (X509Certificate) keyStore.getCertificate(keyAlias);
156 if (this.certificate == null) {
157 throw new KeyStoreException("The \"" + keyAlias + "\" X509Certificate alias was not found in keystore.");
158 }
159 java.security.cert.Certificate[] certificateChain = keyStore.getCertificateChain(keyAlias);
160 ArrayList certList = new ArrayList();
161 if (certificateChain == null) {
162 certList.add(this.certificate);
163 } else {
164 for (int i = 0; i < certificateChain.length; i++) {
165 certList.add(certificateChain[i]);
166 }
167 }
168
169
170
171 this.certStore = CertStore.getInstance("Collection",
172 new CollectionCertStoreParameters(certList), "BC");
173
174 }
175
176 /***
177 * Getter for property privateKey.
178 * @return Value of property privateKey.
179 */
180 public PrivateKey getPrivateKey() {
181 return this.privateKey;
182 }
183
184 /***
185 * Getter for property certificate.
186 * @return Value of property certificate.
187 */
188 public X509Certificate getCertificate() {
189 return this.certificate;
190 }
191
192 /***
193 * Getter for property certStore.
194 * @return Value of property certStore.
195 */
196 public CertStore getCertStore() {
197 return this.certStore;
198 }
199
200 /***
201 * Creates an <CODE>SMIMESignedGenerator</CODE>. Includes a signer private key and certificate,
202 * and a pool of certs and cerls (if any) to go with the signature.
203 * @return The generated SMIMESignedGenerator.
204 */
205 public SMIMESignedGenerator createGenerator() throws CertStoreException, SMIMEException {
206
207
208 SMIMESignedGenerator generator = new SMIMESignedGenerator();
209
210
211
212 generator.addSigner(this.privateKey, this.certificate, SMIMESignedGenerator.DIGEST_SHA1);
213
214
215 generator.addCertificatesAndCRLs(this.certStore);
216
217 return generator;
218
219 }
220
221 /***
222 * Generates a signed MimeMultipart from a MimeMessage.
223 * @param message The message to sign.
224 * @return The signed <CODE>MimeMultipart</CODE>.
225 */
226 public MimeMultipart generate(MimeMessage message) throws CertStoreException,
227 NoSuchAlgorithmException, NoSuchProviderException, SMIMEException {
228
229
230 SMIMESignedGenerator generator = createGenerator();
231
232
233 return generator.generate(message, "BC");
234
235 }
236
237 /***
238 * Generates a signed MimeMultipart from a MimeBodyPart.
239 * @param content The content to sign.
240 * @return The signed <CODE>MimeMultipart</CODE>.
241 */
242 public MimeMultipart generate(MimeBodyPart content) throws CertStoreException,
243 NoSuchAlgorithmException, NoSuchProviderException, SMIMEException {
244
245
246 SMIMESignedGenerator generator = createGenerator();
247
248
249 return generator.generate(content, "BC");
250
251 }
252
253 /***
254 * Extracts the signer <I>distinguished name</I> (DN) from an <CODE>X509Certificate</CODE>.
255 * @param certificate The certificate to extract the information from.
256 * @return The requested information.
257 */
258 public static String getSignerDistinguishedName(X509Certificate certificate) {
259
260 return certificate.getSubjectDN().toString();
261
262 }
263
264 /***
265 * Extracts the signer <I>common name</I> (CN=) from an <CODE>X509Certificate</CODE> <I>distinguished name</I>.
266 * @param certificate The certificate to extract the information from.
267 * @return The requested information.
268 * @see getSignerDistinguishedName(X509Certificate)
269 */
270 public static String getSignerCN(X509Certificate certificate) {
271
272 return extractAttribute(certificate.getSubjectDN().toString(), "CN=");
273
274 }
275
276 /***
277 * Extracts the signer <I>email address</I> (EMAILADDRESS=) from an <CODE>X509Certificate</CODE> <I>distinguished name</I>.
278 * @param certificate The certificate to extract the information from.
279 * @return The requested information.
280 * @see getSignerDistinguishedName(X509Certificate)
281 */
282 public static String getSignerAddress(X509Certificate certificate) {
283
284 return extractAttribute(certificate.getSubjectDN().toString(), "EMAILADDRESS=");
285
286 }
287
288 /***
289 * Getter for property signerDistinguishedName.
290 * @return Value of property signerDistinguishedName.
291 * @see getSignerDistinguishedName(X509Certificate)
292 */
293 public String getSignerDistinguishedName() {
294 return getSignerDistinguishedName(getCertificate());
295 }
296
297 /***
298 * Getter for property signerCN.
299 * @return Value of property signerCN.
300 * @see getSignerCN(X509Certificate)
301 */
302 public String getSignerCN() {
303 return getSignerCN(getCertificate());
304 }
305
306 /***
307 * Getter for property signerAddress.
308 * @return Value of property signerMailAddress.
309 * @see getSignerAddress(X509Certificate)
310 */
311 public String getSignerAddress() {
312 return getSignerAddress(getCertificate());
313 }
314
315 private static String extractAttribute(String DistinguishedName, String attributeName) {
316
317 int i = DistinguishedName.indexOf(attributeName);
318
319 if (i < 0) {
320 return null;
321 }
322
323 i += attributeName.length();
324 int j = DistinguishedName.indexOf(",", i);
325
326 if (j - 1 <= 0) {
327 return null;
328 }
329
330 return DistinguishedName.substring(i, j).trim();
331
332 }
333
334 }