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}