All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.cesecore.keys.util.KeyTools Maven / Gradle / Ivy

/*************************************************************************
 *                                                                       *
 *  CESeCore: CE Security Core                                           *
 *                                                                       *
 *  This software 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 any later version.                    *
 *                                                                       *
 *  See terms of license at gnu.org.                                     *
 *                                                                       *
 *************************************************************************/
package org.cesecore.keys.util;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.UnsupportedEncodingException;
import java.math.BigInteger;
import java.nio.charset.Charset;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.security.interfaces.DSAParams;
import java.security.interfaces.DSAPrivateKey;
import java.security.interfaces.DSAPublicKey;
import java.security.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.AlgorithmParameterSpec;
import java.security.spec.DSAParameterSpec;
import java.security.spec.ECFieldFp;
import java.security.spec.ECGenParameterSpec;
import java.security.spec.ECParameterSpec;
import java.security.spec.ECPoint;
import java.security.spec.EllipticCurve;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.RSAKeyGenParameterSpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;

import javax.crypto.interfaces.DHPrivateKey;
import javax.crypto.interfaces.DHPublicKey;

import org.apache.commons.lang.StringUtils;
import org.apache.log4j.Logger;
import org.bouncycastle.asn1.ASN1Exception;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.DERBMPString;
import org.bouncycastle.asn1.DERBitString;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.asn1.x509.SubjectKeyIdentifier;
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.cert.X509ExtensionUtils;
import org.bouncycastle.cert.bc.BcX509ExtensionUtils;
import org.bouncycastle.crypto.ec.CustomNamedCurves;
import org.bouncycastle.crypto.util.PublicKeyFactory;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jcajce.provider.asymmetric.util.EC5Util;
import org.bouncycastle.jcajce.provider.asymmetric.util.ECUtil;
import org.bouncycastle.jce.ECGOST3410NamedCurveTable;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.interfaces.PKCS12BagAttributeCarrier;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.provider.JCEECPublicKey;
import org.bouncycastle.jce.spec.ECNamedCurveSpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECCurve;
import org.bouncycastle.openssl.jcajce.JcaPEMWriter;
import org.bouncycastle.pkcs.jcajce.JcaPKCS10CertificationRequest;
import org.bouncycastle.util.encoders.DecoderException;
import org.bouncycastle.util.encoders.Hex;
import org.cesecore.certificates.util.AlgorithmConstants;
import org.cesecore.certificates.util.AlgorithmTools;
import org.cesecore.config.CesecoreConfiguration;
import org.cesecore.internal.InternalResources;
import org.cesecore.util.Base64;
import org.cesecore.util.CertTools;
import org.cesecore.util.CryptoProviderTools;
import org.ejbca.cvc.PublicKeyEC;

/**
 * Tools to handle common key and keystore operations.
 * 
 * @version $Id: KeyTools.java 31098 2019-01-10 11:09:32Z samuellb $
 */
public final class KeyTools {
    private static final Logger log = Logger.getLogger(KeyTools.class);
    private static final InternalResources intres = InternalResources.getInstance();

    private static final byte[] BAG_ATTRIBUTES = "Bag Attributes\n".getBytes();
    private static final byte[] FRIENDLY_NAME = "    friendlyName: ".getBytes();
    private static final byte[] SUBJECT_ATTRIBUTE = "subject=/".getBytes();
    private static final byte[] ISSUER_ATTRIBUTE = "issuer=/".getBytes();
    private static final byte[] BEGIN_CERTIFICATE = "-----BEGIN CERTIFICATE-----".getBytes();
    private static final byte[] END_CERTIFICATE = "-----END CERTIFICATE-----".getBytes();
    private static final byte[] BEGIN_PRIVATE_KEY = "-----BEGIN PRIVATE KEY-----".getBytes();
    private static final byte[] END_PRIVATE_KEY = "-----END PRIVATE KEY-----".getBytes();
    private static final byte[] NL = "\n".getBytes();

    /**
     * Prevent from creating new KeyTools object
     */
    private KeyTools() {
        // should never be called
    }

    /**
     * Generates a keypair
     * 
     * @param keySpec
     *            string specification of keys to generate, typical value is 2048 for RSA keys,
     *            1024 for DSA keys, secp256r1 for ECDSA keys, or null if algspec is to be used.
     * @param algSpec
     *            AlgorithmParameterSpec of keys to generate, typically an EXParameterSpec for EC keys, or null if keySpec is to be used.
     * @param keyAlg
     *            algorithm of keys to generate, typical value is RSA, DSA or ECDSA, see AlgorithmConstants.KEYALGORITHM_XX
     * 
     * @see org.cesecore.certificates.util.AlgorithmConstants
     * @see org.bouncycastle.asn1.x9.X962NamedCurves
     * @see org.bouncycastle.asn1.nist.NISTNamedCurves
     * @see org.bouncycastle.asn1.sec.SECNamedCurves
     * 
     * @return KeyPair the generated keypair
     * @throws InvalidAlgorithmParameterException  if the given parameters are inappropriate for this key pair generator.
     * @see org.cesecore.certificates.util.AlgorithmConstants#KEYALGORITHM_RSA
     */
    public static KeyPair genKeys(final String keySpec, final AlgorithmParameterSpec algSpec, final String keyAlg) throws InvalidAlgorithmParameterException {
        if (log.isTraceEnabled()) {
            log.trace(">genKeys(" + keySpec + ", " + keyAlg + ")");
        }

        final KeyPairGenerator keygen;
        try {
            keygen = KeyPairGenerator.getInstance(keyAlg, BouncyCastleProvider.PROVIDER_NAME);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException("Algorithm " + keyAlg + " was not recognized.", e);
        } catch (NoSuchProviderException e) {
            throw new IllegalStateException("BouncyCastle was not found as a provider.", e);
        }
        if (StringUtils.equals(keyAlg, AlgorithmConstants.KEYALGORITHM_ECDSA) || StringUtils.equals(keyAlg, AlgorithmConstants.KEYALGORITHM_EC)) {
            if ((keySpec != null) && !StringUtils.equals(keySpec, "implicitlyCA")) {
                log.debug("Generating named curve ECDSA key pair: " + keySpec);
                // Check if we have an OID for this named curve
                if (ECUtil.getNamedCurveOid(keySpec) != null) {
                    ECGenParameterSpec bcSpec = new ECGenParameterSpec(keySpec);
                    keygen.initialize(bcSpec, new SecureRandom());                    
                } else {
                    if (log.isDebugEnabled()) {
                        log.debug("Curve did not have an OID in BC, trying to pick up Parameter spec: " + keySpec);
                    }
                    // This may be a new curve without OID, like curve25519 and we have to do something a bit different
                    X9ECParameters ecP = CustomNamedCurves.getByName(keySpec);
                    if (ecP == null) {
                        throw new InvalidAlgorithmParameterException("Can not generate EC curve, no OID and no ECParameters found: "+keySpec);
                    }
                    org.bouncycastle.jce.spec.ECParameterSpec ecSpec = new org.bouncycastle.jce.spec.ECParameterSpec(
                            ecP.getCurve(), ecP.getG(), ecP.getN(), ecP.getH(), ecP.getSeed()); 
                    keygen.initialize(ecSpec, new SecureRandom());                    
                }
                // The old code should work in BC v1.50b6 and later, but in versions prior to that the below produces a key with explicit parameter encoding instead of named curves.
                // There is a test for this in KeyToolsTest.testGenKeysECDSAx9
                //                ecSpec = ECNamedCurveTable.getParameterSpec(keySpec);
                //                if (ecSpec == null) {
                //                    throw new InvalidAlgorithmParameterException("keySpec " + keySpec + " is invalid for ECDSA.");
                //                }
                //                keygen.initialize(ecSpec, new SecureRandom());
            } else if (algSpec != null) {
                log.debug("Generating ECDSA key pair from AlgorithmParameterSpec: " + algSpec);
                keygen.initialize(algSpec, new SecureRandom());
            } else if (StringUtils.equals(keySpec, "implicitlyCA")) {
                log.debug("Generating implicitlyCA encoded ECDSA key pair");
                // If the keySpec is null, we have "implicitlyCA" defined EC parameters
                // The parameters were already installed when we installed the provider
                // We just make sure that ecSpec == null here
                keygen.initialize(null, new SecureRandom());
            } else {
                throw new InvalidAlgorithmParameterException("No keySpec no algSpec and no implicitlyCA specified");
            }
        } else if(keyAlg.equals(AlgorithmConstants.KEYALGORITHM_ECGOST3410)) {
            final AlgorithmParameterSpec ecSpec;
            if(keySpec != null) {
                log.debug("Generating keys from given key specifications : " + keySpec);
                ecSpec = ECGOST3410NamedCurveTable.getParameterSpec(keySpec);
                if(ecSpec == null) throw new InvalidAlgorithmParameterException(
                        "Key specification " + keySpec + " is invalid for ECGOST3410");
            } else if(algSpec != null) {
                log.debug("Generating keys from given algorithm parameters : " + algSpec);
                ecSpec = algSpec;
            } else {
                throw new InvalidAlgorithmParameterException("No key or algorithm specifications");
            }
            keygen.initialize(ecSpec, new SecureRandom());
        } else if(keyAlg.equals(AlgorithmConstants.KEYALGORITHM_DSTU4145)) {
            final AlgorithmParameterSpec ecSpec;
            if(keySpec != null) {
                log.debug("Generating keys from given key specifications : " + keySpec);
                ecSpec = dstuOidToAlgoParams(keySpec);
                if(ecSpec == null) throw new InvalidAlgorithmParameterException(
                        "Key specification " + keySpec + " is invalid for DSTU4145");
            } else if(algSpec != null) {
                log.debug("Generating keys from given algorithm parameters : " + algSpec);
                ecSpec = algSpec;
            } else {
                throw new InvalidAlgorithmParameterException("No key or algorithm specifications");
            }
            keygen.initialize(ecSpec, new SecureRandom());
        } else if (keySpec.startsWith("DSA")) {
            // DSA key with "DSA" in keyspec
            final int keysize = Integer.parseInt(keySpec.substring(3));
            keygen.initialize(keysize);
        } else {
            // RSA or DSA key where keyspec is simply the key length
            final int keysize = Integer.parseInt(keySpec);
            keygen.initialize(keysize);
        }

        final KeyPair keys = keygen.generateKeyPair();

        if (log.isDebugEnabled()) {
            final PublicKey pk = keys.getPublic();
            final int len = getKeyLength(pk);
            log.debug("Generated " + keys.getPublic().getAlgorithm() + " keys with length " + len);
        }
        log.trace("= 0. 0 usually means the length can not be calculated, for example if the key is an EC
     *         key and the "implicitlyCA" encoding is used.
     */
    public static int getKeyLength(final PublicKey pk) {
        if (pk instanceof RSAPublicKey) {
            final RSAPublicKey rsapub = (RSAPublicKey) pk;
            return rsapub.getModulus().bitLength();
        }
        if (pk instanceof JCEECPublicKey) {
            final JCEECPublicKey ecpriv = (JCEECPublicKey) pk;
            final org.bouncycastle.jce.spec.ECParameterSpec spec = ecpriv.getParameters();
            if (spec != null) {
                return spec.getN().bitLength();
            }
            // We support the key, but we don't know the key length
            return 0;
        }
        if (pk instanceof BCECPublicKey) {
            final BCECPublicKey ecpriv = (BCECPublicKey) pk;
            final org.bouncycastle.jce.spec.ECParameterSpec spec = ecpriv.getParameters();
            if (spec != null) {
                return spec.getN().bitLength();
            }
            // We support the key, but we don't know the key length
            return 0;
        }
        if (pk instanceof ECPublicKey) {
            final ECPublicKey ecpriv = (ECPublicKey) pk;
            final java.security.spec.ECParameterSpec spec = ecpriv.getParams();
            if (spec != null) {
                return spec.getOrder().bitLength(); // does this really return something we expect?
            }
            // We support the key, but we don't know the key length
            return 0;
        }
        if (pk instanceof DSAPublicKey) {
            final DSAPublicKey dsapub = (DSAPublicKey) pk;
            if (dsapub.getParams() != null) {
                return dsapub.getParams().getP().bitLength();
            }
            return dsapub.getY().bitLength();
        }
        // Unknown key type
        return -1;
    }

    /**
     * Gets the key AlgorithmParameterSpec of supported keys. Can be used to initialize a KeyPairGenerator to generate a key of equal type and size.
     * 
     * @param pk
     *            PublicKey used to derive the AlgorithmParameterSpec
     * @return null if key is unsupported or pk is null, otherwise a AlgorithmParameterSpec.
     */
    public static AlgorithmParameterSpec getKeyGenSpec(final PublicKey pk) {
        if (pk == null) {
            return null;
        }
        if (pk instanceof RSAPublicKey) {
            log.debug("getKeyGenSpec: RSA");
            final RSAPublicKey rpk = (RSAPublicKey) pk;
            return new RSAKeyGenParameterSpec(getKeyLength(pk), rpk.getPublicExponent());
        }
        if (pk instanceof DSAPublicKey) {
            log.debug("getKeyGenSpec: DSA");
            final DSAPublicKey dpk = (DSAPublicKey) pk;
            final DSAParams params = dpk.getParams();
            return new DSAParameterSpec(params.getP(), params.getQ(), params.getG());
        }
        if (pk instanceof ECPublicKey) {
            log.debug("getKeyGenSpec: ECPublicKey");
            final ECPublicKey ecpub = (ECPublicKey) pk;
            final java.security.spec.ECParameterSpec sunsp = ecpub.getParams();
            final EllipticCurve ecurve = new EllipticCurve(sunsp.getCurve().getField(), sunsp.getCurve().getA(), sunsp.getCurve().getB());
            // ECParameterSpec par = new ECNamedCurveSpec(null, sunsp.getCurve(), sunsp.getGenerator(), sunsp.getOrder(),
            // BigInteger.valueOf(sunsp.getCofactor()));
            final ECParameterSpec params = new ECParameterSpec(ecurve, sunsp.getGenerator(), sunsp.getOrder(), sunsp.getCofactor());
            if (log.isDebugEnabled()) {
                log.debug("Fieldsize: " + params.getCurve().getField().getFieldSize());
                final EllipticCurve curve = params.getCurve();
                log.debug("CurveA: " + curve.getA().toString(16));
                log.debug("CurveB: " + curve.getB().toString(16));
                log.debug("CurveSeed: " + curve.getSeed());
                final ECFieldFp field = (ECFieldFp) curve.getField();
                log.debug("CurveSfield: " + field.getP().toString(16));
                final ECPoint p = params.getGenerator();
                log.debug("Generator: " + p.getAffineX().toString(16) + ", " + p.getAffineY().toString(16));
                log.debug("Order: " + params.getOrder().toString(16));
                log.debug("CoFactor: " + params.getCofactor());
            }
            return params;
        }
        if (pk instanceof JCEECPublicKey) {
            log.debug("getKeyGenSpec: JCEECPublicKey");
            final JCEECPublicKey ecpub = (JCEECPublicKey) pk;
            final org.bouncycastle.jce.spec.ECParameterSpec bcsp = ecpub.getParameters();
            final ECCurve curve = bcsp.getCurve();
            // TODO: this probably does not work for key generation with the Sun PKCS#11 provider. Maybe seed needs to be set to null as above? Or
            // something else, the BC curve is it the same?
            final ECParameterSpec params = new ECNamedCurveSpec(null, curve, bcsp.getG(), bcsp.getN(), bcsp.getH());
            return params;
            // EllipticCurve ecc = new EllipticCurve(curve.)
            // ECParameterSpec sp = new ECParameterSpec(, bcsp.getG(), bcsp.getN(), bcsp.getH().intValue());
        }
        return null;
    }

    /**
     * Creates PKCS12-file that can be imported in IE or Firefox. The alias for the private key is set to 'privateKey' and the private key password is
     * null.
     * 
     * @param alias
     *            the alias used for the key entry
     * @param privKey
     *            RSA private key
     * @param cert
     *            user certificate
     * @param cacert
     *            CA-certificate or null if only one cert in chain, in that case use 'cert'.
     * 
     * @return KeyStore containing PKCS12-keystore
     * @throws CertificateException if the certificate couldn't be parsed
     * @throws CertificateEncodingException if the encoded bytestream of the certificate couldn't be retrieved
     * @throws NoSuchAlgorithmException if the algorithm defined in privKey couldn't be found
     * @throws InvalidKeySpecException if the key specification defined in privKey couldn't be found
     */
    public static KeyStore createP12(final String alias, final PrivateKey privKey, final Certificate cert, final Certificate cacert)
            throws CertificateException, NoSuchAlgorithmException, InvalidKeySpecException {
        final Certificate[] chain;

        if (cacert == null) {
            chain = null;
        } else {
            chain = new Certificate[1];
            chain[0] = cacert;
        }

        return createP12(alias, privKey, cert, chain);
    } // createP12

    /**
     * Creates PKCS12-file that can be imported in IE or Firefox. The alias for the private key is set to 'privateKey' and the private key password is
     * null.
     * 
     * @param alias
     *            the alias used for the key entry
     * @param privKey
     *            RSA private key
     * @param cert
     *            user certificate
     * @param cacerts
     *            Collection of X509Certificate, or null if only one cert in chain, in that case use 'cert'.
     * @return KeyStore containing PKCS12-keystore
     * @throws CertificateException if the certificate couldn't be parsed
     * @throws CertificateEncodingException if the encoded bytestream of the certificate couldn't be retrieved
     * @throws NoSuchAlgorithmException if the algorithm defined in privKey couldn't be found
     * @throws InvalidKeySpecException if the key specification defined in privKey couldn't be found
     */
    public static KeyStore createP12(final String alias, final PrivateKey privKey, final Certificate cert, final Collection cacerts)
            throws CertificateException, NoSuchAlgorithmException, InvalidKeySpecException {
        final Certificate[] chain;
        if (cacerts == null) {
            chain = null;
        } else {
            chain = cacerts.toArray(new Certificate[cacerts.size()]);
        }
        return createP12(alias, privKey, cert, chain);
    } // createP12

    /**
     * Creates PKCS12-file that can be imported in IE or Firefox. The alias for the private key is set to 'privateKey' and the private key password is
     * null.
     * 
     * @param alias
     *            the alias used for the key entry
     * @param privKey
     *            RSA private key
     * @param cert
     *            user certificate
     * @param cachain
     *            CA-certificate chain or null if only one cert in chain, in that case use 'cert'.
     * @return KeyStore containing PKCS12-keystore
     * @throws CertificateException if the certificate couldn't be parsed
     * @throws CertificateEncodingException if the encoded bytestream of the certificate couldn't be retrieved
     * @throws NoSuchAlgorithmException if the algorithm defined in privKey couldn't be found
     * @throws InvalidKeySpecException if the key specification defined in privKey couldn't be found

     */
    public static KeyStore createP12(final String alias, final PrivateKey privKey, final Certificate cert, final Certificate[] cachain)
            throws CertificateEncodingException, CertificateException, NoSuchAlgorithmException, InvalidKeySpecException {
        if (log.isTraceEnabled()) {
            log.trace(">createP12: alias=" + alias + ", privKey, cert=" + CertTools.getSubjectDN(cert) + ", cachain.length="
                    + ((cachain == null) ? 0 : cachain.length));
        }
        // Certificate chain
        if (cert == null) {
            throw new IllegalArgumentException("Parameter cert cannot be null.");
        }
        int len = 1;
        if (cachain != null) {
            len += cachain.length;
        }
        final Certificate[] chain = new Certificate[len];
        // To not get a ClassCastException we need to generate a real new certificate with BC
        final CertificateFactory cf = CertTools.getCertificateFactory();
        chain[0] = cf.generateCertificate(new ByteArrayInputStream(cert.getEncoded()));

        if (cachain != null) {
            for (int i = 0; i < cachain.length; i++) {
                final X509Certificate tmpcert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(cachain[i].getEncoded()));
                chain[i + 1] = tmpcert;
            }
        }
        if (chain.length > 1) {
            for (int i = 1; i < chain.length; i++) {
                final X509Certificate cacert = (X509Certificate) cf.generateCertificate(new ByteArrayInputStream(chain[i].getEncoded()));
                // Set attributes on CA-cert
                try {
                    final PKCS12BagAttributeCarrier caBagAttr = (PKCS12BagAttributeCarrier) chain[i];
                    // We construct a friendly name for the CA, and try with some parts from the DN if they exist.
                    String cafriendly = CertTools.getPartFromDN(CertTools.getSubjectDN(cacert), "CN");
                    // On the ones below we +i to make it unique, O might not be otherwise
                    if (cafriendly == null) {
                        cafriendly = CertTools.getPartFromDN(CertTools.getSubjectDN(cacert), "O");
                        if (cafriendly == null) {
                            cafriendly = CertTools.getPartFromDN(CertTools.getSubjectDN(cacert), "OU");
                            if (cafriendly == null) {
                                cafriendly = "CA_unknown" + i;
                            } else {
                                cafriendly = cafriendly +i;
                            }
                        } else {
                            cafriendly = cafriendly +i;
                        }
                    }
                    caBagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString(cafriendly));
                } catch (ClassCastException e) {
                    log.error("ClassCastException setting BagAttributes, can not set friendly name: ", e);
                }
            }
        }

        // Set attributes on user-cert
        try {
            final PKCS12BagAttributeCarrier certBagAttr = (PKCS12BagAttributeCarrier) chain[0];
            certBagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString(alias));
            // in this case we just set the local key id to that of the public key
            certBagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, createSubjectKeyId(chain[0].getPublicKey()));
        } catch (ClassCastException e) {
            log.error("ClassCastException setting BagAttributes, can not set friendly name: ", e);
        }
        try {
        // "Clean" private key, i.e. remove any old attributes
        final KeyFactory keyfact = KeyFactory.getInstance(privKey.getAlgorithm(), BouncyCastleProvider.PROVIDER_NAME);
        final PrivateKey pk = keyfact.generatePrivate(new PKCS8EncodedKeySpec(privKey.getEncoded()));
        // Set attributes for private key
            try {
                final PKCS12BagAttributeCarrier keyBagAttr = (PKCS12BagAttributeCarrier) pk;
                // in this case we just set the local key id to that of the public key
                keyBagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_friendlyName, new DERBMPString(alias));
                keyBagAttr.setBagAttribute(PKCSObjectIdentifiers.pkcs_9_at_localKeyId, createSubjectKeyId(chain[0].getPublicKey()));
            } catch (ClassCastException e) {
                log.error("ClassCastException setting BagAttributes, can not set friendly name: ", e);
            }
            // store the key and the certificate chain
            // BC PKCS12 uses 3DES for key protection and 40 bit RC2 for protecting the certificates
            final KeyStore store = KeyStore.getInstance("PKCS12", BouncyCastleProvider.PROVIDER_NAME);
            store.load(null, null);
            store.setKeyEntry(alias, pk, null, chain);
            if (log.isTraceEnabled()) {
                log.trace("createJKS: alias=" + alias + ", privKey, cert=" + CertTools.getSubjectDN(cert) + ", cachain.length="
                    + ((cachain == null) ? 0 : cachain.length));
        }
        final String caAlias = "cacert";

        // Certificate chain
        if (cert == null) {
            throw new IllegalArgumentException("Parameter cert cannot be null.");
        }
        int len = 1;
        if (cachain != null) {
            len += cachain.length;
        }
        final Certificate[] chain = new Certificate[len];
        chain[0] = cert;
        if (cachain != null) {
            System.arraycopy(cachain, 0, chain, 1, cachain.length);
        }

        // store the key and the certificate chain
        final KeyStore store;
        try {
            store = KeyStore.getInstance("JKS");
        } catch (KeyStoreException e) {
            throw new IllegalStateException("No JKS implementation found in provider", e);
        }
        try {
            store.load(null, null);
        } catch (NoSuchAlgorithmException e) {
            throw new IllegalStateException(e);
        } catch (CertificateException e) {
            throw new IllegalStateException(e);
        } catch (IOException e) {
            throw new IllegalStateException(e);
        }

        // First load the key entry
        final X509Certificate[] usercert = new X509Certificate[1];
        usercert[0] = cert;
        try {
            store.setKeyEntry(alias, privKey, password.toCharArray(), usercert);
        } catch (KeyStoreException e) {
            throw new IllegalStateException("Keystore apparently hasn't been loaded?", e);
 
        }

        // Add the root cert as trusted
        if (cachain != null) {
            if (!CertTools.isSelfSigned(cachain[cachain.length - 1])) {
                throw new IllegalArgumentException("Root cert is not self-signed.");
            }
            store.setCertificateEntry(caAlias, cachain[cachain.length - 1]);
        }

        // Set the complete chain
        log.debug("Storing cert chain of length " + chain.length);
        store.setKeyEntry(alias, privKey, password.toCharArray(), chain);
        if (log.isTraceEnabled()) {
            log.trace(" e = ks.aliases();
        Object o = null;
        String alias = "";
        PrivateKey serverPrivKey = null;
        while (e.hasMoreElements()) {
            o = e.nextElement();
            if (o instanceof String) {
                if ((ks.isKeyEntry((String) o)) && ((serverPrivKey = (PrivateKey) ks.getKey((String) o, password)) != null)) {
                    alias = (String) o;
                    break;
                }
            }
        }

        final byte[] privKeyEncoded = serverPrivKey!=null ? serverPrivKey.getEncoded() : "".getBytes();

        final Certificate[] chain = KeyTools.getCertChain(ks, (String) o);
        final X509Certificate userX509Certificate = (X509Certificate) chain[0];
        {
            final byte[] output = userX509Certificate.getEncoded();
            final String sn = CertTools.getSubjectDN(userX509Certificate);

            final String subjectdnpem = sn.replace(',', '/');
            final String issuerdnpem = CertTools.getIssuerDN(userX509Certificate).replace(',', '/');

            buffer.write(BAG_ATTRIBUTES);
            buffer.write(FRIENDLY_NAME);
            buffer.write(alias.getBytes());
            buffer.write(NL);
            buffer.write(BEGIN_PRIVATE_KEY);
            buffer.write(NL);

            final byte[] privKey = Base64.encode(privKeyEncoded);
            buffer.write(privKey);
            buffer.write(NL);
            buffer.write(END_PRIVATE_KEY);
            buffer.write(NL);
            buffer.write(BAG_ATTRIBUTES);
            buffer.write(FRIENDLY_NAME);
            buffer.write(alias.getBytes());
            buffer.write(NL);
            buffer.write(SUBJECT_ATTRIBUTE);
            buffer.write(subjectdnpem.getBytes());
            buffer.write(NL);
            buffer.write(ISSUER_ATTRIBUTE);
            buffer.write(issuerdnpem.getBytes());
            buffer.write(NL);
            buffer.write(BEGIN_CERTIFICATE);
            buffer.write(NL);

            final byte[] userCertB64 = Base64.encode(output);
            buffer.write(userCertB64);
            buffer.write(NL);
            buffer.write(END_CERTIFICATE);
            buffer.write(NL);
        }
        if (!CertTools.isSelfSigned(userX509Certificate)) {
            for (int num = 1; num < chain.length; num++) {
                final X509Certificate tmpX509Cert = (X509Certificate) chain[num];
                final String sn = CertTools.getSubjectDN(tmpX509Cert);

                final String cnTmp = CertTools.getPartFromDN(sn, "CN");
                final String cn = StringUtils.isEmpty(cnTmp) ? cnTmp : "Unknown";

                final String subjectdnpem = sn.replace(',', '/');
                final String issuerdnpem = CertTools.getIssuerDN(tmpX509Cert).replace(',', '/');

                buffer.write(BAG_ATTRIBUTES);
                buffer.write(FRIENDLY_NAME);
                buffer.write(cn.getBytes());
                buffer.write(NL);
                buffer.write(SUBJECT_ATTRIBUTE);
                buffer.write(subjectdnpem.getBytes());
                buffer.write(NL);
                buffer.write(ISSUER_ATTRIBUTE);
                buffer.write(issuerdnpem.getBytes());
                buffer.write(NL);

                final byte[] tmpOutput = tmpX509Cert.getEncoded();
                buffer.write(BEGIN_CERTIFICATE);
                buffer.write(NL);

                final byte[] tmpCACertB64 = Base64.encode(tmpOutput);
                buffer.write(tmpCACertB64);
                buffer.write(NL);
                buffer.write(END_CERTIFICATE);
                buffer.write(NL);
            }
        }
        return buffer.toByteArray();
    }

    /** @return a buffer with the public key in PEM format */
    public static String getAsPem(final PublicKey publicKey) throws IOException {
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
        try ( final JcaPEMWriter pemWriter = new JcaPEMWriter(new OutputStreamWriter(baos)) ) {
            pemWriter.writeObject(publicKey);
        }
        return new String(baos.toByteArray(), "UTF8");
    }

    /**
     * Retrieves the certificate chain from a keystore.
     * 
     * @param keyStore
     *            the keystore, which has been loaded and opened.
     * @param privateKeyAlias
     *            the alias of the privatekey for which the certchain belongs.
     * 
     * @return array of Certificate, or null if no certificates are found.
     */
    public static Certificate[] getCertChain(final KeyStore keyStore, final String privateKeyAlias) throws KeyStoreException {
        if (log.isTraceEnabled()) {
            log.trace(">getCertChain: alias='" + privateKeyAlias + "'");
        }
        final Certificate[] certchain = keyStore.getCertificateChain(privateKeyAlias);
        if (certchain == null) {
            return null;
        }
        log.debug("Certchain retrieved from alias '" + privateKeyAlias + "' has length " + certchain.length);

        if (certchain.length < 1) {
            log.error("Cannot load certificate chain with alias '" + privateKeyAlias + "' from keystore.");
            if (log.isTraceEnabled()) {
                log.trace(" availableSignAlgorithms = AlgorithmTools.getSignatureAlgorithms(pub);
                SignWithWorkingAlgorithm.doSignTask(availableSignAlgorithms, getProvider(sProvider), operation);
                signBV = operation.getSignature();
                testSigAlg = operation.getSignatureAlgorithm();
                if (signBV == null) {
                    throw new InvalidKeyException("Result from signing is null.");
                }
                if (log.isDebugEnabled()) {
                    log.trace("Created signature of size: " + signBV.length);
                    log.trace("Created signature: " + new String(Hex.encode(signBV)));
                }
            }
            {
                final Signature signature;
                try {
                    signature = Signature.getInstance(testSigAlg, "BC");
                } catch (NoSuchProviderException | NoSuchAlgorithmException e) {
                    throw new IllegalStateException("BouncyCastle was not found as a provider.", e);
                }
                signature.initVerify(pub);
                signature.update(input);
                if (!signature.verify(signBV)) {
                    throw new InvalidKeyException("Signature was not correctly verified.");
                }
            }
        } catch ( InvalidKeyException e ) {
            throw e;
        } catch (  TaskWithSigningException | SignatureException e ) {
            throw new InvalidKeyException(String.format("Exception testing key: %s", e.getMessage()), e);
        }
    }

    /**
     * Print parameters of public part of a key.
     * 
     * @param publK
     *            the key
     * @param ps
     *            stream to print to.
     */
    public static void printPublicKeyInfo(final PublicKey publK, final PrintStream ps) {
        if (publK instanceof RSAPublicKey) {
            ps.println("RSA key:");
            final RSAPublicKey rsa = (RSAPublicKey) publK;
            ps.println("  modulus: " + rsa.getModulus().toString(16));
            ps.println("  public exponent: " + rsa.getPublicExponent().toString(16));
            return;
        }
        if (publK instanceof ECPublicKey) {
            ps.println("Elliptic curve key:");
            final ECPublicKey ec = (ECPublicKey) publK;
            ps.println("  Named curve: "+AlgorithmTools.getKeySpecification(ec));
            ps.println("  the affine x-coordinate: " + ec.getW().getAffineX().toString(16));
            ps.println("  the affine y-coordinate: " + ec.getW().getAffineY().toString(16));
            return;
        }
        if (publK instanceof DHPublicKey) {
            ps.println("DH key:");
            final DHPublicKey dh = (DHPublicKey) publK;
            ps.println("  the public value y: " + dh.getY().toString(16));
            return;
        }
        if (publK instanceof DSAPublicKey) {
            ps.println("DSA key:");
            final DSAPublicKey dsa = (DSAPublicKey) publK;
            ps.println("  the public value y: " + dsa.getY().toString(16));
            return;
        }
    }

    /**
     * Test if a private key is extractable (could be stored).
     * 
     * @param privK
     *            key to test.
     * @return true if the key is extractable.
     */
    public static boolean isPrivateKeyExtractable(final PrivateKey privK) {
        if (privK instanceof RSAPrivateKey) {
            final RSAPrivateKey rsa = (RSAPrivateKey) privK;
            final BigInteger result = rsa.getPrivateExponent();
            return result != null && result.bitLength() > 0;
        }
        if (privK instanceof ECPrivateKey) {
            final ECPrivateKey ec = (ECPrivateKey) privK;
            final BigInteger result = ec.getS();
            return result != null && result.bitLength() > 0;
        }
        if (privK instanceof DHPrivateKey) {
            final DHPrivateKey dh = (DHPrivateKey) privK;
            final BigInteger result = dh.getX();
            return result != null && result.bitLength() > 0;
        }
        if (privK instanceof DSAPrivateKey) {
            final DSAPrivateKey dsa = (DSAPrivateKey) privK;
            final BigInteger result = dsa.getX();
            return result != null && result.bitLength() > 0;
        }
        return false;
    }

    public static void checkValidKeyLength(String keyspec) throws InvalidKeyException, InvalidAlgorithmParameterException {
        final String keyAlg = keyspecToKeyalg(keyspec);
        final int len;
        if (keyAlg.equals(AlgorithmConstants.KEYALGORITHM_RSA)) {
            len = Integer.parseInt(keyspec); 
        } else if (keyAlg.equals(AlgorithmConstants.KEYALGORITHM_DSA)) {
            len = Integer.parseInt(keyspec.substring(3));
        } else {
            // Assume it's elliptic curve
            final KeyPair kp = KeyTools.genKeys(keyspec, keyAlg);
            len = KeyTools.getKeyLength(kp.getPublic());
        }
        checkValidKeyLength(keyAlg, len);
    }

    public static void checkValidKeyLength(final PublicKey pk) throws InvalidKeyException {
        final String keyAlg = AlgorithmTools.getKeyAlgorithm(pk);
        final int len = KeyTools.getKeyLength(pk);
        checkValidKeyLength(keyAlg, len);
    }

    public static void checkValidKeyLength(final String keyAlg, final int len) throws InvalidKeyException {
        final boolean isEcdsa = AlgorithmConstants.KEYALGORITHM_ECDSA.equals(keyAlg);
        final boolean isGost3410 = AlgorithmTools.isGost3410Enabled() && AlgorithmConstants.KEYALGORITHM_ECGOST3410.equals(keyAlg);
        final boolean isDstu4145 = AlgorithmTools.isDstu4145Enabled() && keyAlg.startsWith(CesecoreConfiguration.getOidDstu4145()+".");
        if (isEcdsa || isGost3410 || isDstu4145) {
            // We allow key lengths of 0, because that means that implicitlyCA is used. 
            // for ImplicitlyCA we have no idea what the key length is, on the other hand only real professionals
            // will ever use that to we will allow it.
            if ((len > 0) && (len < 224)) {
                final String msg = intres.getLocalizedMessage("catoken.invalidkeylength", "ECDSA", "224", Integer.valueOf(len));
                throw new InvalidKeyException(msg);
            }                            
        } else if (AlgorithmConstants.KEYALGORITHM_RSA.equals(keyAlg) || AlgorithmConstants.KEYALGORITHM_DSA.equals(keyAlg)) {
            if (len < 1024) {
                final String msg = intres.getLocalizedMessage("catoken.invalidkeylength", "RSA/DSA", "1024", Integer.valueOf(len));
                throw new InvalidKeyException(msg);
            }
        }
    }
    
    /**
     * Gets the parameter spec from a given OID of a DSTU curve (they don't have names) 
     */
    public static AlgorithmParameterSpec dstuOidToAlgoParams(String dstuOid) {
        return new ECGenParameterSpec(dstuOid);
    }
    
    public static String keyspecToKeyalg(String keyspec) {
        if (StringUtils.isNumeric(keyspec)) {
            return AlgorithmConstants.KEYALGORITHM_RSA;
        }
        if (keyspec.startsWith(AlgorithmConstants.KEYALGORITHM_RSA)) {
            return AlgorithmConstants.KEYALGORITHM_RSA;
        }
        if (keyspec.startsWith(AlgorithmConstants.KEYALGORITHM_DSA)) {
            return AlgorithmConstants.KEYALGORITHM_DSA;
        }
        if (AlgorithmTools.isGost3410Enabled() && keyspec.startsWith(AlgorithmConstants.KEYSPECPREFIX_ECGOST3410)) {
            return AlgorithmConstants.KEYALGORITHM_ECGOST3410;
        }
        if (AlgorithmTools.isDstu4145Enabled() && keyspec.startsWith(CesecoreConfiguration.getOidDstu4145()+".")) {
            return AlgorithmConstants.KEYALGORITHM_DSTU4145;
        }
        return AlgorithmConstants.KEYALGORITHM_ECDSA;
    }
    
    /**
     * Converts a standalone specspec that starts with the keyalg to a short keyspec which
     * is to be used together with a separate "keyalg" value.
     */
    public static String shortenKeySpec(String keyspec) {
        if (keyspec.startsWith(AlgorithmConstants.KEYALGORITHM_DSA) || keyspec.startsWith(AlgorithmConstants.KEYALGORITHM_RSA) ) {
            return keyspec.substring(3);
        }
        return keyspec;
    }
    
    /**
     * Converts a keyalg/keyspec pair into a standalone specspec.
     */
    public static String keyalgspecToKeyspec(String keyalg, String keyspec) {
        if ("DSA".equals(keyalg)) {
            return "DSA" + keyspec;
        }
        return keyspec;
    }

    /** 
     * Get the ASN.1 encoded PublicKey as a Java PublicKey Object.
     * @param asn1EncodedPublicKey the ASN.1 encoded PublicKey
     * @return the ASN.1 encoded PublicKey as a Java Object
     */
    public static PublicKey getPublicKeyFromBytes(byte[] asn1EncodedPublicKey) {
        try {
            final SubjectPublicKeyInfo keyInfo;
            keyInfo = SubjectPublicKeyInfo.getInstance(asn1EncodedPublicKey);
            final AlgorithmIdentifier keyAlg = keyInfo.getAlgorithm();
            final X509EncodedKeySpec xKeySpec = new X509EncodedKeySpec(new DERBitString(keyInfo).getBytes());
            final KeyFactory keyFact = KeyFactory.getInstance(keyAlg.getAlgorithm().getId(), BouncyCastleProvider.PROVIDER_NAME);
            return keyFact.generatePublic(xKeySpec);
        } catch (IOException | NoSuchAlgorithmException | NoSuchProviderException | InvalidKeySpecException e) {
            log.debug("Unable to decode PublicKey.", e);
        }
        return null;
    }
    
    /**
     * Extracts the binary data from a PEM of a specified kind, e.g. public key.
     *  
     * @param pem PEM data to extract from. May contain other types of data as well.
     * @param beginMarker E.g. CertTools.BEGIN_PUBLIC_KEY
     * @param endMarker E.g. CertTools.END_PUBLIC_KEY
     * @return The first entry of the matching type, or null if it couldn't be parsed.
     */
    public static byte[] getBytesFromPEM(String pem, String beginMarker, String endMarker) {
        final int start = pem.indexOf(beginMarker);
        final int end = pem.indexOf(endMarker, start);
        if (start == -1 || end == -1) {
            log.debug("Could not find "+beginMarker+" and "+endMarker+" lines in PEM");
            return null;
        }
        
        final String base64 = pem.substring(start + beginMarker.length(), end);
        try {
            return Base64.decode(base64.getBytes("ASCII"));
        } catch (UnsupportedEncodingException e) {
            log.debug(String.format("Invalid byte in PEM data: %s", e.getMessage()));
            return null;
        }
    }
    
    /**
     * Extracts the binary DER data from a public key file. The file may be either in PEM format
     * or in DER format. In the latter case, the file contents is returned as-is.
     *  
     * @param file Data of a PEM or DER file.
     * @return DER encoded public key.
     * @throws CertificateParsingException If the data isn't a public key in either PEM or DER format.
     */
    public static byte[] getBytesFromPublicKeyFile(final byte[] file) throws CertificateParsingException {
        if (file.length == 0) {
            throw new CertificateParsingException("Public key file is empty");
        }
        final String fileText = Charset.forName("ASCII").decode(java.nio.ByteBuffer.wrap(file)).toString();
        final byte[] asn1bytes;
        {
            final byte[] tmpBytes = getBytesFromPEM(fileText, CertTools.BEGIN_PUBLIC_KEY, CertTools.END_PUBLIC_KEY);
            asn1bytes = tmpBytes!=null ? tmpBytes : file; // Assume it's in ASN1 format already if null
        }
        try {
            PublicKeyFactory.createKey(asn1bytes); // Check that it's a valid public key
            return asn1bytes;
        } catch (IOException | IllegalArgumentException e) {
            throw new CertificateParsingException("File is neither a valid PEM nor DER file.", e);
        }
    }

    /** Like {@link getBytesFromPublicKeyFile}, but allows for missing ----BEGIN... and END lines. */
    public static byte[] getBytesFromCtLogKey(final byte[] file) throws CertificateParsingException {
        try {
            return getBytesFromPublicKeyFile(file);
        } catch (CertificateParsingException originalException) {
            log.debug("Could not parse key as PEM or DER, trying as raw base64.", originalException);
            final byte[] decoded;
            try {
                decoded = Base64.decode(file);
            } catch (DecoderException ignored) {
                log.debug("Public key file is not valid base64");
                throw new CertificateParsingException("Public key could not be parsed as either PEM, DER or base64.", originalException);
            }
            if (decoded == null || decoded.length == 0) {
                log.debug("Decoded base64 data of public key is empty or null");
                throw originalException;
            }
            try {
                PublicKeyFactory.createKey(decoded); // Check that it's a valid public key
                return decoded;
            } catch (IOException | IllegalArgumentException e) {
                final String msg = "The base64 encoded data does not represent a public key.";
                log.debug(msg);
                throw new CertificateParsingException(msg, e);
            }
        }
    }

    /**
     * Returns the modulus of the public key.
     * @param publicKey public key
     * @return modulus of the public key
     */
    public static String getKeyModulus(final PublicKey publicKey) {
        String modulus = null;
        if ( publicKey instanceof RSAPublicKey ) {
            byte[] modulusBytes = ((RSAPublicKey) publicKey).getModulus().toByteArray();
            modulus = new String(Hex.encode(modulusBytes));
        } else if(publicKey instanceof DSAPublicKey) {
            byte[] modulusBytes = ((DSAPublicKey)publicKey).getY().toByteArray();
            modulus = new String(Hex.encode(modulusBytes));
        } else if(publicKey instanceof ECPublicKey) {
            byte[] modulusBytesX = ((ECPublicKey)publicKey).getW().getAffineX().toByteArray();
            byte[] modulusBytesY = ((ECPublicKey)publicKey).getW().getAffineY().toByteArray();
            modulus = new String(Hex.encode(modulusBytesX)).concat(new String(Hex.encode(modulusBytesY)));
        }
        return modulus;
    }
    
    /**
     * Returns the exponent of the public key.
     * @param publicKey public key
     * @return modulus of the public key
     */
    public static String getKeyPublicExponent(final PublicKey publicKey) {
        String exponent = null;
        if ( publicKey instanceof RSAPublicKey ) {
            exponent = ((RSAPublicKey) publicKey).getPublicExponent().toString();
        }
        return exponent;
    }
    
    /**
     * Generates the SHA256 fingerprint of the given text string.
     * @param text input on what to generate the fingerprint
     * @return SHA256 fingerprint of given input string 
     */
    public static String getSha256Fingerprint(String text) {
        byte[] sha256Fingerprint = CertTools.generateSHA256Fingerprint(text.getBytes());
        return new String(Hex.encode(sha256Fingerprint));
    }
    
    /**
     * Returns the signature of the given JcaPKCS10CertificationRequest.
     * @param certificationRequest BouncyCastle JcaPKCS10CertificationRequest certification request
     * @return signature of given certification request
     */
    public static String getCertificateRequestSignature(JcaPKCS10CertificationRequest certificationRequest) {
        return new String(Hex.encode(certificationRequest.getSignature()));
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy