io.crate.protocols.ssl.SslConfiguration.java Source code

Java tutorial

Introduction

Here is the source code for io.crate.protocols.ssl.SslConfiguration.java

Source

/*
 * This file is part of a module with proprietary Enterprise Features.
 *
 * Licensed to Crate.io Inc. ("Crate.io") under one or more contributor
 * license agreements.  See the NOTICE file distributed with this work for
 * additional information regarding copyright ownership.
 *
 * Unauthorized copying of this file, via any medium is strictly prohibited.
 *
 * To use this file, Crate.io must have given you permission to enable and
 * use such Enterprise Features and you must have a valid Enterprise or
 * Subscription Agreement with Crate.io.  If you enable or use the Enterprise
 * Features, you represent and warrant that you have a valid Enterprise or
 * Subscription Agreement with Crate.io.  Your use of the Enterprise Features
 * if governed by the terms and conditions of your Enterprise or Subscription
 * Agreement with Crate.io.
 */

package io.crate.protocols.ssl;

import io.crate.settings.CrateSetting;
import io.netty.handler.ssl.ApplicationProtocolConfig;
import io.netty.handler.ssl.ClientAuth;
import io.netty.handler.ssl.SslContext;
import io.netty.handler.ssl.SslContextBuilder;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.ssl.SslProvider;
import org.apache.logging.log4j.Logger;
import org.elasticsearch.common.logging.Loggers;
import org.elasticsearch.common.settings.Settings;

import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.List;
import java.util.Optional;

import static io.crate.Constants.KEYSTORE_DEFAULT_TYPE;

/**
 * Builds a Netty {@link SSLContext} which is passed upon creation of a {@link SslHandler}
 * which is responsible for establishing the SSL connection in a Netty pipeline.
 *
 * http://docs.oracle.com/javase/6/docs/technotes/guides/security/jsse/JSSERefGuide.html
 *
 * TrustManager:
 * By convention, contains trusted certificates to verify the authenticity of an unknown
 * certificates which may be signed with a known certificate.
 * This is where your CA certificates go.
 *
 * KeyManager:
 * Contains the private key for data encryption and the certificate (public key) to send
 * to the remote end.
 *
 * See also {@link io.crate.protocols.postgres.SslReqHandler}
 */
@SuppressWarnings("WeakerAccess")
public final class SslConfiguration {

    private SslConfiguration() {
    }

    public static SslContext buildSslContext(Settings settings) {
        try {
            KeyStoreSettings keyStoreSettings = new KeyStoreSettings(settings);

            Optional<TrustStoreSettings> trustStoreSettings = TrustStoreSettings.tryLoad(settings);
            TrustManager[] trustManagers = null;
            if (trustStoreSettings.isPresent()) {
                trustManagers = trustStoreSettings.get().trustManagers;
            }

            // Use the newest SSL standard which is (at the time of writing) TLSv1.2
            // If we just specify "TLS" here, it depends on the JVM implementation which version we'll get.
            SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
            sslContext.init(keyStoreSettings.keyManagers, trustManagers, null);
            SSLContext.setDefault(sslContext);

            List<String> enabledCiphers = Arrays.asList(sslContext.createSSLEngine().getEnabledCipherSuites());

            final X509Certificate[] keystoreCerts = keyStoreSettings.exportServerCertChain();
            final PrivateKey privateKey = keyStoreSettings.exportDecryptedKey();

            X509Certificate[] trustedCertificates = keyStoreSettings.exportRootCertificates();
            if (trustStoreSettings.isPresent()) {
                trustedCertificates = trustStoreSettings.get().exportRootCertificates(trustedCertificates);
            }

            final SslContextBuilder sslContextBuilder = SslContextBuilder.forServer(privateKey, keystoreCerts)
                    .ciphers(enabledCiphers).applicationProtocolConfig(ApplicationProtocolConfig.DISABLED)
                    .clientAuth(ClientAuth.OPTIONAL).sessionCacheSize(0).sessionTimeout(0).startTls(false)
                    .sslProvider(SslProvider.JDK);

            if (trustedCertificates != null && trustedCertificates.length > 0) {
                sslContextBuilder.trustManager(trustedCertificates);
            }

            return sslContextBuilder.build();

        } catch (SslConfigurationException e) {
            throw e;
        } catch (Exception e) {
            throw new SslConfigurationException("Failed to build SSL configuration", e);
        }
    }

    abstract static class AbstractKeyStoreSettings {

        final Logger LOGGER = Loggers.getLogger(getClass());

        final KeyStore keyStore;
        final String keyStorePath;
        final char[] keyStorePassword;

        AbstractKeyStoreSettings(Settings settings) throws Exception {
            this.keyStorePath = checkStorePath(getPathSetting().setting().get(settings));
            this.keyStorePassword = getPassword(getPasswordSetting().setting().get(settings));
            this.keyStore = KeyStore.getInstance(KEYSTORE_DEFAULT_TYPE);
        }

        abstract CrateSetting<String> getPathSetting();

        abstract CrateSetting<String> getPasswordSetting();

        X509Certificate[] exportRootCertificates() throws KeyStoreException {
            return exportRootCertificates(new X509Certificate[0]);
        }

        X509Certificate[] exportRootCertificates(X509Certificate[] existingCerts) throws KeyStoreException {

            final List<X509Certificate> trustedCerts = new ArrayList<>();

            final Enumeration<String> aliases = keyStore.aliases();

            while (aliases.hasMoreElements()) {
                String _alias = aliases.nextElement();

                if (keyStore.isCertificateEntry(_alias)) {
                    LOGGER.info("Found certificate with alias {}", _alias);
                    final X509Certificate cert = (X509Certificate) keyStore.getCertificate(_alias);
                    if (cert != null) {
                        trustedCerts.add(cert);
                    }
                }
            }

            X509Certificate[] certsToAdd = trustedCerts.toArray(new X509Certificate[0]);

            X509Certificate[] trustedCertificates = Arrays.copyOf(existingCerts,
                    existingCerts.length + certsToAdd.length);

            System.arraycopy(certsToAdd, 0, trustedCertificates, existingCerts.length, certsToAdd.length);

            return trustedCertificates;
        }

        X509Certificate[] exportServerCertChain() throws KeyStoreException {

            X509Certificate[] allCerts = new X509Certificate[0];
            final Enumeration<String> aliases = keyStore.aliases();

            while (aliases.hasMoreElements()) {
                String alias = aliases.nextElement();
                if (keyStore.isKeyEntry(alias)) {
                    LOGGER.info("Found key with alias {}", alias);
                    Certificate[] certs = keyStore.getCertificateChain(alias);
                    if (certs != null && certs.length > 0) {
                        X509Certificate[] newAllCerts = Arrays.copyOf(allCerts, allCerts.length + certs.length,
                                X509Certificate[].class);
                        //noinspection SuspiciousSystemArraycopy
                        System.arraycopy(certs, 0, newAllCerts, allCerts.length, certs.length);
                        allCerts = newAllCerts;
                    }
                }
            }

            return allCerts;
        }

        static String checkStorePath(String keystoreFilePath) throws FileNotFoundException {

            if (keystoreFilePath == null || keystoreFilePath.length() == 0) {
                throw new FileNotFoundException("Empty file path for keystore.");
            }

            Path path = Paths.get(keystoreFilePath);
            if (Files.isDirectory(path, LinkOption.NOFOLLOW_LINKS)) {
                throw new FileNotFoundException(
                        "[" + keystoreFilePath + "] is a directory, expected file for keystore.");
            }

            if (!Files.isReadable(path)) {
                throw new FileNotFoundException("Unable to read [" + keystoreFilePath + "] for "
                        + "key store. Please make sure this file exists and has read permissions.");
            }

            return keystoreFilePath;
        }

        static char[] getPassword(String password) {
            if (password == null) {
                return new char[] {};
            }
            return password.toCharArray();
        }
    }

    static class KeyStoreSettings extends AbstractKeyStoreSettings {

        final KeyManager[] keyManagers;
        final char[] keyStoreKeyPassword;

        KeyStoreSettings(Settings settings) throws Exception {
            super(settings);

            this.keyStoreKeyPassword = getPassword(
                    SslConfigSettings.SSL_KEYSTORE_KEY_PASSWORD.setting().get(settings));

            try (FileInputStream is = new FileInputStream(new File(keyStorePath))) {
                keyStore.load(is, keyStorePassword);
            }

            KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
            keyFactory.init(keyStore, keyStoreKeyPassword);

            this.keyManagers = keyFactory.getKeyManagers();
        }

        PrivateKey exportDecryptedKey()
                throws KeyStoreException, UnrecoverableKeyException, NoSuchAlgorithmException {
            Enumeration<String> aliases = keyStore.aliases();
            if (!aliases.hasMoreElements()) {
                throw new KeyStoreException("No aliases found in keystore");
            }

            while (aliases.hasMoreElements()) {
                String alias = aliases.nextElement();
                if (keyStore.isKeyEntry(alias)) {
                    LOGGER.info("Found private key with alias {}", alias);
                    Key key = keyStore.getKey(alias, keyStoreKeyPassword);
                    if (key instanceof PrivateKey) {
                        return (PrivateKey) key;
                    }
                }
            }

            throw new KeyStoreException("No key matching the password found in keystore: " + keyStorePath);
        }

        @Override
        CrateSetting<String> getPathSetting() {
            return SslConfigSettings.SSL_KEYSTORE_FILEPATH;
        }

        @Override
        CrateSetting<String> getPasswordSetting() {
            return SslConfigSettings.SSL_KEYSTORE_PASSWORD;
        }

    }

    static class TrustStoreSettings extends AbstractKeyStoreSettings {

        final TrustManager[] trustManagers;

        private TrustStoreSettings(Settings settings) throws Exception {
            super(settings);

            keyStore.load(new FileInputStream(new File(keyStorePath)), keyStorePassword);

            TrustManagerFactory trustFactory = TrustManagerFactory
                    .getInstance(TrustManagerFactory.getDefaultAlgorithm());
            trustFactory.init(keyStore);

            this.trustManagers = trustFactory.getTrustManagers();
        }

        @Override
        CrateSetting<String> getPathSetting() {
            return SslConfigSettings.SSL_TRUSTSTORE_FILEPATH;
        }

        @Override
        CrateSetting<String> getPasswordSetting() {
            return SslConfigSettings.SSL_TRUSTSTORE_PASSWORD;
        }

        static Optional<TrustStoreSettings> tryLoad(Settings settings) throws Exception {
            try {
                return Optional.of(new TrustStoreSettings(settings));
            } catch (FileNotFoundException e) {
                return Optional.empty();
            }
        }
    }
}