1 /*
   2  * Copyright (c) 1997, 2019, 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.<X509Certificate>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 isClient) {
 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 = null;
 160         if (isClient) {
 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, String authType,
 196                 Socket socket, boolean isClient) throws CertificateException {
 197         Validator v = checkTrustedInit(chain, authType, isClient);
 198 
 199         X509Certificate[] trustedChain = null;
 200         if ((socket != null) && socket.isConnected() &&
 201                                         (socket instanceof SSLSocket)) {
 202 
 203             SSLSocket sslSocket = (SSLSocket)socket;
 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 = new SSLAlgorithmConstraints(
 219                                 sslSocket, localSupportedSignAlgs, false);
 220             } else {
 221                 constraints = new SSLAlgorithmConstraints(sslSocket, false);
 222             }
 223 
 224             // Grab any stapled OCSP responses for use in validation
 225             List<byte[]> responseList = Collections.emptyList();
 226             if (!isClient && isExtSession) {
 227                 responseList =
 228                         ((ExtendedSSLSession)session).getStatusResponses();
 229             }
 230             trustedChain = v.validate(chain, null, responseList,
 231                     constraints, isClient ? null : authType);
 232 
 233             // check if EE certificate chains to a public root CA (as
 234             // pre-installed in cacerts)
 235             boolean chainsToPublicCA = AnchorCertificates.contains(
 236                     trustedChain[trustedChain.length-1]);
 237 
 238             // check endpoint identity
 239             String identityAlg = sslSocket.getSSLParameters().
 240                     getEndpointIdentificationAlgorithm();
 241             if (identityAlg != null && !identityAlg.isEmpty()) {
 242                 checkIdentity(session, trustedChain[0], identityAlg, isClient,
 243                         getRequestedServerNames(socket), chainsToPublicCA);
 244             }
 245         } else {
 246             trustedChain = v.validate(chain, null, Collections.emptyList(),
 247                     null, isClient ? null : authType);
 248         }
 249 
 250         if (SSLLogger.isOn && SSLLogger.isOn("ssl,trustmanager")) {
 251             SSLLogger.fine("Found trusted certificate",
 252                     trustedChain[trustedChain.length - 1]);
 253         }
 254     }
 255 
 256     private void checkTrusted(X509Certificate[] chain, String authType,
 257             SSLEngine engine, boolean isClient) throws CertificateException {
 258         Validator v = checkTrustedInit(chain, authType, isClient);
 259 
 260         X509Certificate[] trustedChain = null;
 261         if (engine != null) {
 262             SSLSession session = engine.getHandshakeSession();
 263             if (session == null) {
 264                 throw new CertificateException("No handshake session");
 265             }
 266 
 267             // create the algorithm constraints
 268             boolean isExtSession = (session instanceof ExtendedSSLSession);
 269             AlgorithmConstraints constraints;
 270             if (isExtSession &&
 271                     ProtocolVersion.useTLS12PlusSpec(session.getProtocol())) {
 272                 ExtendedSSLSession extSession = (ExtendedSSLSession)session;
 273                 String[] localSupportedSignAlgs =
 274                         extSession.getLocalSupportedSignatureAlgorithms();
 275 
 276                 constraints = new SSLAlgorithmConstraints(
 277                                 engine, localSupportedSignAlgs, false);
 278             } else {
 279                 constraints = new SSLAlgorithmConstraints(engine, false);
 280             }
 281 
 282             // Grab any stapled OCSP responses for use in validation
 283             List<byte[]> responseList = Collections.emptyList();
 284             if (!isClient && isExtSession) {
 285                 responseList =
 286                         ((ExtendedSSLSession)session).getStatusResponses();
 287             }
 288             trustedChain = v.validate(chain, null, responseList,
 289                     constraints, isClient ? null : authType);
 290 
 291             // check if EE certificate chains to a public root CA (as
 292             // pre-installed in cacerts)
 293             boolean chainsToPublicCA = AnchorCertificates.contains(
 294                     trustedChain[trustedChain.length-1]);
 295 
 296             // check endpoint identity
 297             String identityAlg = engine.getSSLParameters().
 298                     getEndpointIdentificationAlgorithm();
 299             if (identityAlg != null && !identityAlg.isEmpty()) {
 300                 checkIdentity(session, trustedChain[0], identityAlg, isClient,
 301                         getRequestedServerNames(engine), chainsToPublicCA);
 302             }
 303         } else {
 304             trustedChain = v.validate(chain, null, Collections.emptyList(),
 305                     null, isClient ? 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 
 364             SSLSocket sslSocket = (SSLSocket)socket;
 365             SSLSession session = sslSocket.getHandshakeSession();
 366 
 367             if (session != null && (session instanceof ExtendedSSLSession)) {
 368                 ExtendedSSLSession extSession = (ExtendedSSLSession)session;
 369                 return extSession.getRequestedServerNames();
 370             }
 371         }
 372 
 373         return Collections.<SNIServerName>emptyList();
 374     }
 375 
 376     // Also used by X509KeyManagerImpl
 377     static List<SNIServerName> getRequestedServerNames(SSLEngine engine) {
 378         if (engine != null) {
 379             SSLSession session = engine.getHandshakeSession();
 380 
 381             if (session != null && (session instanceof ExtendedSSLSession)) {
 382                 ExtendedSSLSession extSession = (ExtendedSSLSession)session;
 383                 return extSession.getRequestedServerNames();
 384             }
 385         }
 386 
 387         return Collections.<SNIServerName>emptyList();
 388     }
 389 
 390     /*
 391      * Per RFC 6066, if an application negotiates a server name using an
 392      * application protocol and then upgrades to TLS, and if a server_name
 393      * extension is sent, then the extension SHOULD contain the same name
 394      * that was negotiated in the application protocol.  If the server_name
 395      * is established in the TLS session handshake, the client SHOULD NOT
 396      * attempt to request a different server name at the application layer.
 397      *
 398      * According to the above spec, we only need to check either the identity
 399      * in server_name extension or the peer host of the connection.  Peer host
 400      * is not always a reliable fully qualified domain name. The HostName in
 401      * server_name extension is more reliable than peer host. So we prefer
 402      * the identity checking aginst the server_name extension if present, and
 403      * may failove to peer host checking.
 404      */
 405     private static void checkIdentity(SSLSession session,
 406             X509Certificate cert,
 407             String algorithm,
 408             boolean isClient,
 409             List<SNIServerName> sniNames,
 410             boolean chainsToPublicCA) throws CertificateException {
 411 
 412         boolean identifiable = false;
 413         String peerHost = session.getPeerHost();
 414         if (isClient) {
 415             String hostname = getHostNameInSNI(sniNames);
 416             if (hostname != null) {
 417                 try {
 418                     checkIdentity(hostname, cert, algorithm, chainsToPublicCA);
 419                     identifiable = true;
 420                 } catch (CertificateException ce) {
 421                     if (hostname.equalsIgnoreCase(peerHost)) {
 422                         throw ce;
 423                     }
 424 
 425                     // otherwisw, failover to check peer host
 426                 }
 427             }
 428         }
 429 
 430         if (!identifiable) {
 431             checkIdentity(peerHost, cert, algorithm, chainsToPublicCA);
 432         }
 433     }
 434 
 435     /*
 436      * Identify the peer by its certificate and hostname.
 437      *
 438      * Lifted from sun.net.www.protocol.https.HttpsClient.
 439      */
 440     static void checkIdentity(String hostname, X509Certificate cert,
 441             String algorithm) throws CertificateException {
 442         checkIdentity(hostname, cert, algorithm, false);
 443     }
 444 
 445     private static void checkIdentity(String hostname, X509Certificate cert,
 446             String algorithm, boolean chainsToPublicCA)
 447             throws CertificateException {
 448         if (algorithm != null && !algorithm.isEmpty()) {
 449             // if IPv6 strip off the "[]"
 450             if ((hostname != null) && hostname.startsWith("[") &&
 451                     hostname.endsWith("]")) {
 452                 hostname = hostname.substring(1, hostname.length() - 1);
 453             }
 454 
 455             if (algorithm.equalsIgnoreCase("HTTPS")) {
 456                 HostnameChecker.getInstance(HostnameChecker.TYPE_TLS).match(
 457                         hostname, cert, chainsToPublicCA);
 458             } else if (algorithm.equalsIgnoreCase("LDAP") ||
 459                     algorithm.equalsIgnoreCase("LDAPS")) {
 460                 HostnameChecker.getInstance(HostnameChecker.TYPE_LDAP).match(
 461                         hostname, cert, chainsToPublicCA);
 462             } else {
 463                 throw new CertificateException(
 464                         "Unknown identification algorithm: " + algorithm);
 465             }
 466         }
 467     }
 468 }
 469