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