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  
21  
22  package org.apache.james.mailet.crypto;
23  
24  import java.io.BufferedInputStream;
25  import java.io.FileInputStream;
26  import java.io.FileNotFoundException;
27  import java.io.IOException;
28  import java.security.InvalidAlgorithmParameterException;
29  import java.security.KeyStore;
30  import java.security.KeyStoreException;
31  import java.security.NoSuchAlgorithmException;
32  import java.security.NoSuchProviderException;
33  import java.security.PrivateKey;
34  import java.security.UnrecoverableKeyException;
35  import java.security.cert.CertStore;
36  import java.security.cert.CertStoreException;
37  import java.security.cert.CertificateException;
38  import java.security.cert.CollectionCertStoreParameters;
39  import java.security.cert.X509Certificate;
40  import java.util.ArrayList;
41  import java.util.Enumeration;
42  
43  import javax.mail.internet.MimeBodyPart;
44  import javax.mail.internet.MimeMessage;
45  import javax.mail.internet.MimeMultipart;
46  
47  import org.bouncycastle.mail.smime.SMIMEException;
48  import org.bouncycastle.mail.smime.SMIMESignedGenerator;
49  
50  /**
51   * <p>Loads a {@link java.security.KeyStore} in memory and keeps it ready for the
52   * cryptographic activity.</p>
53   * <p>It has the role of being a simpler intermediate to the crypto libraries.
54   * Uses specifically the <a href="http://www.bouncycastle.org/">Legion of the Bouncy Castle</a>
55   * libraries, particularly for the SMIME activity.</p>
56   * @version CVS $Revision: 721862 $ $Date: 2008-11-30 17:38:25 +0000 (Sun, 30 Nov 2008) $
57   * @since 3.0
58   */
59  public class SMIMEKeyHolder implements KeyHolder{
60      
61      /**
62       * Returns the default keystore type as specified in the Java security properties file,
63       * or the string "jks" (acronym for "Java keystore") if no such property exists.
64       * @return The defaultType, issuing a <CODE>KeyStore.getDefaultType()</CODE>.
65       */
66      public static String getDefaultType() {
67          return KeyStore.getDefaultType();
68      }
69      
70      /**
71       * Holds value of property privateKey.
72       */
73      private PrivateKey privateKey;
74      
75      /**
76       * Holds value of property certificate.
77       */
78      private X509Certificate certificate;
79      
80      /**
81       * Holds value of property certStore.
82       */
83      private CertStore certStore;
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 SMIMEKeyHolder(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 }