001/* 002 * Copyright © 2025 CUI-OpenSource-Software (info@cuioss.de) 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 */ 016package de.cuioss.http.client.handler; 017 018import de.cuioss.http.client.HttpLogMessages; 019import de.cuioss.tools.collect.CollectionLiterals; 020import de.cuioss.tools.logging.CuiLogger; 021import org.jspecify.annotations.Nullable; 022 023import javax.net.ssl.SSLContext; 024import javax.net.ssl.TrustManager; 025import javax.net.ssl.TrustManagerFactory; 026import java.security.*; 027import java.util.Set; 028 029/** 030 * Provider for secure SSL contexts used in HTTPS communications. 031 * <p> 032 * This class enforces secure TLS versions when establishing connections to JWKS endpoints 033 * and other services. It ensures that only modern, secure TLS protocols are used: 034 * <ul> 035 * <li>TLS 1.2 - Minimum recommended version</li> 036 * <li>TLS 1.3 - Preferred when available</li> 037 * </ul> 038 * <p> 039 * The class prevents the use of insecure, deprecated protocols: 040 * <ul> 041 * <li>TLS 1.0 - Deprecated due to security vulnerabilities</li> 042 * <li>TLS 1.1 - Deprecated due to security vulnerabilities</li> 043 * <li>SSL 3.0 - Deprecated due to security vulnerabilities (POODLE attack)</li> 044 * </ul> 045 * <p> 046 * For more details on the security aspects, see the 047 * <a href="https://github.com/cuioss/cui-jwt-validation/tree/main/doc/specification/security.adoc">Security Specification</a> 048 * 049 * @param minimumTlsVersion The minimum TLS version that is considered secure for this instance. 050 * @author Oliver Wolff 051 * @since 1.0 052 */ 053public record SecureSSLContextProvider(String minimumTlsVersion) { 054 055 private static final CuiLogger LOGGER = new CuiLogger(SecureSSLContextProvider.class); 056 057 /** 058 * TLS version 1.2 - Secure 059 */ 060 public static final String TLS_V1_2 = "TLSv1.2"; 061 062 /** 063 * TLS version 1.3 - Secure 064 */ 065 public static final String TLS_V1_3 = "TLSv1.3"; 066 067 /** 068 * Generic TLS - Secure if implemented correctly by the JVM 069 */ 070 public static final String TLS = "TLS"; 071 072 /** 073 * Default secure TLS version to use when creating a new context 074 */ 075 public static final String DEFAULT_TLS_VERSION = TLS_V1_2; 076 077 /** 078 * TLS version 1.0 - Insecure, deprecated 079 */ 080 public static final String TLS_V1_0 = "TLSv1.0"; 081 082 /** 083 * TLS version 1.1 - Insecure, deprecated 084 */ 085 public static final String TLS_V1_1 = "TLSv1.1"; 086 087 /** 088 * SSL version 3 - Insecure, deprecated 089 */ 090 public static final String SSL_V3 = "SSLv3"; 091 092 /** 093 * Set of allowed (secure) TLS versions 094 */ 095 public static final Set<String> ALLOWED_TLS_VERSIONS = CollectionLiterals.immutableSet(TLS_V1_2, TLS_V1_3, TLS); 096 097 /** 098 * Set of forbidden (insecure) TLS versions 099 */ 100 public static final Set<String> FORBIDDEN_TLS_VERSIONS = CollectionLiterals.immutableSet(TLS_V1_0, TLS_V1_1, SSL_V3); 101 102 /** 103 * Creates a new SecureSSLContextProvider instance with the default minimum TLS version (TLS 1.2). 104 */ 105 public SecureSSLContextProvider() { 106 this(DEFAULT_TLS_VERSION); 107 } 108 109 /** 110 * Creates a new SecureSSLContextProvider instance with the specified minimum TLS version. 111 * 112 * @param minimumTlsVersion the minimum TLS version to consider secure 113 * @throws IllegalArgumentException if the specified version is not in the allowed set 114 */ 115 public SecureSSLContextProvider { 116 if (!ALLOWED_TLS_VERSIONS.contains(minimumTlsVersion)) { 117 throw new IllegalArgumentException("Minimum TLS version must be one of the allowed versions: " + ALLOWED_TLS_VERSIONS); 118 } 119 } 120 121 /** 122 * Checks if the given protocol is a secure TLS version according to the minimum version set for this instance. 123 * <p> 124 * For TLS_V1_2 and TLS_V1_3, the comparison is based on the version number. 125 * For TLS (generic), it's considered secure if it's in the allowed versions set. 126 * 127 * @param protocol the protocol to check 128 * @return true if the protocol is a secure TLS version, false otherwise 129 */ 130 public boolean isSecureTlsVersion(@Nullable String protocol) { 131 if (protocol == null) { 132 return false; 133 } 134 135 if (!ALLOWED_TLS_VERSIONS.contains(protocol)) { 136 return false; 137 } 138 139 // If the minimum is TLS_V1_3, only TLS_V1_3 and TLS are considered secure 140 if (TLS_V1_3.equals(minimumTlsVersion)) { 141 return TLS_V1_3.equals(protocol) || TLS.equals(protocol); 142 } 143 144 // If the minimum is TLS_V1_2, all allowed versions are secure 145 return true; 146 } 147 148 /** 149 * Creates a secure SSLContext configured with the minimum TLS version set for this instance. 150 * <p> 151 * This method: 152 * <ol> 153 * <li>Creates an SSLContext instance with the secure protocol version</li> 154 * <li>Initializes a TrustManagerFactory with the default algorithm</li> 155 * <li>Configures the TrustManagerFactory to use the default trust store</li> 156 * <li>Initializes the SSLContext with the trust managers and a secure random source</li> 157 * </ol> 158 * <p> 159 * The resulting SSLContext is configured to trust the certificates in the JVM's default trust store 160 * and does not perform client authentication (no KeyManager is provided). 161 * 162 * @return a configured SSLContext that uses a secure TLS protocol version 163 * @throws NoSuchAlgorithmException if the specified protocol or trust manager algorithm is not available 164 * @throws KeyStoreException if there's an issue accessing the default trust store 165 * @throws KeyManagementException if there's an issue initializing the SSLContext 166 */ 167 public SSLContext createSecureSSLContext() throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException { 168 // Create a secure SSL context with the minimum TLS version 169 SSLContext secureContext = SSLContext.getInstance(minimumTlsVersion); 170 TrustManagerFactory trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); 171 trustManagerFactory.init((KeyStore) null); 172 TrustManager[] trustManagers = trustManagerFactory.getTrustManagers(); 173 secureContext.init(null, trustManagers, new SecureRandom()); 174 return secureContext; 175 } 176 177 /** 178 * Validates the provided SSLContext and returns a secure SSLContext. 179 * <p> 180 * This method: 181 * <ol> 182 * <li>If the provided SSLContext is null, creates a new secure SSLContext</li> 183 * <li>If the provided SSLContext is not null, checks if its protocol is secure</li> 184 * <li>If the protocol is secure, returns the provided SSLContext</li> 185 * <li>If the protocol is not secure, creates a new secure SSLContext</li> 186 * <li>If an exception occurs during validation or creation, falls back to the provided SSLContext or the default SSLContext</li> 187 * </ol> 188 * 189 * @param sslContext the SSLContext to validate, may be null 190 * @return a secure SSLContext, either the validated input or a newly created one (never null) 191 */ 192 public SSLContext getOrCreateSecureSSLContext(@Nullable SSLContext sslContext) { 193 try { 194 if (sslContext != null) { 195 // Validate the provided SSL context 196 String protocol = sslContext.getProtocol(); 197 LOGGER.debug("SSL context protocol: %s", protocol); 198 199 // Check if the protocol is secure according to the configured TLS versions 200 if (isSecureTlsVersion(protocol)) { 201 // The provided context was secure and is being used 202 LOGGER.debug("Using provided SSL context with protocol: %s", protocol); 203 return sslContext; 204 } 205 206 // If not secure, create a new secure context 207 LOGGER.warn(HttpLogMessages.WARN.SSL_INSECURE_PROTOCOL.format(protocol)); 208 SSLContext secureContext = createSecureSSLContext(); 209 LOGGER.debug("Created secure SSL context with minimum TLS version: %s", minimumTlsVersion); 210 return secureContext; 211 } else { 212 // If no context provided, create a new secure one 213 SSLContext secureContext = createSecureSSLContext(); 214 LOGGER.debug("No SSL context provided, created secure context with minimum TLS version: %s", minimumTlsVersion); 215 return secureContext; 216 } 217 } catch (NoSuchAlgorithmException | KeyStoreException | KeyManagementException e) { 218 // If a secure context cannot be created, we must fail hard. 219 throw new IllegalStateException("Failed to create a secure SSL context", e); 220 } 221 } 222}