View Javadoc

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