1 /* 2 * Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 26 package sun.security.ssl; 27 28 import java.lang.invoke.MethodHandles; 29 import java.net.Socket; 30 import java.security.*; 31 import java.security.cert.*; 32 import java.util.*; 33 import java.util.concurrent.locks.ReentrantLock; 34 import javax.net.ssl.*; 35 import sun.security.util.AnchorCertificates; 36 import sun.security.util.HostnameChecker; 37 import sun.security.validator.*; 38 39 /** 40 * This class implements the SunJSSE X.509 trust manager using the internal 41 * validator API in J2SE core. The logic in this class is minimal.<p> 42 * <p> 43 * This class supports both the Simple validation algorithm from previous 44 * JSSE versions and PKIX validation. Currently, it is not possible for the 45 * application to specify PKIX parameters other than trust anchors. This will 46 * be fixed in a future release using new APIs. When that happens, it may also 47 * make sense to separate the Simple and PKIX trust managers into separate 48 * classes. 49 * 50 * @author Andreas Sterbenz 51 */ 52 final class X509TrustManagerImpl extends X509ExtendedTrustManager 53 implements X509TrustManager { 54 55 static { 56 try { 57 MethodHandles.lookup().ensureInitialized(AnchorCertificates.class); 58 } catch (IllegalAccessException e) { 59 throw new ExceptionInInitializerError(e); 60 } 61 } 62 63 private final String validatorType; 64 65 /** 66 * The Set of trusted X509Certificates. 67 */ 68 private final Collection<X509Certificate> trustedCerts; 69 70 private final PKIXBuilderParameters pkixParams; 71 72 // note that we need separate validator for client and server due to 73 // the different extension checks. They are initialized lazily on demand. 74 private volatile Validator clientValidator, serverValidator; 75 76 private final ReentrantLock validatorLock = new ReentrantLock(); 77 78 X509TrustManagerImpl(String validatorType, 79 Collection<X509Certificate> trustedCerts) { 80 81 this.validatorType = validatorType; 82 this.pkixParams = null; 83 84 if (trustedCerts == null) { 85 trustedCerts = Collections.emptySet(); 86 } 87 88 this.trustedCerts = trustedCerts; 89 90 if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { 91 SSLLogger.fine("adding as trusted certificates", 92 (Object[])trustedCerts.toArray(new X509Certificate[0])); 93 } 94 } 95 96 X509TrustManagerImpl(String validatorType, PKIXBuilderParameters params) { 97 this.validatorType = validatorType; 98 this.pkixParams = params; 99 // create server validator eagerly so that we can conveniently 100 // get the trusted certificates 101 // clients need it anyway eventually, and servers will not mind 102 // the little extra footprint 103 Validator v = getValidator(Validator.VAR_TLS_SERVER); 104 trustedCerts = v.getTrustedCertificates(); 105 serverValidator = v; 106 107 if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { 108 SSLLogger.fine("adding as trusted certificates", 109 (Object[])trustedCerts.toArray(new X509Certificate[0])); 110 } 111 } 112 113 @Override 114 public void checkClientTrusted(X509Certificate[] chain, String authType) 115 throws CertificateException { 116 checkTrusted(chain, authType, (Socket)null, true); 117 } 118 119 @Override 120 public void checkServerTrusted(X509Certificate[] chain, String authType) 121 throws CertificateException { 122 checkTrusted(chain, authType, (Socket)null, false); 123 } 124 125 @Override 126 public X509Certificate[] getAcceptedIssuers() { 127 X509Certificate[] certsArray = new X509Certificate[trustedCerts.size()]; 128 trustedCerts.toArray(certsArray); 129 return certsArray; 130 } 131 132 @Override 133 public void checkClientTrusted(X509Certificate[] chain, String authType, 134 Socket socket) throws CertificateException { 135 checkTrusted(chain, authType, socket, true); 136 } 137 138 @Override 139 public void checkServerTrusted(X509Certificate[] chain, String authType, 140 Socket socket) throws CertificateException { 141 checkTrusted(chain, authType, socket, false); 142 } 143 144 @Override 145 public void checkClientTrusted(X509Certificate[] chain, String authType, 146 SSLEngine engine) throws CertificateException { 147 checkTrusted(chain, authType, engine, true); 148 } 149 150 @Override 151 public void checkServerTrusted(X509Certificate[] chain, String authType, 152 SSLEngine engine) throws CertificateException { 153 checkTrusted(chain, authType, engine, false); 154 } 155 156 private Validator checkTrustedInit(X509Certificate[] chain, 157 String authType, boolean checkClientTrusted) { 158 if (chain == null || chain.length == 0) { 159 throw new IllegalArgumentException( 160 "null or zero-length certificate chain"); 161 } 162 163 if (authType == null || authType.isEmpty()) { 164 throw new IllegalArgumentException( 165 "null or zero-length authentication type"); 166 } 167 168 Validator v; 169 if (checkClientTrusted) { 170 v = clientValidator; 171 if (v == null) { 172 validatorLock.lock(); 173 try { 174 v = clientValidator; 175 if (v == null) { 176 v = getValidator(Validator.VAR_TLS_CLIENT); 177 clientValidator = v; 178 } 179 } finally { 180 validatorLock.unlock(); 181 } 182 } 183 } else { 184 // assume double-checked locking with a volatile flag works 185 // (guaranteed under the new Tiger memory model) 186 v = serverValidator; 187 if (v == null) { 188 validatorLock.lock(); 189 try { 190 v = serverValidator; 191 if (v == null) { 192 v = getValidator(Validator.VAR_TLS_SERVER); 193 serverValidator = v; 194 } 195 } finally { 196 validatorLock.unlock(); 197 } 198 } 199 } 200 201 return v; 202 } 203 204 private void checkTrusted(X509Certificate[] chain, 205 String authType, Socket socket, 206 boolean checkClientTrusted) throws CertificateException { 207 Validator v = checkTrustedInit(chain, authType, checkClientTrusted); 208 209 X509Certificate[] trustedChain; 210 if ((socket != null) && socket.isConnected() && 211 (socket instanceof SSLSocket sslSocket)) { 212 213 SSLSession session = sslSocket.getHandshakeSession(); 214 if (session == null) { 215 throw new CertificateException("No handshake session"); 216 } 217 218 // create the algorithm constraints 219 boolean isExtSession = (session instanceof ExtendedSSLSession); 220 AlgorithmConstraints constraints; 221 if (isExtSession && 222 ProtocolVersion.useTLS12PlusSpec(session.getProtocol())) { 223 ExtendedSSLSession extSession = (ExtendedSSLSession)session; 224 String[] localSupportedSignAlgs = 225 extSession.getLocalSupportedSignatureAlgorithms(); 226 227 constraints = SSLAlgorithmConstraints.forSocket( 228 sslSocket, localSupportedSignAlgs, false); 229 } else { 230 constraints = SSLAlgorithmConstraints.forSocket(sslSocket, false); 231 } 232 233 // Grab any stapled OCSP responses for use in validation 234 List<byte[]> responseList = Collections.emptyList(); 235 if (!checkClientTrusted && isExtSession) { 236 responseList = 237 ((ExtendedSSLSession)session).getStatusResponses(); 238 } 239 trustedChain = v.validate(chain, null, responseList, 240 constraints, checkClientTrusted ? null : authType); 241 242 // check endpoint identity 243 String identityAlg = sslSocket.getSSLParameters(). 244 getEndpointIdentificationAlgorithm(); 245 if (identityAlg != null && !identityAlg.isEmpty()) { 246 checkIdentity(session, 247 trustedChain, identityAlg, checkClientTrusted); 248 } 249 } else { 250 trustedChain = v.validate(chain, null, Collections.emptyList(), 251 null, checkClientTrusted ? null : authType); 252 } 253 254 if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { 255 SSLLogger.fine("Found trusted certificate", 256 trustedChain[trustedChain.length - 1]); 257 } 258 } 259 260 private void checkTrusted(X509Certificate[] chain, 261 String authType, SSLEngine engine, 262 boolean checkClientTrusted) throws CertificateException { 263 Validator v = checkTrustedInit(chain, authType, checkClientTrusted); 264 265 X509Certificate[] trustedChain; 266 if (engine != null) { 267 SSLSession session = engine.getHandshakeSession(); 268 if (session == null) { 269 throw new CertificateException("No handshake session"); 270 } 271 272 // create the algorithm constraints 273 boolean isExtSession = (session instanceof ExtendedSSLSession); 274 AlgorithmConstraints constraints; 275 if (isExtSession && 276 ProtocolVersion.useTLS12PlusSpec(session.getProtocol())) { 277 ExtendedSSLSession extSession = (ExtendedSSLSession)session; 278 String[] localSupportedSignAlgs = 279 extSession.getLocalSupportedSignatureAlgorithms(); 280 281 constraints = SSLAlgorithmConstraints.forEngine( 282 engine, localSupportedSignAlgs, false); 283 } else { 284 constraints = SSLAlgorithmConstraints.forEngine(engine, false); 285 } 286 287 // Grab any stapled OCSP responses for use in validation 288 List<byte[]> responseList = Collections.emptyList(); 289 if (!checkClientTrusted && isExtSession) { 290 responseList = 291 ((ExtendedSSLSession)session).getStatusResponses(); 292 } 293 trustedChain = v.validate(chain, null, responseList, 294 constraints, checkClientTrusted ? null : authType); 295 296 // check endpoint identity 297 String identityAlg = engine.getSSLParameters(). 298 getEndpointIdentificationAlgorithm(); 299 if (identityAlg != null && !identityAlg.isEmpty()) { 300 checkIdentity(session, trustedChain, 301 identityAlg, checkClientTrusted); 302 } 303 } else { 304 trustedChain = v.validate(chain, null, Collections.emptyList(), 305 null, checkClientTrusted ? null : authType); 306 } 307 308 if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { 309 SSLLogger.fine("Found trusted certificate", 310 trustedChain[trustedChain.length - 1]); 311 } 312 } 313 314 private Validator getValidator(String variant) { 315 Validator v; 316 if (pkixParams == null) { 317 v = Validator.getInstance(validatorType, variant, trustedCerts); 318 } else { 319 v = Validator.getInstance(validatorType, variant, pkixParams); 320 } 321 return v; 322 } 323 324 // Get string representation of HostName from a list of server names. 325 // 326 // We are only accepting host_name name type in the list. 327 private static String getHostNameInSNI(List<SNIServerName> sniNames) { 328 329 SNIHostName hostname = null; 330 for (SNIServerName sniName : sniNames) { 331 if (sniName.getType() != StandardConstants.SNI_HOST_NAME) { 332 continue; 333 } 334 335 if (sniName instanceof SNIHostName) { 336 hostname = (SNIHostName)sniName; 337 } else { 338 try { 339 hostname = new SNIHostName(sniName.getEncoded()); 340 } catch (IllegalArgumentException iae) { 341 // unlikely to happen, just in case ... 342 if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) { 343 SSLLogger.fine("Illegal server name: " + sniName); 344 } 345 } 346 } 347 348 // no more than server name of the same name type 349 break; 350 } 351 352 if (hostname != null) { 353 return hostname.getAsciiName(); 354 } 355 356 return null; 357 } 358 359 // Also used by X509KeyManagerImpl 360 static List<SNIServerName> getRequestedServerNames(Socket socket) { 361 if (socket != null && socket.isConnected() && 362 socket instanceof SSLSocket) { 363 return getRequestedServerNames( 364 ((SSLSocket)socket).getHandshakeSession()); 365 } 366 367 return Collections.emptyList(); 368 } 369 370 // Also used by X509KeyManagerImpl 371 static List<SNIServerName> getRequestedServerNames(SSLEngine engine) { 372 if (engine != null) { 373 return getRequestedServerNames(engine.getHandshakeSession()); 374 } 375 376 return Collections.emptyList(); 377 } 378 379 private static List<SNIServerName> getRequestedServerNames( 380 SSLSession session) { 381 if (session instanceof ExtendedSSLSession) { 382 return ((ExtendedSSLSession)session).getRequestedServerNames(); 383 } 384 385 return Collections.emptyList(); 386 } 387 388 /* 389 * Per RFC 6066, if an application negotiates a server name using an 390 * application protocol and then upgrades to TLS, and if a server_name 391 * extension is sent, then the extension SHOULD contain the same name 392 * that was negotiated in the application protocol. If the server_name 393 * is established in the TLS session handshake, the client SHOULD NOT 394 * attempt to request a different server name at the application layer. 395 * 396 * According to the above spec, we only need to check either the identity 397 * in server_name extension or the peer host of the connection. Peer host 398 * is not always a reliable fully qualified domain name. The HostName in 399 * server_name extension is more reliable than peer host. So we prefer 400 * the identity checking against the server_name extension if present, and 401 * may failover to peer host checking. 402 */ 403 static void checkIdentity(SSLSession session, 404 X509Certificate[] trustedChain, 405 String algorithm, 406 boolean checkClientTrusted) throws CertificateException { 407 408 // check if EE certificate chains to a public root CA (as 409 // pre-installed in cacerts) 410 boolean chainsToPublicCA = AnchorCertificates.contains( 411 trustedChain[trustedChain.length - 1]); 412 413 boolean identifiable = false; 414 String peerHost = session.getPeerHost(); 415 // Is it a Fully-Qualified Domain Names (FQDN) ending with a dot? 416 if (peerHost != null && peerHost.endsWith(".")) { 417 // Remove the ending dot, which is not allowed in SNIHostName. 418 peerHost = peerHost.substring(0, peerHost.length() - 1); 419 } 420 421 if (!checkClientTrusted) { 422 List<SNIServerName> sniNames = getRequestedServerNames(session); 423 String sniHostName = getHostNameInSNI(sniNames); 424 if (sniHostName != null) { 425 try { 426 checkIdentity(sniHostName, 427 trustedChain[0], algorithm, chainsToPublicCA); 428 identifiable = true; 429 } catch (CertificateException ce) { 430 if (sniHostName.equalsIgnoreCase(peerHost)) { 431 throw ce; 432 } 433 434 // otherwise, failover to check peer host 435 } 436 } 437 } 438 439 if (!identifiable) { 440 try { 441 checkIdentity(peerHost, 442 trustedChain[0], algorithm, chainsToPublicCA); 443 } catch(CertificateException ce) { 444 if (checkClientTrusted && "HTTPS".equalsIgnoreCase(algorithm)) { 445 throw new CertificateException("Endpoint Identification Algorithm " + 446 "HTTPS is not supported on the server side"); 447 } else { 448 throw ce; 449 } 450 } 451 } 452 } 453 454 /* 455 * Identify the peer by its certificate and hostname. 456 * 457 * Lifted from sun.net.www.protocol.https.HttpsClient. 458 */ 459 static void checkIdentity(String hostname, X509Certificate cert, 460 String algorithm) throws CertificateException { 461 checkIdentity(hostname, cert, algorithm, false); 462 } 463 464 private static void checkIdentity(String hostname, X509Certificate cert, 465 String algorithm, boolean chainsToPublicCA) 466 throws CertificateException { 467 if (algorithm != null && !algorithm.isEmpty()) { 468 // if IPv6 strip off the "[]" 469 if ((hostname != null) && hostname.startsWith("[") && 470 hostname.endsWith("]")) { 471 hostname = hostname.substring(1, hostname.length() - 1); 472 } 473 474 if (algorithm.equalsIgnoreCase("HTTPS")) { 475 HostnameChecker.getInstance(HostnameChecker.TYPE_TLS).match( 476 hostname, cert, chainsToPublicCA); 477 } else if (algorithm.equalsIgnoreCase("LDAP") || 478 algorithm.equalsIgnoreCase("LDAPS")) { 479 HostnameChecker.getInstance(HostnameChecker.TYPE_LDAP).match( 480 hostname, cert, chainsToPublicCA); 481 } else { 482 throw new CertificateException( 483 "Unknown identification algorithm: " + algorithm); 484 } 485 } 486 } 487 } 488