//$Header: /cvsroot-fuse/mec-as2/39/mendelson/util/security/KeyStoreUtil.java,v 1.1 2012/04/18 14:10:45 heller Exp $ package de.mendelson.util.security; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.security.GeneralSecurityException; import java.security.Key; import java.security.cert.Certificate; import java.security.KeyStore; import java.security.KeyStoreException; import java.security.Provider; import java.security.cert.CertPath; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.StringTokenizer; import java.util.Vector; import javax.security.auth.x500.X500Principal; import org.bouncycastle.openssl.PEMReader; /* * Copyright (C) mendelson-e-commerce GmbH Berlin Germany * * This software is subject to the license agreement set forth in the license. * Please read and agree to all terms before using this software. * Other product and brand names are trademarks of their respective owners. */ /** * Utility class to handle java keyStore issues * @author S.Heller * @version $Revision: 1.1 $ */ public class KeyStoreUtil { /**Saves the passed keystore *@param keystorePass Password for the keystore *@param filename Filename where to save the keystore to */ public void saveKeyStore(KeyStore keystore, char[] keystorePass, String filename) throws Exception { OutputStream out = null; try { out = new FileOutputStream(filename); keystore.store(out, keystorePass); } finally { if (out != null) { out.close(); } } } /**Loads a keystore and returns it. The passed keystore has to be created *first by the security provider, e.g. using the code *KeyStore.getInstance(<keystoretype>, <provider>); *If the passed filename does not exist a new, empty keystore will be created */ public void loadKeyStore(KeyStore keystoreInstance, String filename, char[] keystorePass) throws Exception { File inFile = new File(filename); FileInputStream inStream = null; try { if (inFile.exists()) { inStream = new FileInputStream(inFile); keystoreInstance.load(inStream, keystorePass); } else { keystoreInstance.load(null, null); } } finally { if (inStream != null) { inStream.close(); } } } /**Renames an entry in the keystore *@param keyStore Keystore to read the keys from *@param oldAlias Old alias to rename *@param newAlias New alias to rename *@param keyPassword Password of the key, not used for keystores of format *PKCS#12, for these types of keystores just pass null. * */ public void renameEntry(KeyStore keyStore, String oldAlias, String newAlias, char[] keyPassword) throws Exception { if (keyPassword == null) { keyPassword = "dummy".toCharArray(); } //copy operation if (keyStore.isKeyEntry(oldAlias)) { Key key = keyStore.getKey(oldAlias, keyPassword); Certificate[] certs = keyStore.getCertificateChain(oldAlias); keyStore.setKeyEntry(newAlias, key, keyPassword, certs); } else { Certificate cert = keyStore.getCertificate(oldAlias); keyStore.setCertificateEntry(newAlias, cert); } //delete operation keyStore.deleteEntry(oldAlias); } /**Imports a X509 certificate into the passed keystore using a special provider *e.g. for the use of BouncyCastle Provider use the code *Provider provBC = Security.getProvider("BC"); * *@param keystore Keystore to import the certificate to *@param certStream Stream to access the cert data from *@param alias Aslias to use in the keystore */ public void importX509Certificate(KeyStore keystore, InputStream certStream, String alias, Provider provider) throws Exception { X509Certificate cert = this.readCertificate(certStream, provider); keystore.setCertificateEntry(alias, cert); } /**Checks if the passed certificate is stored in the keystore and returns its alias. Returns * null if the cert is not in the keystore */ public String getCertificateAlias(KeyStore keystore, X509Certificate cert) throws Exception { Enumeration enumeration = keystore.aliases(); while (enumeration.hasMoreElements()) { String certAlias = (String) enumeration.nextElement(); X509Certificate checkCert = this.convertToX509Certificate(keystore.getCertificate(certAlias)); if (checkCert.getSerialNumber().equals(cert.getSerialNumber()) && checkCert.getNotAfter().equals(cert.getNotAfter()) && checkCert.getNotBefore().equals(cert.getNotBefore())) { return (certAlias); } } return (null); } /**Imports a X509 certificate into the passed keystore using a special provider *e.g. for the use of BouncyCastle Provider use the code *Provider provBC = Security.getProvider("BC"); * *@param keystore Keystore to import the certificate to *@param certStream Stream to access the cert data from *@param alias Aslias to use in the keystore */ public String importX509Certificate(KeyStore keystore, X509Certificate cert, Provider provider) throws Exception { //dont import the certificate if it already exists! if (this.getCertificateAlias(keystore, cert) != null) { return (this.getCertificateAlias(keystore, cert)); } String alias = this.getProposalCertificateAliasForImport(cert); alias = this.ensureUniqueAliasName(keystore, alias); keystore.setCertificateEntry(alias, cert); return (alias); } /**Checks that an alias for an import is unique in this keystore*/ public String ensureUniqueAliasName(KeyStore keystore, String alias) throws Exception { int counter = 1; String newAlias = alias; //add a number to the alias if it already exists with this name while (keystore.containsAlias(newAlias)) { newAlias = alias + counter; counter++; } alias = newAlias; return (alias); } /**Checks the principal of a certificate and returns the proposed alias name */ public String getProposalCertificateAliasForImport(X509Certificate cert) { X500Principal principal = cert.getSubjectX500Principal(); StringTokenizer tokenizer = new StringTokenizer(principal.getName(X500Principal.RFC2253), ","); while (tokenizer.hasMoreTokens()) { String token = tokenizer.nextToken().trim(); if (token.startsWith("CN=")) { return (token.substring(3)); } } //fallback: return a common name. Please check if this alias exists before importing the certificate return ("certificate"); } /**Reads a chain of certificates from the passed stream*/ public Collection<? extends Certificate> readCertificates(InputStream certStream, Provider provider) throws CertificateException { CertificateFactory factory; try { if (provider != null) { factory = CertificateFactory.getInstance("X.509", provider); return (factory.generateCertificates(certStream)); } //Let the default provider parsing the certificate else { factory = CertificateFactory.getInstance("X.509"); return (factory.generateCertificates(certStream)); } } catch (Exception e) { throw new CertificateException("Not a certificate or unsupported encoding."); } } /**Reads a certificate from a stream and returns it */ public X509Certificate readCertificate(InputStream certStream, Provider provider) throws CertificateException { CertificateFactory factory; X509Certificate cert = null; try { if (provider != null) { factory = CertificateFactory.getInstance("X.509", provider); cert = (X509Certificate) factory.generateCertificate(certStream); } //Let the default provider parsing the certificate if (provider == null || cert == null) { factory = CertificateFactory.getInstance("X.509"); cert = (X509Certificate) factory.generateCertificate(certStream); } //still no success, perhaps PEM encoding? Start the PEM reader and see if it could read the cert if (cert == null) { PEMReader pemReader = new PEMReader(new InputStreamReader(certStream)); cert = (X509Certificate) pemReader.readObject(); } } catch (Exception e) { throw new CertificateException("Not a certificate or unsupported encoding."); } if (cert != null) { return (cert); } else { throw new CertificateException("Not a certificate or unsupported encoding."); } } /**Imports a X509 certificate into the passed keystore using a special provider *e.g. for the use of BouncyCastle Provider use the code *Provider provBC = Security.getProvider("BC"); * *@param keystore Keystore to import the certificate to *@param certificateFilename filename to read the certificate from *@param alias Aslias to use in the keystore */ public void importX509Certificate(KeyStore keystore, String certificateFilename, String alias, Provider provider) throws Exception { InputStream inCert = new FileInputStream(certificateFilename); this.importX509Certificate(keystore, inCert, alias, provider); inCert.close(); } /**Imports a X509 certificate into the passed keystore *@param keystore Keystore to import the certificate to *@param certificateFilename filename to read the certificate from *@param alias Aslias to use in the keystore */ public void importX509Certificate(KeyStore keystore, String certificateFilename, String alias) throws Exception { InputStream inCert = new FileInputStream(certificateFilename); this.importX509Certificate(keystore, inCert, alias, null); inCert.close(); } /** * Attempt to order the supplied array of X.509 certificates in issued to * to issued from order. * @param certs The X.509 certificates to order * @return The ordered X.509 certificates */ public X509Certificate[] orderX509CertChain(X509Certificate[] certs) { int ordered = 0; X509Certificate[] tmpCerts = (X509Certificate[]) certs.clone(); X509Certificate[] orderedCerts = new X509Certificate[certs.length]; X509Certificate issuerCertificate = null; // Find the root issuer (ie certificate where issuer is the same // as subject) for (int i = 0; i < tmpCerts.length; i++) { X509Certificate singleCertificate = tmpCerts[i]; if (singleCertificate.getIssuerDN().equals(singleCertificate.getSubjectDN())) { issuerCertificate = singleCertificate; orderedCerts[ordered] = issuerCertificate; ordered++; } } // Couldn't find a root issuer so just return the un-ordered array if (issuerCertificate == null) { return certs; } // Keep making passes through the array of certificates looking for the // next certificate in the chain until the links run out while (true) { boolean foundNext = false; for (int i = 0; i < tmpCerts.length; i++) { X509Certificate singleCertificate = tmpCerts[i]; // Is this certificate the next in the chain? if (singleCertificate.getIssuerDN().equals(issuerCertificate.getSubjectDN()) && singleCertificate != issuerCertificate) { // Yes issuerCertificate = singleCertificate; orderedCerts[ordered] = issuerCertificate; ordered++; foundNext = true; break; } } if (!foundNext) { break; } } // Resize array tmpCerts = new X509Certificate[ordered]; System.arraycopy(orderedCerts, 0, tmpCerts, 0, ordered); // Reverse the order of the array orderedCerts = new X509Certificate[ordered]; for (int i = 0; i < ordered; i++) { orderedCerts[i] = tmpCerts[tmpCerts.length - 1 - i]; } return orderedCerts; } /**Exports an X.509 certificate from a passed keystore, encoding is PKCS7 *@returns the certificate */ public File[] exportX509CertificatePKCS7(KeyStore keystore, String alias, String baseFilename) throws Exception { byte[] certificate = this.exportX509Certificate(keystore, alias, "PKCS7"); File file = new File(baseFilename); if (certificate != null) { FileOutputStream outStream = new FileOutputStream(file); ByteArrayInputStream inStream = new ByteArrayInputStream(certificate); this.copyStreams(inStream, outStream); inStream.close(); outStream.flush(); outStream.close(); } return (new File[]{file}); } /**Converts a x.509 certificate to PEM format which is printable, *BASE64 encoded. */ public String convertX509CertificateToPEM(X509Certificate certificate) throws CertificateEncodingException { // Get Base 64 encoding of certificate String fullEncoded = Base64.encode(certificate.getEncoded()); // Certificate encodng is bounded by a header and footer String header = "-----BEGIN CERTIFICATE-----\n"; String footer = "-----END CERTIFICATE-----\n"; StringBuilder pemBuffer = new StringBuilder(); pemBuffer.append(header); pemBuffer.append(fullEncoded); pemBuffer.append(footer); return (pemBuffer.toString()); } /**Converts the passed certificate to an X509 certificate. Mainly it is already *in this format. */ public final X509Certificate convertToX509Certificate(Certificate certificate) throws CertificateException, IOException { CertificateFactory factory = CertificateFactory.getInstance("X.509"); ByteArrayInputStream inStream = new ByteArrayInputStream(certificate.getEncoded()); X509Certificate cert = (X509Certificate) factory.generateCertificate(inStream); inStream.close(); return (cert); } /**Converts an array x.509 certificate to pkcs#7 format*/ public byte[] convertX509CertificateToPKCS7(X509Certificate[] certificates) throws Exception { CertificateFactory factory = CertificateFactory.getInstance("X.509"); List<X509Certificate> certList = new ArrayList<X509Certificate>(); certList.addAll(Arrays.asList(certificates)); CertPath certPath = factory.generateCertPath(certList); return (certPath.getEncoded("PKCS7")); } /**Exports an X.509 certificate from a passed keystore, encoding is "DER", "PEM", "PKCS7" *@returns the certificate */ public byte[] exportX509Certificate(KeyStore keystore, String alias, String encoding) throws Exception { if (keystore.isKeyEntry(alias)) { Certificate[] certificates = keystore.getCertificateChain(alias); X509Certificate[] x509Certificates = new X509Certificate[certificates.length]; for (int i = 0; i < certificates.length; i++) { x509Certificates[i] = this.convertToX509Certificate(certificates[i]); } x509Certificates = this.orderX509CertChain(x509Certificates); X509Certificate singleCertificate = x509Certificates[0]; //write certificate to file if (encoding.equals("DER")) { byte[] encoded = singleCertificate.getEncoded(); return (encoded); } else if (encoding.equals("PEM")) { return (this.convertX509CertificateToPEM(singleCertificate).getBytes()); } else if (encoding.equals("PKCS7")) { return (this.convertX509CertificateToPKCS7(x509Certificates)); } else { throw new IllegalArgumentException("exportX509Certificate: Unsupported encoding " + encoding); } } if (keystore.isCertificateEntry(alias)) { Certificate certificate = keystore.getCertificate(alias); X509Certificate x509Certificate = this.convertToX509Certificate(certificate); //write certificate to file if (encoding.equals("DER")) { byte[] encoded = x509Certificate.getEncoded(); return (encoded); } else if (encoding.equals("PEM")) { String encoded = this.convertX509CertificateToPEM(x509Certificate); return (encoded.getBytes()); } else if (encoding.equals("PKCS7")) { return (this.convertX509CertificateToPKCS7(new X509Certificate[]{x509Certificate})); } else { throw new IllegalArgumentException("exportX509Certificate: Unsupported encoding " + encoding); } } return (null); } /**Copies all data from one stream to another*/ private void copyStreams(InputStream in, OutputStream out) throws IOException { BufferedInputStream inStream = new BufferedInputStream(in); BufferedOutputStream outStream = new BufferedOutputStream(out); //copy the contents to an output stream byte[] buffer = new byte[1024]; int read = 1024; //a read of 0 must be allowed, sometimes it takes time to //extract data from the input while (read != -1) { read = inStream.read(buffer); if (read > 0) { outStream.write(buffer, 0, read); } } outStream.flush(); } /**Exports an X.509 certificate from a passed keystore, encoding is ASN.1 DER *@returns the certificate */ public File[] exportX509CertificateDER(KeyStore keystore, String alias, String baseFilename) throws Exception { byte[] certificate = this.exportX509Certificate(keystore, alias, "DER"); File file = new File(baseFilename); if (certificate != null) { FileOutputStream outStream = new FileOutputStream(file); ByteArrayInputStream inStream = new ByteArrayInputStream(certificate); this.copyStreams(inStream, outStream); inStream.close(); outStream.flush(); outStream.close(); } return (new File[]{file}); } /**Exports an X.509 certificate from a passed keystore, encoding is PEM *@returns the certificate */ public File[] exportX509CertificatePEM(KeyStore keystore, String alias, String baseFilename) throws Exception { byte[] certificate = this.exportX509Certificate(keystore, alias, "PEM"); File file = new File(baseFilename); if (certificate != null) { FileOutputStream outStream = new FileOutputStream(file); ByteArrayInputStream inStream = new ByteArrayInputStream(certificate); this.copyStreams(inStream, outStream); inStream.close(); outStream.flush(); outStream.close(); } return (new File[]{file}); } /**Extracts the private key from a passed keystore and stores it in ASN.1 encoding as defined *in the PKCS#8 standard *@param keystore keystore that contains the private key *@param keystorePass Password for the keystore *@param alias Alias the keystore holds the private key with */ public void extractPrivateKeyToPKCS8(KeyStore keystore, char[] keystorePass, String alias, File outFile) throws Exception { if (!keystore.isKeyEntry(alias)) { throw new Exception( "The keystore does not contain the private key with the alias " + alias); } Key privateKey = keystore.getKey(alias, keystorePass); if (privateKey != null) { PKCS8EncodedKeySpec pkcs8 = new PKCS8EncodedKeySpec(privateKey.getEncoded()); OutputStream os = new FileOutputStream(outFile); os.write(pkcs8.getEncoded()); os.flush(); os.close(); } } /**Returns a map that contains all certificates of the passed keystore */ public HashMap<String, Certificate> getCertificatesFromKeystore(KeyStore keystore) throws GeneralSecurityException { HashMap<String, Certificate> certMap = new HashMap<String, Certificate>(); Enumeration enumeration = keystore.aliases(); while (enumeration.hasMoreElements()) { String certAlias = (String) enumeration.nextElement(); certMap.put(certAlias, keystore.getCertificate(certAlias)); } return (certMap); } /**Returns a list of aliases for a specified keystore, vector of string because this may be used for GUI lists */ public Vector<String> getKeyAliases(KeyStore keystore) throws KeyStoreException { Enumeration enumeration = keystore.aliases(); Vector<String> keyList = new Vector<String>(); while (enumeration.hasMoreElements()) { String alias = (String) enumeration.nextElement(); if (keystore.isKeyEntry(alias)) { keyList.add(alias); } } return (keyList); } /**Returns a list of aliases for a specified keystore, vector of string because this may be used for GUI lists */ public Vector<String> getNonKeyAliases(KeyStore keystore) throws KeyStoreException { Enumeration enumeration = keystore.aliases(); Vector<String> nonkeyList = new Vector<String>(); while (enumeration.hasMoreElements()) { String alias = (String) enumeration.nextElement(); if (!keystore.isKeyEntry(alias)) { nonkeyList.add(alias); } } return (nonkeyList); } }