001/*
002 * Copyright (C) 2023 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 javax.net.ssl.KeyManagerFactory;
020import javax.net.ssl.SSLContext;
021import javax.net.ssl.TrustManagerFactory;
022import java.io.FileInputStream;
023import java.io.IOException;
024import java.io.InputStream;
025import java.security.GeneralSecurityException;
026import java.security.KeyStore;
027import java.security.SecureRandom;
028import java.util.Enumeration;
029import java.util.HashSet;
030import java.util.Set;
031
032public class SSLContextFactory {
033
034    private static final String[] PROTOCOLS = { "TLSv1.3", "TLSv1.2", "TLSv1.1", "TLSv1" };
035
036    /**
037     * Constructor
038     */
039    private SSLContextFactory() {
040        // DO NOTHING
041    }
042
043    /**
044     * Method to create an SSLContext
045     *
046     * @param keyStoreFilename keyStoreFilename
047     * @param keyStorePassword keyStorePassword
048     * @param certificateAlias certificateAlias
049     * @return the return value
050     * @throws GeneralSecurityException GeneralSecurityException
051     * @throws IOException IOException
052     */
053    public static SSLContext createSSLContext(String keyStoreFilename, String keyStorePassword, String certificateAlias)
054            throws GeneralSecurityException, IOException {
055        KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
056
057        try (InputStream inputStream = new FileInputStream(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,
085                                keyStoreFilename));
086            }
087
088            // Create and initialize an SSLContext
089
090            KeyManagerFactory keyManagerFactory =
091                    KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
092
093            keyManagerFactory.init(keyStore, keyStorePassword.toCharArray());
094
095            TrustManagerFactory trustManagerFactory =
096                    TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
097
098            trustManagerFactory.init(keyStore);
099
100            SSLContext sslContext = createSSLContext();
101
102            sslContext.init(
103                    keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new SecureRandom());
104
105            return sslContext;
106        }
107    }
108
109    /**
110     * Method to create an SSLContext, looping through more secure to less secure TLS protocols
111     *
112     * @return the return value
113     * @throws GeneralSecurityException GeneralSecurityException
114     */
115    private static SSLContext createSSLContext() throws GeneralSecurityException {
116        // Loop through potential protocols since there doesn't appear
117        // to be a way to get the most secure supported protocol
118        for (int i = 0; i < PROTOCOLS.length; i++) {
119            try {
120                return SSLContext.getInstance(PROTOCOLS[i]);
121            } catch (Throwable t) {
122                // DO NOTHING
123            }
124        }
125
126        throw new GeneralSecurityException(String.format("No supported TLS protocols found"));
127    }
128}