001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.security;
019:
020: import java.io.BufferedInputStream;
021: import java.io.FileInputStream;
022: import java.io.FileNotFoundException;
023: import java.io.IOException;
024: import java.security.InvalidAlgorithmParameterException;
025: import java.security.KeyStore;
026: import java.security.KeyStoreException;
027: import java.security.NoSuchAlgorithmException;
028: import java.security.NoSuchProviderException;
029: import java.security.PrivateKey;
030: import java.security.UnrecoverableKeyException;
031: import java.security.cert.CertStore;
032: import java.security.cert.CertStoreException;
033: import java.security.cert.CertificateException;
034: import java.security.cert.CollectionCertStoreParameters;
035: import java.security.cert.X509Certificate;
036: import java.util.ArrayList;
037: import java.util.Enumeration;
038:
039: import javax.mail.internet.MimeBodyPart;
040: import javax.mail.internet.MimeMessage;
041: import javax.mail.internet.MimeMultipart;
042:
043: import org.bouncycastle.mail.smime.SMIMEException;
044: import org.bouncycastle.mail.smime.SMIMESignedGenerator;
045:
046: /**
047: * <p>Loads a {@link java.security.KeyStore} in memory and keeps it ready for the
048: * cryptographic activity.</p>
049: * <p>It has the role of being a simpler intermediate to the crypto libraries.
050: * Uses specifically the <a href="http://www.bouncycastle.org/">Legion of the Bouncy Castle</a>
051: * libraries, particularly for the SMIME activity.</p>
052: * @version CVS $Revision: 494012 $ $Date: 2007-01-08 11:23:58 +0100 (Mo, 08 Jan 2007) $
053: * @since 2.2.1
054: */
055: public class KeyHolder {
056:
057: /**
058: * Returns the default keystore type as specified in the Java security properties file,
059: * or the string "jks" (acronym for "Java keystore") if no such property exists.
060: * @return The defaultType, issuing a <CODE>KeyStore.getDefaultType()</CODE>.
061: */
062: public static String getDefaultType() {
063: return KeyStore.getDefaultType();
064: }
065:
066: /**
067: * Holds value of property privateKey.
068: */
069: private PrivateKey privateKey;
070:
071: /**
072: * Holds value of property certificate.
073: */
074: private X509Certificate certificate;
075:
076: /**
077: * Holds value of property certStore.
078: */
079: private CertStore certStore;
080:
081: /** Creates a new instance of KeyHolder */
082: private KeyHolder() {
083: }
084:
085: /**
086: * Creates a new instance of <CODE>KeyHolder</CODE> using {@link java.security.KeyStore} related parameters.
087: * @param keyStoreFileName The (absolute) file name of the .keystore file to load the keystore from.
088: * @param keyStorePassword The (optional) password used to check the integrity of the keystore.
089: * If given, it is used to check the integrity of the keystore data,
090: * otherwise, if null, the integrity of the keystore is not checked.
091: * @param keyAlias The alias name of the key.
092: * If missing (is null) and if there is only one key in the keystore, will default to it.
093: * @param keyAliasPassword The password of the alias for recovering the key.
094: * If missing (is null) will default to <I>keyStorePassword</I>. At least one of the passwords must be provided.
095: * @param keyStoreType The type of keystore.
096: * If missing (is null) will default to the keystore type as specified in the Java security properties file,
097: * or the string "jks" (acronym for "Java keystore") if no such property exists.
098: * @throws java.security.KeyStoreException Thrown when the <I>keyAlias</I> is specified and not found,
099: * 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,
107: String keyAlias, String keyAliasPassword,
108: String keyStoreType) throws KeyStoreException,
109: FileNotFoundException, IOException,
110: NoSuchAlgorithmException,
111: InvalidAlgorithmParameterException, CertificateException,
112: UnrecoverableKeyException, NoSuchProviderException {
113:
114: try {
115: InitJCE.init();
116: } catch (InstantiationException e) {
117: NoSuchProviderException ex = new NoSuchProviderException(
118: "Error during cryptography provider initialization. Has bcprov-jdkxx-yyy.jar been copied in the lib directory or installed in the system?");
119: ex.initCause(e);
120: throw ex;
121: } catch (IllegalAccessException e) {
122: NoSuchProviderException ex = new NoSuchProviderException(
123: "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: } catch (ClassNotFoundException e) {
127: NoSuchProviderException ex = new NoSuchProviderException(
128: "Error during cryptography provider initialization. Has bcprov-jdkxx-yyy.jar been copied in the lib directory or installed in the system?");
129: ex.initCause(e);
130: throw ex;
131: }
132:
133: if (keyStoreType == null) {
134: keyStoreType = KeyStore.getDefaultType();
135: }
136:
137: KeyStore keyStore = KeyStore.getInstance(keyStoreType);
138: keyStore.load(new BufferedInputStream(new FileInputStream(
139: keyStoreFileName)), keyStorePassword.toCharArray());
140:
141: Enumeration aliases = keyStore.aliases();
142: if (keyAlias == null) {
143: if (aliases.hasMoreElements()) {
144: keyAlias = (String) aliases.nextElement();
145: } else {
146: throw new KeyStoreException(
147: "No alias was found in keystore.");
148: }
149: if (aliases.hasMoreElements()) {
150: throw new KeyStoreException(
151: "No <keyAlias> was given and more than one alias was found in keystore.");
152:
153: }
154: }
155:
156: if (keyAliasPassword == null) {
157: keyAliasPassword = keyStorePassword;
158: }
159:
160: this .privateKey = (PrivateKey) keyStore.getKey(keyAlias,
161: keyAliasPassword.toCharArray());
162: if (this .privateKey == null) {
163: throw new KeyStoreException("The \"" + keyAlias
164: + "\" PrivateKey alias was not found in keystore.");
165: }
166:
167: this .certificate = (X509Certificate) keyStore
168: .getCertificate(keyAlias);
169: if (this .certificate == null) {
170: throw new KeyStoreException(
171: "The \""
172: + keyAlias
173: + "\" X509Certificate alias was not found in keystore.");
174: }
175: java.security.cert.Certificate[] certificateChain = keyStore
176: .getCertificateChain(keyAlias);
177: ArrayList certList = new ArrayList();
178: if (certificateChain == null) {
179: certList.add(this .certificate);
180: } else {
181: for (int i = 0; i < certificateChain.length; i++) {
182: certList.add(certificateChain[i]);
183: }
184: }
185:
186: // create a CertStore containing the certificates we want carried
187: // in the signature
188: this .certStore = CertStore.getInstance("Collection",
189: new CollectionCertStoreParameters(certList), "BC");
190:
191: }
192:
193: /**
194: * Getter for property privateKey.
195: * @return Value of property privateKey.
196: */
197: public PrivateKey getPrivateKey() {
198: return this .privateKey;
199: }
200:
201: /**
202: * Getter for property certificate.
203: * @return Value of property certificate.
204: */
205: public X509Certificate getCertificate() {
206: return this .certificate;
207: }
208:
209: /**
210: * Getter for property certStore.
211: * @return Value of property certStore.
212: */
213: public CertStore getCertStore() {
214: return this .certStore;
215: }
216:
217: /**
218: * Creates an <CODE>SMIMESignedGenerator</CODE>. Includes a signer private key and certificate,
219: * and a pool of certs and cerls (if any) to go with the signature.
220: * @return The generated SMIMESignedGenerator.
221: */
222: public SMIMESignedGenerator createGenerator()
223: throws CertStoreException, SMIMEException {
224:
225: // create the generator for creating an smime/signed message
226: SMIMESignedGenerator generator = new SMIMESignedGenerator();
227:
228: // add a signer to the generator - this specifies we are using SHA1
229: // the encryption algorithm used is taken from the key
230: generator.addSigner(this .privateKey, this .certificate,
231: SMIMESignedGenerator.DIGEST_SHA1);
232:
233: // add our pool of certs and cerls (if any) to go with the signature
234: generator.addCertificatesAndCRLs(this .certStore);
235:
236: return generator;
237:
238: }
239:
240: /**
241: * Generates a signed MimeMultipart from a MimeMessage.
242: * @param message The message to sign.
243: * @return The signed <CODE>MimeMultipart</CODE>.
244: */
245: public MimeMultipart generate(MimeMessage message)
246: throws CertStoreException, NoSuchAlgorithmException,
247: NoSuchProviderException, SMIMEException {
248:
249: // create the generator for creating an smime/signed MimeMultipart
250: SMIMESignedGenerator generator = createGenerator();
251:
252: // do it
253: return generator.generate(message, "BC");
254:
255: }
256:
257: /**
258: * Generates a signed MimeMultipart from a MimeBodyPart.
259: * @param content The content to sign.
260: * @return The signed <CODE>MimeMultipart</CODE>.
261: */
262: public MimeMultipart generate(MimeBodyPart content)
263: throws CertStoreException, NoSuchAlgorithmException,
264: NoSuchProviderException, SMIMEException {
265:
266: // create the generator for creating an smime/signed MimeMultipart
267: SMIMESignedGenerator generator = createGenerator();
268:
269: // do it
270: return generator.generate(content, "BC");
271:
272: }
273:
274: /**
275: * Extracts the signer <I>distinguished name</I> (DN) from an <CODE>X509Certificate</CODE>.
276: * @param certificate The certificate to extract the information from.
277: * @return The requested information.
278: */
279: public static String getSignerDistinguishedName(
280: X509Certificate certificate) {
281:
282: return certificate.getSubjectDN().toString();
283:
284: }
285:
286: /**
287: * Extracts the signer <I>common name</I> (CN=) from an <CODE>X509Certificate</CODE> <I>distinguished name</I>.
288: * @param certificate The certificate to extract the information from.
289: * @return The requested information.
290: * @see getSignerDistinguishedName(X509Certificate)
291: */
292: public static String getSignerCN(X509Certificate certificate) {
293:
294: return extractAttribute(certificate.getSubjectDN().toString(),
295: "CN=");
296:
297: }
298:
299: /**
300: * Extracts the signer <I>email address</I> (EMAILADDRESS=) from an <CODE>X509Certificate</CODE> <I>distinguished name</I>.
301: * @param certificate The certificate to extract the information from.
302: * @return The requested information.
303: * @see getSignerDistinguishedName(X509Certificate)
304: */
305: public static String getSignerAddress(X509Certificate certificate) {
306:
307: return extractAttribute(certificate.getSubjectDN().toString(),
308: "EMAILADDRESS=");
309:
310: }
311:
312: /**
313: * Getter for property signerDistinguishedName.
314: * @return Value of property signerDistinguishedName.
315: * @see getSignerDistinguishedName(X509Certificate)
316: */
317: public String getSignerDistinguishedName() {
318: return getSignerDistinguishedName(getCertificate());
319: }
320:
321: /**
322: * Getter for property signerCN.
323: * @return Value of property signerCN.
324: * @see getSignerCN(X509Certificate)
325: */
326: public String getSignerCN() {
327: return getSignerCN(getCertificate());
328: }
329:
330: /**
331: * Getter for property signerAddress.
332: * @return Value of property signerMailAddress.
333: * @see getSignerAddress(X509Certificate)
334: */
335: public String getSignerAddress() {
336: return getSignerAddress(getCertificate());
337: }
338:
339: private static String extractAttribute(String DistinguishedName,
340: String attributeName) {
341:
342: int i = DistinguishedName.indexOf(attributeName);
343:
344: if (i < 0) {
345: return null;
346: }
347:
348: i += attributeName.length();
349: int j = DistinguishedName.indexOf(",", i);
350:
351: if (j - 1 <= 0) {
352: return null;
353: }
354:
355: return DistinguishedName.substring(i, j).trim();
356:
357: }
358:
359: }
|