net.solarnetwork.pki.bc.BCCertificateService.java Source code

Java tutorial

Introduction

Here is the source code for net.solarnetwork.pki.bc.BCCertificateService.java

Source

/* ==================================================================
 * BCCertificateService.java - Dec 5, 2012 10:42:17 AM
 * 
 * Copyright 2007-2012 SolarNetwork.net Dev Team
 * 
 * This program is free software; you can redistribute it and/or 
 * modify it under the terms of the GNU General Public License as 
 * published by the Free Software Foundation; either version 2 of 
 * the License, or (at your option) any later version.
 * 
 * This program 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 
 * General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License 
 * along with this program; if not, write to the Free Software 
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 
 * 02111-1307 USA
 * ==================================================================
 */

package net.solarnetwork.pki.bc;

import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.math.BigInteger;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.cert.CertPath;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.atomic.AtomicLong;
import javax.security.auth.x500.X500Principal;
import net.solarnetwork.support.CertificateException;
import net.solarnetwork.support.CertificateService;
import net.solarnetwork.support.CertificationAuthorityService;
import org.bouncycastle.asn1.x509.BasicConstraints;
import org.bouncycastle.asn1.x509.KeyUsage;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.cert.CertIOException;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX500NameUtil;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509ExtensionUtils;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.OperatorException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.pkcs.PKCS10CertificationRequest;
import org.bouncycastle.pkcs.PKCS10CertificationRequestBuilder;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.bouncycastle.util.io.pem.PemWriter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Bouncy Castle implementation of {@link CertificateService}.
 * 
 * @author matt
 * @version 1.0
 */
public class BCCertificateService implements CertificateService, CertificationAuthorityService {

    private final AtomicLong counter = new AtomicLong(System.currentTimeMillis());

    private int certificateExpireDays = 730;
    private int authorityExpireDays = 7300;
    private String signatureAlgorithm = "SHA256WithRSA";

    private final Logger log = LoggerFactory.getLogger(getClass());

    @Override
    public X509Certificate generateCertificate(String dn, PublicKey publicKey, PrivateKey privateKey) {
        X500Principal issuer = new X500Principal(dn);
        Date now = new Date();
        Date expire = new Date(now.getTime() + (1000L * 60L * 60L * 24L * certificateExpireDays));
        JcaX509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(issuer,
                new BigInteger(String.valueOf(counter.incrementAndGet())), now, expire, issuer, publicKey);
        JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(signatureAlgorithm);
        ContentSigner signer;
        try {
            signer = signerBuilder.build(privateKey);
        } catch (OperatorCreationException e) {
            throw new CertificateException("Error signing certificate", e);
        }
        X509CertificateHolder holder = builder.build(signer);
        JcaX509CertificateConverter converter = new JcaX509CertificateConverter();
        try {
            return converter.getCertificate(holder);
        } catch (java.security.cert.CertificateException e) {
            throw new CertificateException("Error creating certificate", e);
        }
    }

    @Override
    public X509Certificate generateCertificationAuthorityCertificate(String dn, PublicKey publicKey,
            PrivateKey privateKey) {
        X500Principal issuer = new X500Principal(dn);
        Date now = new Date();
        Date expire = new Date(now.getTime() + (1000L * 60L * 60L * 24L * authorityExpireDays));
        JcaX509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(issuer, new BigInteger("0"), now,
                expire, issuer, publicKey);
        JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(signatureAlgorithm);
        DefaultDigestAlgorithmIdentifierFinder digestAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();
        ContentSigner signer;
        try {
            DigestCalculatorProvider digestCalcProvider = new JcaDigestCalculatorProviderBuilder()
                    .setProvider(new BouncyCastleProvider()).build();
            JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(
                    digestCalcProvider.get(digestAlgFinder.find("SHA-256")));
            builder.addExtension(X509Extension.basicConstraints, true, new BasicConstraints(true));
            builder.addExtension(X509Extension.subjectKeyIdentifier, false,
                    extUtils.createSubjectKeyIdentifier(publicKey));
            builder.addExtension(X509Extension.keyUsage, true, new KeyUsage(
                    KeyUsage.digitalSignature | KeyUsage.nonRepudiation | KeyUsage.keyCertSign | KeyUsage.cRLSign));
            builder.addExtension(X509Extension.authorityKeyIdentifier, false,
                    extUtils.createAuthorityKeyIdentifier(publicKey));

            signer = signerBuilder.build(privateKey);
        } catch (OperatorCreationException e) {
            log.error("Error generating CA certificate [{}]", dn, e);
            throw new CertificateException("Error signing CA certificate", e);
        } catch (CertIOException e) {
            log.error("Error generating CA certificate [{}]", dn, e);
            throw new CertificateException("Error signing CA certificate", e);
        }
        X509CertificateHolder holder = builder.build(signer);
        JcaX509CertificateConverter converter = new JcaX509CertificateConverter();
        try {
            return converter.getCertificate(holder);
        } catch (java.security.cert.CertificateException e) {
            throw new CertificateException("Error creating certificate", e);
        }
    }

    @Override
    public X509Certificate signCertificate(String csrPEM, X509Certificate caCert, PrivateKey privateKey)
            throws CertificateException {
        if (!csrPEM.matches("(?is)^\\s*-----BEGIN.*")) {
            // let's throw in the guards
            csrPEM = "-----BEGIN CERTIFICATE REQUEST-----\n" + csrPEM + "\n-----END CERTIFICATE REQUEST-----\n";
        }
        PemReader reader = null;
        try {
            reader = new PemReader(new StringReader(csrPEM));
            PemObject pemObj = reader.readPemObject();
            log.debug("Parsed PEM type {}", pemObj.getType());
            PKCS10CertificationRequest csr = new PKCS10CertificationRequest(pemObj.getContent());

            Date now = new Date();
            Date expire = new Date(now.getTime() + (1000L * 60L * 60L * 24L * certificateExpireDays));
            X509v3CertificateBuilder builder = new X509v3CertificateBuilder(JcaX500NameUtil.getIssuer(caCert),
                    new BigInteger(String.valueOf(counter.incrementAndGet())), now, expire, csr.getSubject(),
                    csr.getSubjectPublicKeyInfo());

            JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(signatureAlgorithm);
            ContentSigner signer;
            DefaultDigestAlgorithmIdentifierFinder digestAlgFinder = new DefaultDigestAlgorithmIdentifierFinder();
            try {
                DigestCalculatorProvider digestCalcProvider = new JcaDigestCalculatorProviderBuilder()
                        .setProvider(new BouncyCastleProvider()).build();
                JcaX509ExtensionUtils extUtils = new JcaX509ExtensionUtils(
                        digestCalcProvider.get(digestAlgFinder.find("SHA-256")));
                builder.addExtension(X509Extension.basicConstraints, false, new BasicConstraints(false));
                builder.addExtension(X509Extension.subjectKeyIdentifier, false,
                        extUtils.createSubjectKeyIdentifier(csr.getSubjectPublicKeyInfo()));
                builder.addExtension(X509Extension.authorityKeyIdentifier, false,
                        extUtils.createAuthorityKeyIdentifier(caCert));

                signer = signerBuilder.build(privateKey);
            } catch (OperatorException e) {
                log.error("Error signing CSR {}", csr.getSubject(), e);
                throw new CertificateException("Error signing CSR" + csr.getSubject() + ": " + e.getMessage());
            } catch (CertificateEncodingException e) {
                log.error("Error signing CSR {}", csr.getSubject().toString(), e);
                throw new CertificateException("Error signing CSR" + csr.getSubject() + ": " + e.getMessage());
            }

            X509CertificateHolder holder = builder.build(signer);
            JcaX509CertificateConverter converter = new JcaX509CertificateConverter();
            try {
                return converter.getCertificate(holder);
            } catch (java.security.cert.CertificateException e) {
                throw new CertificateException("Error creating certificate", e);
            }
        } catch (IOException e) {
            throw new CertificateException("Error signing CSR", e);
        } finally {
            if (reader != null) {
                try {
                    reader.close();
                } catch (IOException e2) {
                    log.warn("IOException closing PemReader", e2);
                }
            }
        }
    }

    @Override
    public String generatePKCS10CertificateRequestString(X509Certificate cert, PrivateKey privateKey)
            throws CertificateException {
        X509CertificateHolder holder;
        try {
            holder = new JcaX509CertificateHolder(cert);
        } catch (CertificateEncodingException e) {
            throw new CertificateException("Error creating CSR", e);
        }
        PKCS10CertificationRequestBuilder builder = new PKCS10CertificationRequestBuilder(holder.getSubject(),
                holder.getSubjectPublicKeyInfo());
        JcaContentSignerBuilder signerBuilder = new JcaContentSignerBuilder(signatureAlgorithm);
        ContentSigner signer;
        try {
            signer = signerBuilder.build(privateKey);
        } catch (OperatorCreationException e) {
            throw new CertificateException("Error signing certificate request", e);
        }
        PKCS10CertificationRequest csr = builder.build(signer);
        StringWriter writer = new StringWriter();
        PemWriter pemWriter = new PemWriter(writer);
        try {
            pemWriter.writeObject(new PemObject("CERTIFICATE REQUEST", csr.getEncoded()));
        } catch (IOException e) {
            throw new CertificateException("Error signing certificate", e);
        } finally {
            try {
                pemWriter.flush();
                pemWriter.close();
                writer.close();
            } catch (IOException e) {
                // ignore this
            }
        }
        return writer.toString();
    }

    private void orderCertificateChain(Map<X500Principal, X509Certificate> map, List<X509Certificate> results,
            X509Certificate c) {
        X509Certificate parent = map.get(c.getIssuerX500Principal());
        if (parent != null) {
            orderCertificateChain(map, results, parent);
        }

        // find parent in results, or else add to end
        for (ListIterator<X509Certificate> itr = results.listIterator(); itr.hasNext();) {
            X509Certificate p = itr.next();
            if (p.getSubjectDN().equals(c.getIssuerDN())) {
                itr.previous();
                itr.add(c);
                break;
            }
        }
        map.remove(c.getSubjectX500Principal());
    }

    private void orderCertificateChain(Map<X500Principal, X509Certificate> map, List<X509Certificate> results) {
        while (map.size() > 0) {
            orderCertificateChain(map, results, map.values().iterator().next());
        }
    }

    @Override
    public X509Certificate[] parsePKCS7CertificateChainString(String pem) throws CertificateException {
        if (!pem.matches("(?is)^\\s*-----BEGIN.*")) {
            // let's throw in the guards
            pem = "-----BEGIN CERTIFICATE CHAIN-----\n" + pem + "\n-----END CERTIFICATE CHAIN-----\n";
        }
        PemReader reader = new PemReader(new StringReader(pem));
        List<X509Certificate> results = new ArrayList<X509Certificate>(3);
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            PemObject pemObj = reader.readPemObject();
            log.debug("Parsed PEM type {}", pemObj.getType());
            Collection<? extends Certificate> certs = cf
                    .generateCertificates(new ByteArrayInputStream(pemObj.getContent()));

            // OK barf, generateCertificates() and even CertPath doesn't return the chain in order
            // (see http://bugs.sun.com/view_bug.do?bug_id=6238093; but we can't use the Sun-specific
            // workaround listed there). So let's try to order them ourselves
            Map<X500Principal, X509Certificate> map = new LinkedHashMap<X500Principal, X509Certificate>();
            for (Certificate c : certs) {
                X509Certificate x509 = (X509Certificate) c;
                if (x509.getIssuerDN().equals(x509.getSubjectDN())) {
                    // root CA
                    results.add(x509);
                } else {
                    map.put(x509.getSubjectX500Principal(), x509);
                }
            }
            if (results.size() == 0) {
                // no root, just add everything to list
                results.addAll(map.values());
            } else {
                orderCertificateChain(map, results);
            }
        } catch (IOException e) {
            throw new CertificateException("Error reading certificate", e);
        } catch (java.security.cert.CertificateException e) {
            throw new CertificateException("Error loading CertificateFactory", e);
        } finally {
            try {
                reader.close();
            } catch (IOException e) {
                // ignore me
            }
        }
        return results.toArray(new X509Certificate[results.size()]);
    }

    @Override
    public String generatePKCS7CertificateChainString(X509Certificate[] chain) throws CertificateException {
        try {
            CertificateFactory cf = CertificateFactory.getInstance("X.509");
            List<X509Certificate> chainList = Arrays.asList(chain);
            CertPath path = cf.generateCertPath(chainList);
            StringWriter out = new StringWriter();
            PemWriter writer = new PemWriter(out);
            PemObject pemObj = new PemObject("CERTIFICATE" + (chain.length > 1 ? " CHAIN" : ""),
                    path.getEncoded("PKCS7"));
            writer.writeObject(pemObj);
            writer.flush();
            writer.close();
            out.close();
            String result = out.toString();
            log.debug("Generated cert chain:\n{}", result);
            return result;
        } catch (IOException e) {
            throw new CertificateException("Error generating PKCS#7 chain", e);
        } catch (java.security.cert.CertificateException e) {
            throw new CertificateException("Error generating PKCS#7 chain", e);
        }
    }

    public void setCertificateExpireDays(int certificateExpireDays) {
        this.certificateExpireDays = certificateExpireDays;
    }

    public void setSignatureAlgorithm(String signatureAlgorithm) {
        this.signatureAlgorithm = signatureAlgorithm;
    }

    public void setAuthorityExpireDays(int authorityExpireDays) {
        this.authorityExpireDays = authorityExpireDays;
    }

}