org.tolven.security.cert.CertificateHelper.java Source code

Java tutorial

Introduction

Here is the source code for org.tolven.security.cert.CertificateHelper.java

Source

/*
 *  Copyright (C) 2006 Tolven Inc 
 *
 * This library is free software; you can redistribute it and/or modify it under the terms of 
 * the GNU Lesser General Public License as published by the Free Software Foundation; either 
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; 
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 
 * See the GNU Lesser General Public License for more details.
 * 
 * Contact: info@tolvenhealth.com
 */
package org.tolven.security.cert;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.math.BigInteger;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
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.util.Date;
import java.util.Enumeration;

import javax.naming.InvalidNameException;
import javax.naming.directory.Attributes;
import javax.naming.directory.BasicAttributes;
import javax.naming.ldap.Rdn;
import javax.security.auth.x500.X500Principal;

import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.x509.X509V3CertificateGenerator;
import org.tolven.naming.TolvenPerson;

/**
 * This class provides functionality for creating certificates
 * 
 * @author Joseph Isaac
 *
 */
public class CertificateHelper {

    public static final String TOLVEN_CREDENTIAL_FORMAT_PKCS12 = "pkcs12";
    public static final String USER_PRIVATE_KEY_ALGORITHM_PROP = "RSA";
    public static final String USER_PRIVATE_KEY_LENGTH_PROP = "1024";
    public static final String PBE_KEY_ALGORITHM_PROP = "PBEWithMD5AndDES";
    public static final String USER_PASSWORD_SALT_LENGTH_PROP = "8";
    public static final String USER_PASSWORD_ITERATION_COUNT_PROP = "20";

    private SecureRandom secureRandom;

    public CertificateHelper() {
        //TODO Is this the right place to add a provider. It should be a one time initialization for the JVM
        Security.addProvider(new org.bouncycastle.jce.provider.BouncyCastleProvider());
    }

    public void updateCredentials(TolvenPerson tolvenPerson, String base64UserPKCS12, char[] userPassword) {
        KeyStore keyStore = null;
        try {
            try {
                byte[] userPKCS12 = Base64.decodeBase64(base64UserPKCS12.getBytes("UTF-8"));
                keyStore = getKeyStore(userPKCS12, userPassword);
            } catch (Exception ex) {
                throw new RuntimeException("Could not decode userPKCS12", ex);
            }
        } catch (Exception ex) {
            throw new RuntimeException("Could not retrieve keystore from " + base64UserPKCS12, ex);
        }
        byte[] userCertificate = getX509CertificateByteArray(keyStore);
        tolvenPerson.setUserCertificate(userCertificate);
        byte[] userPKCS12Bytes = toByteArray(keyStore, userPassword);
        tolvenPerson.setUserPKCS12(userPKCS12Bytes);
    }

    public void createCredentials(TolvenPerson tolvenPerson, char[] userPassword) {
        KeyStore keyStore = createPKCS12KeyStore(tolvenPerson.getUid(), tolvenPerson.getCn(),
                tolvenPerson.getOrganizationUnitName(), tolvenPerson.getOrganizationName(),
                tolvenPerson.getStateOrProvince(), userPassword);
        byte[] userCertificate = getX509CertificateByteArray(keyStore);
        tolvenPerson.setUserCertificate(userCertificate);
        byte[] userPKCS12 = CertificateHelper.toByteArray(keyStore, userPassword);
        tolvenPerson.setUserPKCS12(userPKCS12);
    }

    /**
     * Return the X509Certificate of the first alias in the keyStore
     * 
     * @param keyStore
     * @return
     */
    public static X509Certificate getX509Certificate(KeyStore keyStore) {
        String alias = null;
        try {
            Enumeration<String> aliases = keyStore.aliases();
            if (!aliases.hasMoreElements()) {
                throw new RuntimeException("KeyStore contains no aliases");
            }
            alias = aliases.nextElement();
        } catch (KeyStoreException ex) {
            throw new RuntimeException("Could obtain alias: " + alias + " in the userPKCS12 keystore", ex);
        }
        try {
            Certificate[] certificateChain = keyStore.getCertificateChain(alias);
            if (certificateChain == null || certificateChain.length == 0) {
                throw new RuntimeException("KeyStore contains no certificate with alias " + alias);
            }
            return (X509Certificate) certificateChain[0];
        } catch (KeyStoreException ex) {
            throw new RuntimeException(
                    "Could not obtain X509Certificate from userPKCS12 keystore using alias: " + alias, ex);
        }
    }

    public PrivateKey getPrivateKey(KeyStore keyStore, char[] password) {
        String alias = null;
        try {
            Enumeration<String> aliases = keyStore.aliases();
            if (!aliases.hasMoreElements()) {
                throw new RuntimeException("KeyStore contains no aliases");
            }
            alias = aliases.nextElement();
        } catch (KeyStoreException ex) {
            throw new RuntimeException("Could obtain alias: " + alias + " in the userPKCS12 keystore", ex);
        }
        try {
            return (PrivateKey) keyStore.getKey(alias, password);
        } catch (Exception ex) {
            throw new RuntimeException("Could not get PrivateKey from KeyStore using alias: " + alias, ex);
        }
    }

    /**
     * Return the X509Certificate as a byte[] of the first alias in the keyStore
     * 
     * @param keyStore
     * @return
     */
    public static byte[] getX509CertificateByteArray(KeyStore keyStore) {
        X509Certificate x509Certificate = getX509Certificate(keyStore);
        try {
            return x509Certificate.getEncoded();
        } catch (CertificateEncodingException ex) {
            throw new RuntimeException("Could not get encoded bytes from X509Certificate", ex);
        }
    }

    public KeyStore createPKCS12KeyStore(String uid, String cn, String organizationUnitName,
            String organizationName, String stateOrProvince, char[] password) {
        X509CertificatePrivateKeyPair x509CertificatePrivateKeyPair = createX509CertificatePrivateKeyPair(uid, cn,
                organizationUnitName, organizationName, stateOrProvince);
        return x509CertificatePrivateKeyPair.createPKCS12KeyStore(uid, password);
    }

    private X509CertificatePrivateKeyPair createX509CertificatePrivateKeyPair(String email, String commonName,
            String organizationUnitName, String organizationName, String stateOrProvince) {
        String privateKeyAlgorithm = USER_PRIVATE_KEY_ALGORITHM_PROP;
        KeyPairGenerator keyPairGenerator;
        try {
            keyPairGenerator = KeyPairGenerator.getInstance(privateKeyAlgorithm);
        } catch (NoSuchAlgorithmException ex) {
            throw new RuntimeException("Could not get KeyPairGenerator for algorithm: " + privateKeyAlgorithm, ex);
        }
        int keySize = Integer.parseInt(USER_PRIVATE_KEY_LENGTH_PROP);
        keyPairGenerator.initialize(keySize);
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        X500Principal x500Principal = getX500Principal(email, commonName, organizationUnitName, organizationName,
                stateOrProvince);
        return createSelfSignedCertificate(x500Principal, keyPair.getPublic(), keyPair.getPrivate());
    }

    private X509CertificatePrivateKeyPair createSelfSignedCertificate(X500Principal subjectX500Principal,
            PublicKey publicKey, PrivateKey privateKey) {
        X509Certificate certificate = signCertificate(subjectX500Principal, publicKey, subjectX500Principal,
                privateKey);
        return new X509CertificatePrivateKeyPair(certificate, privateKey);
    }

    private BigInteger getNextSerialNumber() {
        byte[] randomLong = new byte[8];
        getSecureRandom().nextBytes(randomLong);
        BigInteger serialNumber = new BigInteger(randomLong);
        if (serialNumber.compareTo(BigInteger.valueOf(0)) > 0) {
            return serialNumber;
        } else if (serialNumber.compareTo(BigInteger.valueOf(0)) < 0) {
            return BigInteger.valueOf(0).subtract(serialNumber);
        } else {
            return BigInteger.valueOf(1).add(serialNumber);
        }
    }

    private SecureRandom getSecureRandom() {
        //TODO: This may be better placed where it only gets created once for certain
        if (secureRandom == null)
            try {
                secureRandom = SecureRandom.getInstance("SHA1PRNG");
            } catch (NoSuchAlgorithmException ex) {
                throw new RuntimeException("Could not obtain a SecureRandom instance", ex);
            }
        return secureRandom;
    }

    public static X500Principal getX500Principal(String email, String commonName, String organizationUnitName,
            String organizationName, String stateOrProvince) {
        if (null == email || null == commonName || null == organizationUnitName || null == organizationName
                || null == stateOrProvince) {
            throw new RuntimeException(
                    "Certificate requires EmailAddress, Common Name, organizationUnitName, organizationName, stateOrProvince");
        }
        Attributes attributes = new BasicAttributes();
        attributes.put(X509Name.EmailAddress.toString(), email);
        attributes.put(X509Name.CN.toString(), commonName);
        attributes.put(X509Name.OU.toString(), organizationUnitName);
        attributes.put(X509Name.O.toString(), organizationName);
        attributes.put(X509Name.ST.toString(), stateOrProvince);
        Rdn rdn;
        try {
            rdn = new Rdn(attributes);
        } catch (InvalidNameException ex) {
            throw new RuntimeException("Failed to obtain a Relative Distinguised Name", ex);
        }
        return new X500Principal(rdn.toString());
    }

    private X509Certificate signCertificate(X500Principal subjectX500Principal, PublicKey subjectPublicKey,
            X500Principal issuerX500Principal, PrivateKey issuerPrivateKey) {
        X509V3CertificateGenerator gen = new X509V3CertificateGenerator();
        gen.setSignatureAlgorithm("SHA1withRSA");
        gen.setSubjectDN(subjectX500Principal);
        gen.setSerialNumber(getNextSerialNumber());
        gen.setIssuerDN(issuerX500Principal);
        gen.setNotBefore(new Date());
        gen.setNotAfter(new Date(new Date().getTime() + 1000000000000L));
        gen.setPublicKey(subjectPublicKey);
        try {
            return gen.generate(issuerPrivateKey);
        } catch (Exception e) {
            throw new RuntimeException("Could not sign cerfificate for: " + subjectX500Principal.getName(), e);
        }
    }

    public static byte[] toByteArray(KeyStore keyStore, char[] password) {
        ByteArrayOutputStream baos = null;
        try {
            baos = new ByteArrayOutputStream();
            try {
                keyStore.store(baos, password);
            } catch (Exception ex) {
                throw new RuntimeException("Could not store keystore", ex);
            }
            byte[] byteArr = baos.toByteArray();
            return byteArr;
        } finally {
            if (baos != null)
                try {
                    baos.close();
                } catch (Exception ex) {
                    throw new RuntimeException("Could not close bytearrayoutputstream for keystore", ex);
                }
        }
    }

    public static X509Certificate getX509Certificate(byte[] bytes) {
        //return (X509Certificate) getPEMObject(bytes);
        X509Certificate x509Certificate = null;
        ByteArrayInputStream bis = null;
        try {
            bis = new ByteArrayInputStream(bytes);
            try {
                CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
                x509Certificate = (X509Certificate) certificateFactory.generateCertificate(bis);
            } catch (CertificateException ex) {
                throw new RuntimeException("Could not generate an X509 certificate", ex);
            }
        } finally {
            if (bis != null) {
                try {
                    bis.close();
                } catch (Exception ex) {
                    throw new RuntimeException(
                            "Could not close bytearrayinputstream after generating an X509 certificate", ex);
                }
            }
        }
        return x509Certificate;
    }

    public static KeyStore getKeyStore(byte[] bytes, char[] password) {
        ByteArrayInputStream bais = null;
        try {
            bais = new ByteArrayInputStream(bytes);
            try {
                KeyStore keyStore = KeyStore.getInstance(TOLVEN_CREDENTIAL_FORMAT_PKCS12);
                keyStore.load(bais, password);
                return keyStore;
            } catch (Exception ex) {
                throw new RuntimeException("Could not load keystore", ex);
            }
        } finally {
            if (bais != null)
                try {
                    bais.close();
                } catch (Exception ex) {
                    throw new RuntimeException("Could not close bytearrayinputstream after loading keystore", ex);
                }
        }
    }

    public static void changeKeyStorePassword(KeyStore keyStore, char[] oldPassword, char[] newPassword) {
        String alias = null;
        try {
            Enumeration<String> aliases = keyStore.aliases();
            if (!aliases.hasMoreElements()) {
                throw new RuntimeException("KeyStore contains no aliases");
            }
            alias = aliases.nextElement();
        } catch (KeyStoreException ex) {
            throw new RuntimeException("Could obtain alias: " + alias + " in the userPKCS12 keystore", ex);
        }
        changeKeyStorePassword(keyStore, alias, oldPassword, newPassword);
    }

    public static void changeKeyStorePassword(KeyStore keyStore, String alias, char[] oldPassword,
            char[] newPassword) {
        try {
            PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, oldPassword);
            keyStore.setKeyEntry(alias, privateKey, newPassword, keyStore.getCertificateChain(alias));
        } catch (GeneralSecurityException ex) {
            throw new RuntimeException("Could not change the keystore password for with alias: " + alias, ex);
        }
    }

    /**
     * During generation of a PrivateKey and Certificate pair, both components are often created simultaneously, so
     * this class is a wrapper which allows both to be returned together
     * @author Joseph Isaac
     *
     */
    private class X509CertificatePrivateKeyPair {

        private PrivateKey privateKey;
        private X509Certificate certificate;

        public X509CertificatePrivateKeyPair(X509Certificate certificate, PrivateKey privateKey) {
            this.certificate = certificate;
            this.privateKey = privateKey;
        }

        public PrivateKey getPrivateKey() {
            return privateKey;
        }

        public X509Certificate getX509Certificate() {
            return certificate;
        }

        public KeyStore createPKCS12KeyStore(String alias, char[] password) {
            // We can't use Bouncy Castle here because it requires unrestricted strength
            // which Tolven does not require (although it is allowed). 
            try {
                KeyStore keyStore = KeyStore.getInstance(CertificateHelper.TOLVEN_CREDENTIAL_FORMAT_PKCS12);
                try {
                    keyStore.load(null, password);
                } catch (Exception ex) {
                    throw new RuntimeException("Could not load keystore for alias: " + alias, ex);
                }
                X509Certificate[] certificateChain = new X509Certificate[1];
                certificateChain[0] = getX509Certificate();
                keyStore.setKeyEntry(alias, getPrivateKey(), password, certificateChain);
                return keyStore;
            } catch (GeneralSecurityException ex) {
                throw new RuntimeException("Could not create a PKCS12 keystore for alias: " + alias, ex);
            }
        }

    }

}