de.mendelson.util.security.KeyStoreUtil.java Source code

Java tutorial

Introduction

Here is the source code for de.mendelson.util.security.KeyStoreUtil.java

Source

//$Header: /cvsroot/mec-as2/b47/de/mendelson/util/security/KeyStoreUtil.java,v 1.1 2015/01/06 11:07:56 heller Exp $
package de.mendelson.util.security;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
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.KeyStore;
import java.security.KeyStoreException;
import java.security.Provider;
import java.security.cert.CertPath;
import java.security.cert.Certificate;
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.PEMParser;

/*
 * 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);
            this.saveKeyStore(keystore, keystorePass, out);
        } finally {
            if (out != null) {
                out.close();
            }
        }
    }

    /**
     * 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, OutputStream outStream) throws Exception {
        keystore.store(outStream, keystorePass);
    }

    /**
     * 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 {
        List<X509Certificate> certList = this.readCertificates(certStream, provider);
        keystore.setCertificateEntry(alias, certList.get(0));
    }

    /**
     * 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");
    }

    /**
     * Tries to read a certificate from a byte array, may return null if reading
     * the data fails
     */
    private List<X509Certificate> readCertificates(byte[] data, Provider provider) throws CertificateException {
        CertificateFactory factory;
        List<X509Certificate> certList = null;
        if (provider != null) {
            factory = CertificateFactory.getInstance("X.509", provider);
        } else {
            factory = CertificateFactory.getInstance("X.509");
        }
        try {
            //try to read p7b files first - all other read methods will ignore certificates if there is stored more than one
            //cert in the p7b file
            Collection<? extends Certificate> tempCertList = factory
                    .generateCertPath(new ByteArrayInputStream(data), "PKCS7").getCertificates();
            if (tempCertList != null && !tempCertList.isEmpty()) {
                certList = new ArrayList<X509Certificate>();
                for (Certificate cert : tempCertList) {
                    certList.add((X509Certificate) cert);
                }
            }
        } catch (Exception e) {
        }
        try {
            if (certList == null) {
                factory = CertificateFactory.getInstance("X.509", provider);
                Collection<? extends Certificate> tempCertList = factory
                        .generateCertificates(new ByteArrayInputStream(data));
                if (tempCertList != null && !tempCertList.isEmpty()) {
                    certList = new ArrayList<X509Certificate>();
                    for (Certificate cert : tempCertList) {
                        certList.add((X509Certificate) cert);
                    }
                }
            }
        } catch (Exception e) {
        }
        try {
            //still no success, perhaps PEM encoding? Start the PEM reader and see if it could read the cert
            if (certList == null) {
                PEMParser pemParser = new PEMParser(new InputStreamReader(new ByteArrayInputStream(data)));
                X509Certificate cert = (X509Certificate) pemParser.readObject();
                if (cert != null) {
                    certList = new ArrayList<X509Certificate>();
                    certList.add(cert);
                }
            }
        } catch (Exception e) {
            //ignore so far
        }
        return (certList);
    }

    /**
     * Reads a chain of certificates from the passed stream
     */
    public List<X509Certificate> readCertificates(InputStream certStream, Provider provider) throws Exception {
        List<X509Certificate> certList = null;
        ByteArrayOutputStream memOut = new ByteArrayOutputStream();
        this.copyStreams(certStream, memOut);
        memOut.close();
        byte[] data = memOut.toByteArray();
        certList = this.readCertificates(data, provider);
        if (certList == null) {
            //no success, perhaps base64 encoded data? Decode it and retry to read
            byte[] decoded = Base64.decode(new String(data));
            certList = this.readCertificates(decoded, provider);
        }
        if (certList != null) {
            return (certList);
        } else {
            throw new CertificateException("This is not a valid certificate or contains unsupported encoding.");
        }
    }

    /**
     * Reads a certificate from a stream and returns it
     *
     * @deprecated
     */
    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) {
                PEMParser pemParser = new PEMParser(new InputStreamReader(certStream));
                cert = (X509Certificate) pemParser.readObject();
            }
        } catch (Exception e) {
            throw new CertificateException("Not a certificate or unsupported encoding: " + e.getMessage());
        }
        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", "BC");
        List<Certificate> certList = new ArrayList<Certificate>();
        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);
    }

}