View Javadoc

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         // create a CertStore containing the certificates we want carried
170         // in the signature
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         // create the generator for creating an smime/signed message
208         SMIMESignedGenerator generator = new SMIMESignedGenerator();
209         
210         // add a signer to the generator - this specifies we are using SHA1
211         // the encryption algorithm used is taken from the key
212         generator.addSigner(this.privateKey, this.certificate, SMIMESignedGenerator.DIGEST_SHA1);
213         
214         // add our pool of certs and cerls (if any) to go with the signature
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         // create the generator for creating an smime/signed MimeMultipart
230         SMIMESignedGenerator generator = createGenerator();
231         
232         // do it
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         // create the generator for creating an smime/signed MimeMultipart
246         SMIMESignedGenerator generator = createGenerator();
247         
248         // do it
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 }