001/*
002 * Copyright (C) 2023-present The Prometheus jmx_exporter Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package io.prometheus.jmx.common.http.ssl;
018
019import java.io.IOException;
020import java.io.InputStream;
021import java.nio.file.Files;
022import java.nio.file.Paths;
023import java.security.GeneralSecurityException;
024import java.security.KeyStore;
025import java.security.SecureRandom;
026import java.util.Enumeration;
027import java.util.HashSet;
028import java.util.Set;
029import javax.net.ssl.KeyManagerFactory;
030import javax.net.ssl.SSLContext;
031import javax.net.ssl.TrustManagerFactory;
032
033public class SSLContextFactory {
034
035    private static final String[] PROTOCOLS = {"TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1"};
036
037    /** Constructor */
038    private SSLContextFactory() {
039        // DO NOTHING
040    }
041
042    /**
043     * Method to create an SSLContext
044     *
045     * @param keyStoreFilename keyStoreFilename
046     * @param keyStorePassword keyStorePassword
047     * @param certificateAlias certificateAlias
048     * @return the return value
049     * @throws GeneralSecurityException GeneralSecurityException
050     * @throws IOException IOException
051     */
052    public static SSLContext createSSLContext(
053            String keyStoreFilename, String keyStorePassword, String certificateAlias)
054            throws GeneralSecurityException, IOException {
055        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
056
057        try (InputStream inputStream = Files.newInputStream(Paths.get(keyStoreFilename))) {
058            // Load the keystore
059            keyStore.load(inputStream, keyStorePassword.toCharArray());
060
061            // Loop through the certificate aliases in the keystore
062            // building a set of certificate aliases that don't match
063            // the requested certificate alias
064            Set<String> certificateAliasesToRemove = new HashSet<>();
065            Enumeration<String> aliases = keyStore.aliases();
066            while (aliases.hasMoreElements()) {
067                String keyStoreCertificateAlias = aliases.nextElement();
068                if (!keyStoreCertificateAlias.equals(certificateAlias)) {
069                    certificateAliasesToRemove.add(keyStoreCertificateAlias);
070                }
071            }
072
073            // Remove the certificate aliases that don't
074            // match the requested certificate alias from the keystore
075            for (String certificateAliasToRemove : certificateAliasesToRemove) {
076                keyStore.deleteEntry(certificateAliasToRemove);
077            }
078
079            // Validate the keystore contains the certificate alias that is requested
080            if (!keyStore.containsAlias(certificateAlias)) {
081                throw new GeneralSecurityException(
082                        String.format(
083                                "certificate alias [%s] not found in keystore [%s]",
084                                certificateAlias, keyStoreFilename));
085            }
086
087            // Create and initialize an SSLContext
088
089            KeyManagerFactory keyManagerFactory =
090                    KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
091
092            keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
093
094            TrustManagerFactory trustManagerFactory =
095                    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
096
097            trustManagerFactory.init(keyStore);
098
099            SSLContext sslContext = createSSLContext();
100
101            sslContext.init(
102                    keyManagerFactory.getKeyManagers(),
103                    trustManagerFactory.getTrustManagers(),
104                    new SecureRandom());
105
106            return sslContext;
107        }
108    }
109
110    /**
111     * Method to create an SSLContext, looping through more secure to less secure TLS protocols
112     *
113     * @return the return value
114     * @throws GeneralSecurityException GeneralSecurityException
115     */
116    private static SSLContext createSSLContext() throws GeneralSecurityException {
117        // Loop through potential protocols since there doesn't appear
118        // to be a way to get the most secure supported protocol
119        for (String protocol : PROTOCOLS) {
120            try {
121                return SSLContext.getInstance(protocol);
122            } catch (Throwable t) {
123                // DO NOTHING
124            }
125        }
126
127        throw new GeneralSecurityException("No supported TLS protocols found");
128    }
129}