ch.bfh.unicert.certimport.CertificateIssuer.java Source code

Java tutorial

Introduction

Here is the source code for ch.bfh.unicert.certimport.CertificateIssuer.java

Source

/*
 * Copyright (c) 2014 Berner Fachhochschule, Switzerland.
 * Bern University of Applied Sciences, Engineering and Information Technology,
 * Research Institute for Security in the Information Society, E-Voting Group,
 * Biel, Switzerland.
 *
 * Project UniCert.
 *
 * Distributable under GPL license.
 * See terms of license at gnu.org.
 */
package ch.bfh.unicert.certimport;

import ch.bfh.uniboard.clientlib.BoardErrorException;
import ch.bfh.uniboard.clientlib.PostException;
import ch.bfh.uniboard.clientlib.PostHelper;
import ch.bfh.unicrypt.helper.converter.classes.ConvertMethod;
import ch.bfh.unicrypt.helper.converter.classes.bytearray.BigIntegerToByteArray;
import ch.bfh.unicrypt.helper.converter.classes.bytearray.ByteArrayToByteArray;
import ch.bfh.unicrypt.helper.converter.classes.bytearray.StringToByteArray;
import ch.bfh.unicrypt.helper.hash.HashAlgorithm;
import ch.bfh.unicrypt.helper.hash.HashMethod;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.ByteOrder;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PublicKey;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.RSAPrivateCrtKey;
import java.security.interfaces.RSAPublicKey;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bouncycastle.asn1.ASN1Encodable;
import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.DERBoolean;
import org.bouncycastle.asn1.DERInteger;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.DERObjectIdentifier;
import org.bouncycastle.asn1.DEROutputStream;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.DigestInfo;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x509.TBSCertificateStructure;
import org.bouncycastle.asn1.x509.Time;
import org.bouncycastle.asn1.x509.V3TBSCertificateGenerator;
import org.bouncycastle.asn1.x509.X509CertificateStructure;
import org.bouncycastle.asn1.x509.X509Extension;
import org.bouncycastle.asn1.x509.X509Extensions;
import org.bouncycastle.asn1.x509.X509Name;
import org.bouncycastle.asn1.x509.X509ObjectIdentifiers;
import org.bouncycastle.crypto.AsymmetricBlockCipher;
import org.bouncycastle.crypto.InvalidCipherTextException;
import org.bouncycastle.crypto.digests.SHA1Digest;
import org.bouncycastle.crypto.encodings.PKCS1Encoding;
import org.bouncycastle.crypto.engines.RSAEngine;
import org.bouncycastle.crypto.params.RSAPrivateCrtKeyParameters;
import org.bouncycastle.jce.PrincipalUtil;
import org.bouncycastle.jce.provider.X509CertificateObject;

/**
 * Implements the certificate issuence service used by the authentication component of the unicert webclient. Upon
 * request constructs a certificate for the requestor and returns it
 *
 * @author Eric Dubuis <eric.dubuis@bfh.ch>
 * @author Reto Koenig <reto.koenig@bfh.ch>
 * @author Philmon von Bergen <philemon.vonbergen@bfh.ch>
 */
public class CertificateIssuer {

    private static final int MIN_RSA_SIZE = 1010;
    private static final int MIN_DLOG_SIZE = 1010;

    private static final String ATTRIBUTE_NAME_PUBLICKEY = "publickey";
    private static final String ATTRIBUTE_NAME_SIG = "signature";
    private static final String ATTRIBUTE_NAME_BOARD_SIG = "boardSignature";
    private static final String GROUPED = "group";
    private static final String SECTIONED = "section";

    protected static final HashMethod HASH_METHOD = HashMethod.getInstance(HashAlgorithm.SHA256,
            ConvertMethod.getInstance(BigIntegerToByteArray.getInstance(ByteOrder.BIG_ENDIAN),
                    ByteArrayToByteArray.getInstance(false),
                    StringToByteArray.getInstance(Charset.forName("UTF-8"))),
            HashMethod.Mode.RECURSIVE);

    private static final Logger logger = Logger.getLogger(CertificateIssuer.class.getName());
    private KeyStore caKs;
    private final String issuerId;
    private final String privKeyPass;
    private String boardId;

    public CertificateIssuer(String keyStorePath, String keyStorePass, String issuerId, String privKeyPass,
            String boardId) {
        this.issuerId = issuerId;
        this.privKeyPass = privKeyPass;
        this.boardId = boardId;
        try {
            this.loadKeyStore(keyStorePath, keyStorePass);
        } catch (KeyStoreException | IOException | NoSuchAlgorithmException | CertificateException ex) {
            logger.log(Level.SEVERE, null, ex);
        }
    }

    public Certificate createClientCertificate(IdentityData id, String keyStorePath, PublicKey pk, int validity,
            String applicationIdentifier, String[] roles, String uniBoardWsdlURL, String uniBoardServiceURL,
            String section) throws CertificateCreationException {

        X509Certificate caCert;
        RSAPrivateCrtKey privKey;
        try {
            caCert = this.readIssuerCertificate(this.issuerId);
            privKey = this.readPrivateKey(this.issuerId, this.privKeyPass);
        } catch (KeyStoreException | NoSuchAlgorithmException | UnrecoverableKeyException ex) {
            logger.log(Level.SEVERE, null, ex);
            throw new CertificateCreationException("230 Could not create client certificate. Key error");
        }

        RSAPrivateCrtKeyParameters cipherParams = this.createIssuerCipherParams(privKey);

        X509Certificate clientCert;

        Hashtable extension = new Hashtable();

        extension.put(new DERObjectIdentifier(ExtensionOID.APPLICATION_IDENTIFIER.getOID()),
                new X509Extension(DERBoolean.FALSE, CertificateHelper.stringToDER(applicationIdentifier)));

        String completeRole = "";
        for (String role : roles) {
            completeRole += role + ", ";
        }
        completeRole = completeRole.substring(0, completeRole.length() - 2);
        extension.put(new DERObjectIdentifier(ExtensionOID.ROLE.getOID()),
                new X509Extension(DERBoolean.FALSE, CertificateHelper.stringToDER(completeRole)));

        extension.put(new DERObjectIdentifier(ExtensionOID.IDENTITY_PROVIDER.getOID()),
                new X509Extension(DERBoolean.FALSE, CertificateHelper.stringToDER(id.getIdentityProvider())));

        Map<String, String> extensionMap = new HashMap();
        if (id.getOtherValues() != null) {
            for (Entry<ExtensionOID, String> entry : id.getOtherValues().entrySet()) {
                extension.put(new DERObjectIdentifier(entry.getKey().getOID()),
                        new X509Extension(DERBoolean.FALSE, CertificateHelper.stringToDER(entry.getValue())));
                extensionMap.put(entry.getKey().getName(), entry.getValue());
            }
        }

        try {

            String x509NameString = "";
            x509NameString += "CN=" + id.getCommonName();

            if (id.getSurname() != null && !id.getSurname().equals("")) {
                x509NameString += ", SURNAME=" + id.getSurname();
            }
            if (id.getGivenName() != null && !id.getGivenName().equals("")) {
                x509NameString += ", GIVENNAME=" + id.getGivenName();
            }
            if (id.getUniqueIdentifier() != null && !id.getUniqueIdentifier().equals("")) {
                x509NameString += ", UID=" + id.getUniqueIdentifier();
            }
            if (id.getOrganisation() != null && !id.getOrganisation().equals("")) {
                x509NameString += ", O=" + id.getOrganisation();
            }
            if (id.getOrganisationUnit() != null && !id.getOrganisationUnit().equals("")) {
                x509NameString += ", OU=" + id.getOrganisationUnit();
            }
            if (id.getCountryName() != null && !id.getCountryName().equals("")) {
                x509NameString += ", C=" + id.getCountryName();
            }
            if (id.getState() != null && !id.getState().equals("")) {
                x509NameString += ", ST=" + id.getState();
            }
            if (id.getLocality() != null && !id.getLocality().equals("")) {
                x509NameString += ", L=" + id.getLocality();
            }

            X509Name x509Name = new X509Name(x509NameString);

            V3TBSCertificateGenerator certGen = new V3TBSCertificateGenerator();
            certGen.setSerialNumber(new DERInteger(BigInteger.valueOf(System.currentTimeMillis())));
            certGen.setIssuer(PrincipalUtil.getSubjectX509Principal(caCert));
            certGen.setSubject(x509Name);
            certGen.setExtensions(new X509Extensions(extension));
            DERObjectIdentifier sigOID = new DERObjectIdentifier("1.2.840.113549.1.1.5");
            AlgorithmIdentifier sigAlgId = new AlgorithmIdentifier(sigOID, new DERNull());
            certGen.setSignature(sigAlgId);
            certGen.setSubjectPublicKeyInfo(new SubjectPublicKeyInfo(
                    (ASN1Sequence) new ASN1InputStream(new ByteArrayInputStream(pk.getEncoded())).readObject()));
            certGen.setStartDate(new Time(new Date(System.currentTimeMillis())));
            certGen.setEndDate(new Time(getExpiryDate(validity).getTime()));
            TBSCertificateStructure tbsCert = certGen.generateTBSCertificate();

            //Sign certificate
            SHA1Digest digester = new SHA1Digest();
            AsymmetricBlockCipher rsa = new PKCS1Encoding(new RSAEngine());
            ByteArrayOutputStream bOut = new ByteArrayOutputStream();
            DEROutputStream dOut = new DEROutputStream(bOut);
            dOut.writeObject(tbsCert);
            byte[] signature;
            byte[] certBlock = bOut.toByteArray();
            // first create digest
            digester.update(certBlock, 0, certBlock.length);
            byte[] hash = new byte[digester.getDigestSize()];
            digester.doFinal(hash, 0);
            // then sign it
            rsa.init(true, cipherParams);
            DigestInfo dInfo = new DigestInfo(new AlgorithmIdentifier(X509ObjectIdentifiers.id_SHA1, null), hash);
            byte[] digest = dInfo.getEncoded(ASN1Encodable.DER);
            signature = rsa.processBlock(digest, 0, digest.length);

            ASN1EncodableVector v = new ASN1EncodableVector();
            v.add(tbsCert);
            v.add(sigAlgId);
            v.add(new DERBitString(signature));

            // Create CRT data structure
            clientCert = new X509CertificateObject(new X509CertificateStructure(new DERSequence(v)));
            clientCert.verify(caCert.getPublicKey());
        } catch (IOException | InvalidCipherTextException | CertificateException | NoSuchAlgorithmException
                | InvalidKeyException | NoSuchProviderException | SignatureException e) {
            logger.log(Level.SEVERE, "Could not create client certificate: {0}", new Object[] { e.getMessage() });
            throw new CertificateCreationException("230 Could not create client certificate");
        }

        Certificate cert = new Certificate(clientCert, id.getCommonName(), id.getUniqueIdentifier(),
                id.getOrganisation(), id.getOrganisationUnit(), id.getCountryName(), id.getState(),
                id.getLocality(), id.getSurname(), id.getGivenName(), applicationIdentifier, roles,
                id.getIdentityProvider(), extensionMap);

        //post message on UniBoard if corresponding JNDI parameter is defined
        postOnUniBoard(cert, uniBoardWsdlURL, uniBoardServiceURL, section, (RSAPublicKey) caCert.getPublicKey(),
                privKey);

        return cert;

    }

    protected void postOnUniBoard(Certificate cert, String wsdlUrl, String endpointUrl, String section,
            RSAPublicKey publicKey, RSAPrivateCrtKey privateKey) throws CertificateCreationException {

        try {
            X509Certificate boardCertificate = (X509Certificate) caKs.getCertificate(this.boardId);
            DSAPublicKey pk = (DSAPublicKey) boardCertificate.getPublicKey();
            PostHelper ph = new PostHelper(publicKey, privateKey, pk, wsdlUrl, endpointUrl);
            ph.post(cert.toJSON().getBytes("UTF-8"), section, "certificate");

        } catch (PostException | BoardErrorException | UnsupportedEncodingException | KeyStoreException
                | ch.bfh.uniboard.clientlib.signaturehelper.SignatureException ex) {
            logger.log(Level.SEVERE, "230 UniBoard rejected post: " + ex.getMessage());
            throw new CertificateCreationException("230 UniBoard rejected post: " + ex.getMessage());
        }
    }

    /**
     * Returns the calendar representing the expiration date. By default, 2 years after now is set
     *
     * @return a calendar
     */
    private Calendar getExpiryDate(Integer validityInYears) {
        //Default is 2
        if (validityInYears == null) {
            validityInYears = 2;
        }
        Calendar expiry = Calendar.getInstance();
        expiry.add(Calendar.YEAR, validityInYears);
        return expiry;
    }

    /**
     * Given a private RSA key creates an object containing all relevant cipher parameters.
     *
     * @param rsaPrivKey a RSA private key
     * @return the cipher parameters
     * @throws CertificateCreationException if there is an error
     */
    private RSAPrivateCrtKeyParameters createIssuerCipherParams(RSAPrivateCrtKey rsaPrivKey)
            throws CertificateCreationException {
        RSAPrivateCrtKeyParameters cipherParams;
        try {
            cipherParams = new RSAPrivateCrtKeyParameters(rsaPrivKey.getModulus(), rsaPrivKey.getPublicExponent(),
                    rsaPrivKey.getPrivateExponent(), rsaPrivKey.getPrimeP(), rsaPrivKey.getPrimeQ(),
                    rsaPrivKey.getPrimeExponentP(), rsaPrivKey.getPrimeExponentQ(), rsaPrivKey.getCrtCoefficient());
        } catch (Exception ex) {
            logger.log(Level.SEVERE, "Could not get issuer cipher parameters: {0}",
                    new Object[] { ex.getMessage() });
            throw new CertificateCreationException("200 Could not get issuer cipher parameters");
        }
        return cipherParams;
    }

    private void loadKeyStore(String keyStorePath, String keyStorePass) throws KeyStoreException,
            FileNotFoundException, IOException, NoSuchAlgorithmException, CertificateException {
        //Load keystore with private key for the manager
        caKs = KeyStore.getInstance(System.getProperty("javax.net.ssl.keyStoreType", "jks"));

        File file = new File(keyStorePath);
        InputStream in = new FileInputStream(file);

        caKs.load(in, keyStorePass.toCharArray());
    }

    private X509Certificate readIssuerCertificate(String issuerId) throws KeyStoreException {

        // retrieve certificate of CA / issuer component.
        return (X509Certificate) caKs.getCertificate(issuerId);
    }

    private RSAPrivateCrtKey readPrivateKey(String issuerId, String privateKeyPass)
            throws KeyStoreException, NoSuchAlgorithmException, UnrecoverableKeyException {

        // load the private key entry of issuer from the keystore
        return (RSAPrivateCrtKey) caKs.getKey(issuerId, privateKeyPass.toCharArray());

    }

}