001: /*************************************************************************
002: * *
003: * EJBCA: The OpenSource Certificate Authority *
004: * *
005: * This software is free software; you can redistribute it and/or *
006: * modify it under the terms of the GNU Lesser General Public *
007: * License as published by the Free Software Foundation; either *
008: * version 2.1 of the License, or any later version. *
009: * *
010: * See terms of license at gnu.org. *
011: * *
012: *************************************************************************/package org.ejbca.util;
013:
014: import java.io.ByteArrayInputStream;
015: import java.io.ByteArrayOutputStream;
016: import java.io.File;
017: import java.io.FileInputStream;
018: import java.io.IOException;
019: import java.io.InputStream;
020: import java.io.PrintWriter;
021: import java.lang.reflect.Constructor;
022: import java.security.AuthProvider;
023: import java.security.InvalidAlgorithmParameterException;
024: import java.security.KeyFactory;
025: import java.security.KeyPair;
026: import java.security.KeyPairGenerator;
027: import java.security.KeyStore;
028: import java.security.KeyStoreException;
029: import java.security.NoSuchAlgorithmException;
030: import java.security.NoSuchProviderException;
031: import java.security.PrivateKey;
032: import java.security.PublicKey;
033: import java.security.SecureRandom;
034: import java.security.cert.Certificate;
035: import java.security.cert.CertificateException;
036: import java.security.cert.CertificateFactory;
037: import java.security.cert.X509Certificate;
038: import java.security.interfaces.ECPublicKey;
039: import java.security.interfaces.RSAPublicKey;
040: import java.security.spec.InvalidKeySpecException;
041: import java.security.spec.PKCS8EncodedKeySpec;
042: import java.util.ArrayList;
043: import java.util.Collection;
044:
045: import javax.crypto.Cipher;
046:
047: import org.apache.commons.lang.StringUtils;
048: import org.apache.log4j.Logger;
049: import org.bouncycastle.asn1.ASN1InputStream;
050: import org.bouncycastle.asn1.ASN1Sequence;
051: import org.bouncycastle.asn1.DERBMPString;
052: import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
053: import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
054: import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
055: import org.bouncycastle.jce.ECNamedCurveTable;
056: import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
057: import org.bouncycastle.jce.provider.JCEECPublicKey;
058: import org.ejbca.core.model.ca.catoken.CATokenConstants;
059:
060: /**
061: * Tools to handle common key and keystore operations.
062: *
063: * @version $Id: KeyTools.java,v 1.18 2008/03/10 12:32:14 anatom Exp $
064: */
065: public class KeyTools {
066: private static Logger log = Logger.getLogger(KeyTools.class);
067:
068: /** The name of Suns pkcs11 implementation */
069: public static final String SUNPKCS11CLASS = "sun.security.pkcs11.SunPKCS11";
070:
071: /**
072: * Prevent from creating new KeyTools object
073: */
074: private KeyTools() {
075: }
076:
077: /**
078: * Generates a keypair
079: *
080: * @param keyspec specification of keys to generate, typical value is 1024 for RSA keys or prime192v1 for ECDSA keys
081: * @param keyalg algorithm of keys to generate, typical value is RSA or ECDSA, see org.ejbca.core.model.ca.catoken.CATokenConstants.KEYALGORITHM_XX
082: *
083: * @see org.ejbca.core.model.ca.catoken.CATokenConstants
084: * @see org.bouncycastle.asn1.x9.X962NamedCurves
085: * @see org.bouncycastle.asn1.nist.NISTNamedCurves
086: * @see org.bouncycastle.asn1.sec.SECNamedCurves
087: *
088: * @return KeyPair the generated keypair
089: * @throws InvalidAlgorithmParameterException
090: */
091: public static KeyPair genKeys(String keySpec, String keyAlg)
092: throws NoSuchAlgorithmException, NoSuchProviderException,
093: InvalidAlgorithmParameterException {
094: if (log.isDebugEnabled()) {
095: log.debug(">genKeys(" + keySpec + ", " + keyAlg + ")");
096: }
097:
098: KeyPairGenerator keygen = KeyPairGenerator.getInstance(keyAlg,
099: "BC");
100: if (StringUtils.equals(keyAlg,
101: CATokenConstants.KEYALGORITHM_ECDSA)) {
102: org.bouncycastle.jce.spec.ECParameterSpec ecSpec = null;
103: if ((keySpec == null)
104: || StringUtils.equals(keySpec, "implicitlyCA")) {
105: log
106: .debug("Generating implicitlyCA encoded ECDSA key pair");
107: // If the keySpec is null, we have "implicitlyCA" defined EC parameters
108: // The parameters were already installed when we installed the provider
109: // We just make sure that ecSpec == null here
110: } else {
111: log.debug("Generating named curve ECDSA key pair");
112: // We have EC keys
113: ecSpec = ECNamedCurveTable.getParameterSpec(keySpec);
114: }
115: keygen.initialize(ecSpec, new SecureRandom());
116: } else {
117: // RSA keys
118: int keysize = Integer.parseInt(keySpec);
119: keygen.initialize(keysize);
120: }
121:
122: KeyPair keys = keygen.generateKeyPair();
123:
124: if (log.isDebugEnabled()) {
125: PublicKey pk = keys.getPublic();
126: int len = getKeyLength(pk);
127: log.debug("Generated " + keys.getPublic().getAlgorithm()
128: + " keys with length " + len);
129: log.debug("<genKeys()");
130: }
131:
132: return keys;
133: } // genKeys
134:
135: /**
136: * Gets the key length of supported keys
137: * @param priv PrivateKey to check
138: * @return -1 if key is unsupported, otherwise a number >= 0. 0 usually means the length can not be calculated,
139: * for example if the key is en EC key and the "implicitlyCA" encoding is used.
140: */
141: public static int getKeyLength(PublicKey pk) {
142: int len = -1;
143: if (pk instanceof RSAPublicKey) {
144: RSAPublicKey rsapub = (RSAPublicKey) pk;
145: len = rsapub.getModulus().bitLength();
146: } else if (pk instanceof JCEECPublicKey) {
147: JCEECPublicKey ecpriv = (JCEECPublicKey) pk;
148: org.bouncycastle.jce.spec.ECParameterSpec spec = ecpriv
149: .getParameters();
150: if (spec != null) {
151: len = spec.getN().bitLength();
152: } else {
153: // We support the key, but we don't know the key length
154: len = 0;
155: }
156: } else if (pk instanceof ECPublicKey) {
157: ECPublicKey ecpriv = (ECPublicKey) pk;
158: java.security.spec.ECParameterSpec spec = ecpriv
159: .getParams();
160: if (spec != null) {
161: len = spec.getOrder().bitLength(); // does this really return something we expect?
162: } else {
163: // We support the key, but we don't know the key length
164: len = 0;
165: }
166: }
167: return len;
168: }
169:
170: /**
171: * Creates PKCS12-file that can be imported in IE or Netscape. The alias for the private key is
172: * set to 'privateKey' and the private key password is null.
173: *
174: * @param alias the alias used for the key entry
175: * @param privKey RSA private key
176: * @param cert user certificate
177: * @param cacert CA-certificate or null if only one cert in chain, in that case use 'cert'.
178: *
179: * @return KeyStore containing PKCS12-keystore
180: *
181: * @exception Exception if input parameters are not OK or certificate generation fails
182: */
183: public static KeyStore createP12(String alias, PrivateKey privKey,
184: X509Certificate cert, X509Certificate cacert)
185: throws IOException, KeyStoreException,
186: CertificateException, NoSuchProviderException,
187: NoSuchAlgorithmException, InvalidKeySpecException {
188: Certificate[] chain;
189:
190: if (cacert == null) {
191: chain = null;
192: } else {
193: chain = new Certificate[1];
194: chain[0] = cacert;
195: }
196:
197: return createP12(alias, privKey, cert, chain);
198: } // createP12
199:
200: /**
201: * Creates PKCS12-file that can be imported in IE or Netscape.
202: * The alias for the private key is set to 'privateKey' and the private key password is null.
203: * @param alias the alias used for the key entry
204: * @param privKey RSA private key
205: * @param cert user certificate
206: * @param cacert Collection of X509Certificate, or null if only one cert in chain, in that case use 'cert'.
207: * @param username user's username
208: * @param password user's password
209: * @return KeyStore containing PKCS12-keystore
210: * @exception Exception if input parameters are not OK or certificate generation fails
211: */
212: public static KeyStore createP12(String alias, PrivateKey privKey,
213: X509Certificate cert, Collection cacerts)
214: throws IOException, KeyStoreException,
215: CertificateException, NoSuchProviderException,
216: NoSuchAlgorithmException, InvalidKeySpecException {
217: Certificate[] chain;
218: if (cacerts == null)
219: chain = null;
220: else {
221: chain = new Certificate[cacerts.size()];
222: chain = (Certificate[]) cacerts.toArray(chain);
223: }
224: return createP12(alias, privKey, cert, chain);
225: } // createP12
226:
227: /**
228: * Creates PKCS12-file that can be imported in IE or Netscape. The alias for the private key is
229: * set to 'privateKey' and the private key password is null.
230: *
231: * @param alias the alias used for the key entry
232: * @param privKey RSA private key
233: * @param cert user certificate
234: * @param cachain CA-certificate chain or null if only one cert in chain, in that case use 'cert'.
235: * @return KeyStore containing PKCS12-keystore
236: * @exception Exception if input parameters are not OK or certificate generation fails
237: */
238: public static KeyStore createP12(String alias, PrivateKey privKey,
239: X509Certificate cert, Certificate[] cachain)
240: throws IOException, KeyStoreException,
241: CertificateException, NoSuchProviderException,
242: NoSuchAlgorithmException, InvalidKeySpecException {
243: log.debug(">createP12: alias=" + alias + ", privKey, cert="
244: + CertTools.getSubjectDN(cert) + ", cachain.length="
245: + ((cachain == null) ? 0 : cachain.length));
246:
247: // Certificate chain
248: if (cert == null) {
249: throw new IllegalArgumentException(
250: "Parameter cert cannot be null.");
251: }
252: int len = 1;
253: if (cachain != null) {
254: len += cachain.length;
255: }
256: Certificate[] chain = new Certificate[len];
257: // To not get a ClassCastException we need to genereate a real new certificate with BC
258: CertificateFactory cf = CertTools.getCertificateFactory();
259: chain[0] = cf.generateCertificate(new ByteArrayInputStream(cert
260: .getEncoded()));
261:
262: if (cachain != null) {
263: for (int i = 0; i < cachain.length; i++) {
264: X509Certificate tmpcert = (X509Certificate) cf
265: .generateCertificate(new ByteArrayInputStream(
266: cachain[i].getEncoded()));
267: chain[i + 1] = tmpcert;
268: }
269: }
270: if (chain.length > 1) {
271: for (int i = 1; i < chain.length; i++) {
272: X509Certificate cacert = (X509Certificate) cf
273: .generateCertificate(new ByteArrayInputStream(
274: chain[i].getEncoded()));
275: // Set attributes on CA-cert
276: try {
277: PKCS12BagAttributeCarrier caBagAttr = (PKCS12BagAttributeCarrier) chain[i];
278: // We construct a friendly name for the CA, and try with some parts from the DN if they exist.
279: String cafriendly = CertTools.getPartFromDN(
280: CertTools.getSubjectDN(cacert), "CN");
281: // On the ones below we +i to make it unique, O might not be otherwise
282: if (cafriendly == null) {
283: cafriendly = CertTools.getPartFromDN(CertTools
284: .getSubjectDN(cacert), "O")
285: + i;
286: }
287: if (cafriendly == null) {
288: cafriendly = CertTools.getPartFromDN(CertTools
289: .getSubjectDN(cacert), "OU" + i);
290: }
291: if (cafriendly == null) {
292: cafriendly = "CA_unknown" + i;
293: }
294: caBagAttr
295: .setBagAttribute(
296: PKCSObjectIdentifiers.pkcs_9_at_friendlyName,
297: new DERBMPString(cafriendly));
298: } catch (ClassCastException e) {
299: log
300: .error(
301: "ClassCastException setting BagAttributes, can not set friendly name: ",
302: e);
303: }
304: }
305: }
306:
307: // Set attributes on user-cert
308: try {
309: PKCS12BagAttributeCarrier certBagAttr = (PKCS12BagAttributeCarrier) chain[0];
310: certBagAttr.setBagAttribute(
311: PKCSObjectIdentifiers.pkcs_9_at_friendlyName,
312: new DERBMPString(alias));
313: // in this case we just set the local key id to that of the public key
314: certBagAttr.setBagAttribute(
315: PKCSObjectIdentifiers.pkcs_9_at_localKeyId,
316: createSubjectKeyId(chain[0].getPublicKey()));
317: } catch (ClassCastException e) {
318: log
319: .error(
320: "ClassCastException setting BagAttributes, can not set friendly name: ",
321: e);
322: }
323: // "Clean" private key, i.e. remove any old attributes
324: KeyFactory keyfact = KeyFactory.getInstance(privKey
325: .getAlgorithm(), "BC");
326: PrivateKey pk = keyfact
327: .generatePrivate(new PKCS8EncodedKeySpec(privKey
328: .getEncoded()));
329: // Set attributes for private key
330: try {
331: PKCS12BagAttributeCarrier keyBagAttr = (PKCS12BagAttributeCarrier) pk;
332: // in this case we just set the local key id to that of the public key
333: keyBagAttr.setBagAttribute(
334: PKCSObjectIdentifiers.pkcs_9_at_friendlyName,
335: new DERBMPString(alias));
336: keyBagAttr.setBagAttribute(
337: PKCSObjectIdentifiers.pkcs_9_at_localKeyId,
338: createSubjectKeyId(chain[0].getPublicKey()));
339: } catch (ClassCastException e) {
340: log
341: .error(
342: "ClassCastException setting BagAttributes, can not set friendly name: ",
343: e);
344: }
345: // store the key and the certificate chain
346: KeyStore store = KeyStore.getInstance("PKCS12", "BC");
347: store.load(null, null);
348: store.setKeyEntry(alias, pk, null, chain);
349: log.debug("<createP12: alias=" + alias + ", privKey, cert="
350: + CertTools.getSubjectDN(cert) + ", cachain.length="
351: + ((cachain == null) ? 0 : cachain.length));
352:
353: return store;
354: } // createP12
355:
356: /**
357: * Creates JKS-file that can be used with JDK. The alias for the private key is set to
358: * 'privateKey' and the private key password is null.
359: *
360: * @param alias the alias used for the key entry
361: * @param privKey RSA private key
362: * @param password user's password
363: * @param cert user certificate
364: * @param cachain CA-certificate chain or null if only one cert in chain, in that case use
365: * 'cert'.
366: *
367: * @return KeyStore containing JKS-keystore
368: *
369: * @exception Exception if input parameters are not OK or certificate generation fails
370: */
371: public static KeyStore createJKS(String alias, PrivateKey privKey,
372: String password, X509Certificate cert, Certificate[] cachain)
373: throws Exception {
374: log.debug(">createJKS: alias=" + alias + ", privKey, cert="
375: + CertTools.getSubjectDN(cert) + ", cachain.length="
376: + ((cachain == null) ? 0 : cachain.length));
377:
378: String caAlias = "cacert";
379:
380: // Certificate chain
381: if (cert == null) {
382: throw new IllegalArgumentException(
383: "Parameter cert cannot be null.");
384: }
385: int len = 1;
386: if (cachain != null) {
387: len += cachain.length;
388: }
389: Certificate[] chain = new Certificate[len];
390: chain[0] = cert;
391: if (cachain != null) {
392: for (int i = 0; i < cachain.length; i++) {
393: chain[i + 1] = cachain[i];
394: }
395: }
396:
397: // store the key and the certificate chain
398: KeyStore store = KeyStore.getInstance("JKS");
399: store.load(null, null);
400:
401: // First load the key entry
402: X509Certificate[] usercert = new X509Certificate[1];
403: usercert[0] = cert;
404: store.setKeyEntry(alias, privKey, password.toCharArray(),
405: usercert);
406:
407: // Add the root cert as trusted
408: if (cachain != null) {
409: if (!CertTools
410: .isSelfSigned((X509Certificate) cachain[cachain.length - 1])) {
411: throw new IllegalArgumentException(
412: "Root cert is not self-signed.");
413: }
414: store.setCertificateEntry(caAlias,
415: cachain[cachain.length - 1]);
416: }
417:
418: // Set the complete chain
419: log.debug("Storing cert chain of length " + chain.length);
420: store
421: .setKeyEntry(alias, privKey, password.toCharArray(),
422: chain);
423: log.debug("<createJKS: alias=" + alias + ", privKey, cert="
424: + CertTools.getSubjectDN(cert) + ", cachain.length="
425: + ((cachain == null) ? 0 : cachain.length));
426:
427: return store;
428: } // createJKS
429:
430: /**
431: * Retrieves the certificate chain from a keystore.
432: *
433: * @param keyStore the keystore, which has been loaded and opened.
434: * @param privateKeyAlias the alias of the privatekey for which the certchain belongs.
435: *
436: * @return array of Certificate, or null if no certificates are found.
437: */
438: public static Certificate[] getCertChain(KeyStore keyStore,
439: String privateKeyAlias) throws KeyStoreException {
440: log.debug(">getCertChain: alias='" + privateKeyAlias + "'");
441:
442: Certificate[] certchain = keyStore
443: .getCertificateChain(privateKeyAlias);
444: if (certchain == null) {
445: return null;
446: }
447: log.debug("Certchain retrieved from alias '" + privateKeyAlias
448: + "' has length " + certchain.length);
449:
450: if (certchain.length < 1) {
451: log.error("Cannot load certificate chain with alias '"
452: + privateKeyAlias + "' from keystore.");
453: log.debug("<getCertChain: alias='" + privateKeyAlias
454: + "', retlength=" + certchain.length);
455:
456: return certchain;
457: } else if (certchain.length > 0) {
458: if (CertTools
459: .isSelfSigned((X509Certificate) certchain[certchain.length - 1])) {
460: log
461: .debug("Issuer='"
462: + CertTools
463: .getIssuerDN((X509Certificate) certchain[certchain.length - 1])
464: + "'.");
465: log
466: .debug("Subject='"
467: + CertTools
468: .getSubjectDN((X509Certificate) certchain[certchain.length - 1])
469: + "'.");
470: log.debug("<getCertChain: alias='" + privateKeyAlias
471: + "', retlength=" + certchain.length);
472:
473: return certchain;
474: }
475: }
476:
477: // If we came here, we have a cert which is not root cert in 'cert'
478: ArrayList array = new ArrayList();
479:
480: for (int i = 0; i < certchain.length; i++) {
481: array.add(certchain[i]);
482: }
483:
484: boolean stop = false;
485:
486: while (!stop) {
487: X509Certificate cert = (X509Certificate) array.get(array
488: .size() - 1);
489: String ialias = CertTools.getPartFromDN(CertTools
490: .getIssuerDN(cert), "CN");
491: Certificate[] chain1 = keyStore.getCertificateChain(ialias);
492:
493: if (chain1 == null) {
494: stop = true;
495: } else {
496: log.debug("Loaded certificate chain with length "
497: + chain1.length + " with alias '" + ialias
498: + "'.");
499:
500: if (chain1.length == 0) {
501: log.error("No RootCA certificate found!");
502: stop = true;
503: }
504:
505: for (int j = 0; j < chain1.length; j++) {
506: array.add(chain1[j]);
507:
508: // If one cert is slefsigned, we have found a root certificate, we don't need to go on anymore
509: if (CertTools
510: .isSelfSigned((X509Certificate) chain1[j])) {
511: stop = true;
512: }
513: }
514: }
515: }
516:
517: Certificate[] ret = new Certificate[array.size()];
518:
519: for (int i = 0; i < ret.length; i++) {
520: ret[i] = (X509Certificate) array.get(i);
521: log.debug("Issuer='"
522: + CertTools.getIssuerDN((X509Certificate) ret[i])
523: + "'.");
524: log.debug("Subject='"
525: + CertTools.getSubjectDN((X509Certificate) ret[i])
526: + "'.");
527: }
528:
529: log.debug("<getCertChain: alias='" + privateKeyAlias
530: + "', retlength=" + ret.length);
531:
532: return ret;
533: } // getCertChain
534:
535: /**
536: * create the subject key identifier.
537: *
538: * @param pubKey the public key
539: *
540: * @return SubjectKeyIdentifer asn.1 structure
541: */
542: public static SubjectKeyIdentifier createSubjectKeyId(
543: PublicKey pubKey) {
544: try {
545: ByteArrayInputStream bIn = new ByteArrayInputStream(pubKey
546: .getEncoded());
547: SubjectPublicKeyInfo info = new SubjectPublicKeyInfo(
548: (ASN1Sequence) new ASN1InputStream(bIn)
549: .readObject());
550:
551: return new SubjectKeyIdentifier(info);
552: } catch (Exception e) {
553: throw new RuntimeException("error creating key");
554: }
555: } // createSubjectKeyId
556:
557: /** Creates the SUN PKCS#11 provider using the passed in pkcs11 library.
558: *
559: * @param slot pkcs11 slot number or config file name if libName is null
560: * @param fileName the manufacturers provided pkcs11 library (.dll or .so) or config file name if slot is null
561: * @return AuthProvider of type "sun.security.pkcs11.SunPKCS11"
562: * @throws IOException if the pkcs11 library can not be found, or the SunPKCS11 can not be created.
563: */
564: public static AuthProvider getP11AuthProvider(final String slot,
565: final String fileName, final boolean isIndex)
566: throws IOException {
567: if (StringUtils.isEmpty(fileName)) {
568: throw new IOException("A file name must be supplied.");
569: }
570: final File libFile = new File(fileName);
571: if (!libFile.isFile() || !libFile.canRead()) {
572: throw new IOException("The file " + fileName
573: + " can't be read.");
574: }
575: if (slot == null)
576: return getP11AuthProvider(new FileInputStream(fileName));
577: ByteArrayOutputStream baos = new ByteArrayOutputStream();
578: PrintWriter pw = new PrintWriter(baos);
579: pw.println("name = " + libFile.getName() + "-slot" + slot);
580: pw.println("library = " + libFile.getCanonicalPath());
581: final int slotNr;
582: try {
583: if (slot.length() > 0)
584: slotNr = Integer.parseInt(slot);
585: else
586: slotNr = -1;
587: } catch (NumberFormatException e) {
588: throw new IOException("Slot nr " + slot
589: + " not an integer.");
590: }
591: if (slotNr >= 0) {
592: pw.println("slot" + (isIndex ? "ListIndex" : "") + " = "
593: + slot);
594: }
595: pw.flush();
596: pw.close();
597: if (log.isDebugEnabled()) {
598: log.debug(baos.toString());
599: }
600: return getP11AuthProvider(new ByteArrayInputStream(baos
601: .toByteArray()));
602: }
603:
604: private static AuthProvider getP11AuthProvider(final InputStream is)
605: throws IOException {
606:
607: // We will construct the PKCS11 provider (sun.security...) using reflextion, because
608: // the sun class does not exist on all platforms in jdk5, and we want to be able to compile everything.
609: // The below code replaces the single line:
610: // return new SunPKCS11(new ByteArrayInputStream(baos.toByteArray()));
611: try {
612: final Class implClass = Class.forName(SUNPKCS11CLASS);
613: final Constructor construct = implClass
614: .getConstructor(InputStream.class);
615: return (AuthProvider) construct
616: .newInstance(new Object[] { is });
617: } catch (Exception e) {
618: log.error("Error constructing pkcs11 provider: ", e);
619: IOException ioe = new IOException(
620: "Error constructing pkcs11 provider: "
621: + e.getMessage());
622: ioe.initCause(e);
623: throw ioe;
624: }
625: }
626:
627: /**
628: * Detect if "Unlimited Strength" Policy files hase bean properly installed.
629: *
630: * @return true if key strength is limited
631: */
632: public static boolean isUsingExportableCryptography() {
633: boolean returnValue = true;
634: try {
635: int keylen = Cipher.getMaxAllowedKeyLength("DES");
636: log.debug("MaxAllowedKeyLength for DES is: " + keylen);
637: if (keylen == Integer.MAX_VALUE) {
638: returnValue = false;
639: }
640: } catch (NoSuchAlgorithmException e) {
641: }
642: return returnValue;
643: }
644:
645: } // KeyTools
|