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