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

io.gravitee.common.util.KeyStoreUtils Maven / Gradle / Ivy

There is a newer version: 4.6.0
Show newest version
/**
 * Copyright (C) 2015 The Gravitee team (http://gravitee.io)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.gravitee.common.util;

import static java.util.Arrays.asList;

import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringReader;
import java.math.BigInteger;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.SecureRandom;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Base64;
import java.util.Collection;
import java.util.Date;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.x500.RDN;
import org.bouncycastle.asn1.x500.X500Name;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.asn1.x500.style.IETFUtils;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openssl.PEMKeyPair;
import org.bouncycastle.openssl.PEMParser;
import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.util.io.pem.PemReader;

/**
 * @author Jeoffrey HAEYAERT (jeoffrey.haeyaert at graviteesource.com)
 * @author GraviteeSource Team
 */
public class KeyStoreUtils {

    public static final String TYPE_JKS = "JKS";
    public static final String TYPE_PEM = "PEM";
    public static final String TYPE_PKCS12 = "PKCS12";

    public static final String DEFAULT_ALIAS = "dummy-entry";
    public static final String DEFAULT_KEYSTORE_TYPE = TYPE_PKCS12;
    private static final int DNSNAME = 2;
    private static final Date DEFAULT_NOT_BEFORE = new Date(System.currentTimeMillis() - 31536000000L);
    private static final Date DEFAULT_NOT_AFTER = new Date(253402300799000L);
    private static final int DEFAULT_KEY_LENGTH_BITS = 2048;
    private static final String DEFAULT_SIGNATURE_ALGORITHM = "SHA256WithRSAEncryption";
    private static final String DEFAULT_ALGORITHM = "RSA";

    /**
     * Initializes and returns a {@link KeyStore} from a file path.
     *
     * @param format the format of the specified keystore. Could be PKCS12 or JKS.
     * @param path the path to the keystore file to initialize.
     * @param password the password required to read the keystore. null if not password.
     *
     * @return the initialized keystore or an {@link IllegalArgumentException} if the keystore can't be read or initialized.
     */
    public static KeyStore initFromPath(String format, String path, String password) {
        try (InputStream is = new File(path).toURI().toURL().openStream()) {
            final KeyStore keyStore = KeyStore.getInstance(format);
            keyStore.load(is, passwordToCharArray(password));
            return keyStore;
        } catch (Exception e) {
            throw new IllegalArgumentException(String.format("Unable to load keystore from path [%s]", path), e);
        }
    }

    /**
     * Initializes and returns a {@link KeyStore} from a base64 encoded content string.
     *
     * @param format the format of the specified keystore. Could be PKCS12 or JKS.
     * @param keystore the base64 encoded content representing the keystore to initialize.
     * @param password the password required to read the keystore. null if not password.
     *
     * @return the initialized keystore or an {@link IllegalArgumentException} if the keystore can't be read or initialized.
     */
    public static KeyStore initFromContent(String format, String keystore, String password) {
        try {
            final ByteArrayInputStream stream = new ByteArrayInputStream(Base64.getDecoder().decode(keystore));
            KeyStore keyStore = KeyStore.getInstance(format);
            keyStore.load(stream, passwordToCharArray(password));
            return keyStore;
        } catch (Exception e) {
            throw new IllegalArgumentException("Unable to get keystore from base64", e);
        }
    }

    /**
     * Initializes and returns a {@link KeyStore} containing a self-signed certificate.
     *
     * @param fqdn the fully qualified domain name to use as common name (CN) for the self signed certificate.
     * @param password the password to use to protect the keystore.
     *
     * @return the initialized keystore or an {@link IllegalArgumentException} if the keystore initialized.
     */
    public static KeyStore initSelfSigned(String fqdn, String password) {
        try {
            KeyStore keyStore = KeyStore.getInstance(DEFAULT_KEYSTORE_TYPE);
            keyStore.load(null, new char[0]);

            final KeyPairGenerator keyGen = KeyPairGenerator.getInstance(DEFAULT_ALGORITHM);
            final SecureRandom random = new SecureRandom();
            keyGen.initialize(DEFAULT_KEY_LENGTH_BITS, random);
            final KeyPair keypair = keyGen.generateKeyPair();

            PrivateKey privateKey = keypair.getPrivate();

            X500Name cn = new X500Name("CN=" + fqdn);
            X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(
                cn,
                new BigInteger(64, random),
                DEFAULT_NOT_BEFORE,
                DEFAULT_NOT_AFTER,
                cn,
                keypair.getPublic()
            );

            ContentSigner signer = new JcaContentSignerBuilder(DEFAULT_SIGNATURE_ALGORITHM).build(privateKey);
            X509CertificateHolder certHolder = builder.build(signer);

            final JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(new BouncyCastleProvider());

            X509Certificate certificate = converter.getCertificate(certHolder);
            certificate.verify(keypair.getPublic());

            keyStore.setEntry(
                DEFAULT_ALIAS,
                new KeyStore.PrivateKeyEntry(privateKey, new Certificate[] { certificate }),
                new KeyStore.PasswordProtection(passwordToCharArray(password))
            );

            return keyStore;
        } catch (Exception e) {
            throw new IllegalArgumentException("Unable to get keystore from base64", e);
        }
    }

    /**
     * Initializes and returns a {@link KeyStore} from a list of paths to certificates in PEM format and a list of paths pointing to the corresponding private keys in PEM format.
     *
     * @param pemPathCertificates the list of certificate paths.
     * @param pemPathPrivateKeys the the list of private key paths.
     * @param password the password to use to protect the keystore. null if not password.
     *
     * @return the initialized keystore or an {@link IllegalArgumentException} if the certificates or private keys cannot be read or if the keystore can't be initialized.
     */
    public static KeyStore initFromPems(List pemPathCertificates, List pemPathPrivateKeys, String password) {
        if (pemPathCertificates.size() != pemPathPrivateKeys.size()) {
            throw new IllegalArgumentException(
                String.format(
                    "Mismatch between number of certificates (%s) and number of private keys (%s)",
                    pemPathCertificates.size(),
                    pemPathPrivateKeys.size()
                )
            );
        }

        try {
            final KeyStore keyStore = KeyStore.getInstance(DEFAULT_KEYSTORE_TYPE);
            final char[] charPassword = passwordToCharArray(password);
            keyStore.load(null, charPassword);

            for (int i = 0; i < pemPathCertificates.size(); i++) {
                try (
                    InputStream certIs = new File(pemPathCertificates.get(i)).toURI().toURL().openStream();
                    InputStream keyIs = new File(pemPathPrivateKeys.get(i)).toURI().toURL().openStream()
                ) {
                    final Certificate[] certificates = loadPemCertificates(new String(certIs.readAllBytes()));
                    final PrivateKey privateKey = loadPemPrivateKey(new String(keyIs.readAllBytes()));
                    keyStore.setEntry(
                        DEFAULT_ALIAS + "-" + i,
                        new KeyStore.PrivateKeyEntry(privateKey, certificates),
                        new KeyStore.PasswordProtection(charPassword)
                    );
                }
            }

            return keyStore;
        } catch (Exception e) {
            throw new IllegalArgumentException("Unable to initialize keystore from pem certificate and private key", e);
        }
    }

    /**
     * Initializes and returns a {@link KeyStore} from a list of paths to certificates in PEM format.
     *
     * @param pemPathCertificates the list of certificate paths.
     * @param password the password to use to protect the keystore. null if not password.
     *
     * @return the initialized keystore or an {@link IllegalArgumentException} if the certificates or private keys cannot be read or if the keystore can't be initialized.
     */
    public static KeyStore initFromPemCertificateFiles(List pemPathCertificates, String password) {
        try {
            final KeyStore keyStore = KeyStore.getInstance(DEFAULT_KEYSTORE_TYPE);
            final char[] charPassword = passwordToCharArray(password);
            keyStore.load(null, charPassword);

            var index = 0;
            for (String pemPathCertificate : pemPathCertificates) {
                try (InputStream certIs = new File(pemPathCertificate).toURI().toURL().openStream()) {
                    final Certificate[] certificates = loadPemCertificates(new String(certIs.readAllBytes()));

                    for (Certificate certificate : certificates) {
                        var currentAlias = DEFAULT_ALIAS;
                        if (index > 0) {
                            currentAlias = currentAlias + "_" + index;
                        }
                        keyStore.setCertificateEntry(currentAlias, certificate);
                        index++;
                    }
                }
            }

            return keyStore;
        } catch (Exception e) {
            throw new IllegalArgumentException("Unable to initialize keystore from pem certificate and private key", e);
        }
    }

    /**
     * Initializes and returns a {@link KeyStore} from a certificate content in PEM format and a the corresponding private key content in PEM format.
     *
     * @param pemCertificate the content of the certificate in PEM format.
     * @param pemPrivateKey the content of the private key in PEM format.
     * @param password the password to use to protect the keystore. null if not password.
     *
     * @return the initialized keystore or an {@link IllegalArgumentException} if the certificate or private key cannot be read or if the keystore can't be initialized.
     */
    public static KeyStore initFromPem(String pemCertificate, String pemPrivateKey, String password, String alias) {
        try {
            final KeyStore keyStore = KeyStore.getInstance(DEFAULT_KEYSTORE_TYPE);
            final Certificate[] certificates = loadPemCertificates(pemCertificate);
            final PrivateKey privateKey = loadPemPrivateKey(pemPrivateKey);

            keyStore.load(null, passwordToCharArray(password));
            keyStore.setEntry(
                alias == null ? DEFAULT_ALIAS : alias,
                new KeyStore.PrivateKeyEntry(privateKey, certificates),
                new KeyStore.PasswordProtection(passwordToCharArray(password))
            );

            return keyStore;
        } catch (Exception e) {
            throw new IllegalArgumentException("Unable to initialize keystore from pem certificate and private key", e);
        }
    }

    /**
     * Initializes and returns a {@link KeyStore} from a certificate content in PEM format.
     *
     * @param pemCertificate the content of the certificate in PEM format.
     * @param password the password to use to protect the keystore. null if not password.
     *
     * @return the initialized keystore or an {@link IllegalArgumentException} if the certificate cannot be read
     * or if the keystore can't be initialized.
     */
    public static KeyStore initFromPemCertificate(String pemCertificate, String password, String alias) {
        try {
            final KeyStore keyStore = KeyStore.getInstance(DEFAULT_KEYSTORE_TYPE);
            final Certificate[] certificates = loadPemCertificates(pemCertificate);

            var certAlias = alias == null ? DEFAULT_ALIAS : alias;

            keyStore.load(null, passwordToCharArray(password));
            int index = 0;
            for (Certificate certificate : certificates) {
                var currentAlias = certAlias;
                if (index > 0) {
                    currentAlias = currentAlias + "_" + index;
                }
                keyStore.setCertificateEntry(currentAlias, certificate);
                index++;
            }

            return keyStore;
        } catch (Exception e) {
            throw new IllegalArgumentException("Unable to initialize keystore from pem certificate", e);
        }
    }

    /**
     * Returns the certificate chain from a PEM content.
     *
     * @param pem the content of the certificate chain in PEM format.
     *
     * @return the certificate chain or an {@link IllegalArgumentException} if the certificate chain cannot be read.
     */
    public static Certificate[] loadPemCertificates(String pem) throws Exception {
        JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(new BouncyCastleProvider());

        final PemReader pemReader = new PemReader(new StringReader(pem));
        final List certificates = new ArrayList<>();

        try (PEMParser pemParser = new PEMParser(pemReader)) {
            Object o;

            while ((o = pemParser.readObject()) != null) {
                if (o instanceof X509CertificateHolder) {
                    X509Certificate certificate = converter.getCertificate((X509CertificateHolder) o);
                    if (certificate == null) {
                        continue;
                    }
                    certificates.add(certificate);
                }
            }
        }

        return certificates.toArray(new X509Certificate[0]);
    }

    /**
     * Returns the private key from a PEM content.
     *
     * @param pem the content of the private key in PEM format.
     *
     * @return the private key or an {@link IllegalArgumentException} if the private key cannot be read or has not been found.
     */
    public static PrivateKey loadPemPrivateKey(String pem) throws IOException {
        final JcaPEMKeyConverter converter = new JcaPEMKeyConverter();
        PemReader pemReader = new PemReader(new StringReader(pem));

        try (PEMParser pemParser = new PEMParser(pemReader)) {
            Object o;

            while ((o = pemParser.readObject()) != null) {
                if (o instanceof PEMKeyPair) {
                    PrivateKey privateKey = converter.getPrivateKey(((PEMKeyPair) o).getPrivateKeyInfo());
                    if (privateKey == null) {
                        continue;
                    }
                    return privateKey;
                } else if (o instanceof PrivateKeyInfo) {
                    PrivateKey privateKey = converter.getPrivateKey((PrivateKeyInfo) o);
                    if (privateKey == null) {
                        continue;
                    }
                    return privateKey;
                }
            }

            throw new IllegalArgumentException("No private key found for the specified pem content.");
        }
    }

    /**
     * Convert a password string into a char array.
     * Return an empty char array if the specified password is null
     *
     * @param password the password to convert to char array.
     *
     * @return the corresponding char array.
     */
    public static char[] passwordToCharArray(String password) {
        return password != null ? password.toCharArray() : new char[0];
    }

    /**
     * Find the first alias of the specified {@link KeyStore} and returns it as the default alias.
     *
     * @param keyStore the keystore from where to get the default alias.
     *
     * @return the first alias found or null if no alias has been found or an {@link IllegalArgumentException} if the alias can't be read from the specified {@link KeyStore}.
     */
    public static String getDefaultAlias(KeyStore keyStore) {
        try {
            if (keyStore.aliases().hasMoreElements()) {
                return keyStore.aliases().nextElement();
            }
        } catch (Exception e) {
            throw new IllegalArgumentException("Unable to get default alias from keystore.", e);
        }

        return null;
    }

    /**
     * Initialize a new {@link KeyStore} in PKCS12 format which is the combination of the 2 provided keystores.
     * Note: for convenience, we assume the password is the same for both the source keystores.
     *
     * @param source1 the first keystore to merge.
     * @param source2 the second keystore to merge.
     * @param password the password that protects the source keystores.
     *
     * @return a new {@link KeyStore} which is the combination of the 2 source keystores or an {@link IllegalArgumentException} if one of the source keystores can't be read.
     */
    public static KeyStore merge(KeyStore source1, KeyStore source2, String password) {
        return merge(asList(source1, source2), password);
    }

    /**
     * Initialize a new {@link KeyStore} in PKCS12 format which is the combination of all the provided keystores.
     * Note: for convenience, we assume the password is the same for all the source keystores.
     *
     * @param  sources the keystores to merge.
     * @param password the password that protects the source keystores.
     *
     * @return a new {@link KeyStore} which is the combination of all the source keystores or an {@link IllegalArgumentException} if one of the source keystores can't be read.
     */
    public static KeyStore merge(List sources, String password) {
        try {
            final KeyStore destination = KeyStore.getInstance(DEFAULT_KEYSTORE_TYPE);
            destination.load(null, passwordToCharArray(password));
            sources.forEach(source -> copy(source, destination, password));
            return destination;
        } catch (Exception e) {
            throw new IllegalArgumentException("Unable to merge the 2 keystores", e);
        }
    }

    /**
     * Copy all the {@link KeyStore}'s entries from source to destination {@link KeyStore}.
     * Note: for convenience, we assume the password is the same for all the source keystores.
     *
     * @param  source the source {@link KeyStore} containing the entries to copy.
     * @param destination the destination {@link KeyStore}.
     * @param password the password that protects the source keystores.
     *
     * @throws IllegalArgumentException if one of the keystores can be read or if copy to the destination {@link KeyStore} creates duplicate aliases.
     */
    public static void copy(KeyStore source, KeyStore destination, String password) {
        try {
            final KeyStore.PasswordProtection passwordProtection = new KeyStore.PasswordProtection(passwordToCharArray(password));
            final Enumeration aliases = source.aliases();

            while (aliases.hasMoreElements()) {
                String alias = aliases.nextElement();
                final KeyStore.Entry entry = source.getEntry(alias, passwordProtection);

                if (destination.containsAlias(alias)) {
                    throw new IllegalArgumentException(
                        String.format("The alias [%s] is present in both keystores. Aliases must be unique.", alias)
                    );
                }

                destination.setEntry(alias, entry, passwordProtection);
            }
        } catch (Exception e) {
            throw new IllegalArgumentException("Unable to copy source keystore into destination keystore", e);
        }
    }

    /**
     * Returns a {@link Map} with the following structure:
     * 
    *
  • key: a domain name (or wildcard domain)
  • *
  • value: the matching alias
  • *
* * The list of the domain names is extracted from certificate Common Name (CN) and Subject Alternative Names (SAN). * If multiple certificates matches the same CN or SAN, the last one will be kept. * * @param keyStore the {@link KeyStore} from where to extract the domain names and aliases or an {@link IllegalArgumentException} if the CN or SAN cannot be read from the specified {@link KeyStore}. * * @return the map of domain names and corresponding aliases. */ public static Map getCommonNamesByAlias(KeyStore keyStore) { try { Map names = new HashMap<>(); final Enumeration aliases = keyStore.aliases(); while (aliases.hasMoreElements()) { String alias = aliases.nextElement(); final Certificate certificate = keyStore.getCertificate(alias); if (certificate instanceof X509Certificate) { final X509Certificate x509Certificate = (X509Certificate) certificate; final X500Name x500name = new JcaX509CertificateHolder(x509Certificate).getSubject(); final RDN[] rdNs = x500name.getRDNs(BCStyle.CN); if (rdNs.length > 0) { names.put(IETFUtils.valueToString(rdNs[0].getFirst().getValue()), alias); } Collection> altNames = x509Certificate.getSubjectAlternativeNames(); if (altNames != null) { altNames .stream() .filter(entry -> (int) entry.get(0) == DNSNAME) .forEach(entry -> names.put(entry.get(1).toString(), alias)); } } } return names; } catch (Exception e) { throw new IllegalArgumentException("Unable to extract CN/SAN from keystore.", e); } } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy