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