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}