1 /*
2  * Copyright (C) 2014 The Android Open Source Project
3  * Copyright (c) 2001, 2010, Oracle and/or its affiliates. All rights reserved.
4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5  *
6  * This code is free software; you can redistribute it and/or modify it
7  * under the terms of the GNU General Public License version 2 only, as
8  * published by the Free Software Foundation.  Oracle designates this
9  * particular file as subject to the "Classpath" exception as provided
10  * by Oracle in the LICENSE file that accompanied this code.
11  *
12  * This code is distributed in the hope that it will be useful, but WITHOUT
13  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
15  * version 2 for more details (a copy is included in the LICENSE file that
16  * accompanied this code).
17  *
18  * You should have received a copy of the GNU General Public License version
19  * 2 along with this work; if not, write to the Free Software Foundation,
20  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
21  *
22  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
23  * or visit www.oracle.com if you need additional information or have any
24  * questions.
25  */
26 
27 
28 package sun.net.www.protocol.https;
29 
30 import java.io.IOException;
31 import java.io.UnsupportedEncodingException;
32 import java.io.PrintStream;
33 import java.io.BufferedOutputStream;
34 import java.net.InetAddress;
35 import java.net.Socket;
36 import java.net.SocketException;
37 import java.net.URL;
38 import java.net.UnknownHostException;
39 import java.net.InetSocketAddress;
40 import java.net.Proxy;
41 import java.security.Principal;
42 import java.security.cert.*;
43 import java.util.StringTokenizer;
44 import java.util.Vector;
45 import java.security.AccessController;
46 
47 import javax.security.auth.x500.X500Principal;
48 
49 import javax.net.ssl.*;
50 import sun.net.www.http.HttpClient;
51 import sun.net.www.protocol.http.HttpURLConnection;
52 import sun.security.action.*;
53 
54 import sun.security.util.HostnameChecker;
55 import sun.security.ssl.SSLSocketImpl;
56 
57 import sun.util.logging.PlatformLogger;
58 import static sun.net.www.protocol.http.HttpURLConnection.TunnelState.*;
59 
60 
61 /**
62  * This class provides HTTPS client URL support, building on the standard
63  * "sun.net.www" HTTP protocol handler.  HTTPS is the same protocol as HTTP,
64  * but differs in the transport layer which it uses:  <UL>
65  *
66  *      <LI>There's a <em>Secure Sockets Layer</em> between TCP
67  *      and the HTTP protocol code.
68  *
69  *      <LI>It uses a different default TCP port.
70  *
71  *      <LI>It doesn't use application level proxies, which can see and
72  *      manipulate HTTP user level data, compromising privacy.  It uses
73  *      low level tunneling instead, which hides HTTP protocol and data
74  *      from all third parties.  (Traffic analysis is still possible).
75  *
76  *      <LI>It does basic server authentication, to protect
77  *      against "URL spoofing" attacks.  This involves deciding
78  *      whether the X.509 certificate chain identifying the server
79  *      is trusted, and verifying that the name of the server is
80  *      found in the certificate.  (The application may enable an
81  *      anonymous SSL cipher suite, and such checks are not done
82  *      for anonymous ciphers.)
83  *
84  *      <LI>It exposes key SSL session attributes, specifically the
85  *      cipher suite in use and the server's X509 certificates, to
86  *      application software which knows about this protocol handler.
87  *
88  *      </UL>
89  *
90  * <P> System properties used include:  <UL>
91  *
92  *      <LI><em>https.proxyHost</em> ... the host supporting SSL
93  *      tunneling using the conventional CONNECT syntax
94  *
95  *      <LI><em>https.proxyPort</em> ... port to use on proxyHost
96  *
97  *      <LI><em>https.cipherSuites</em> ... comma separated list of
98  *      SSL cipher suite names to enable.
99  *
100  *      <LI><em>http.nonProxyHosts</em> ...
101  *
102  *      </UL>
103  *
104  * @author David Brownell
105  * @author Bill Foote
106  */
107 
108 // final for export control reasons (access to APIs); remove with care
109 final class HttpsClient extends HttpClient
110     implements HandshakeCompletedListener
111 {
112     // STATIC STATE and ACCESSORS THERETO
113 
114     // HTTPS uses a different default port number than HTTP.
115     private static final int    httpsPortNumber = 443;
116 
117     // default HostnameVerifier class canonical name
118     private static final String defaultHVCanonicalName =
119             "javax.net.ssl.DefaultHostnameVerifier";
120 
121     /** Returns the default HTTPS port (443) */
122     @Override
getDefaultPort()123     protected int getDefaultPort() { return httpsPortNumber; }
124 
125     private HostnameVerifier hv;
126     private SSLSocketFactory sslSocketFactory;
127 
128     // HttpClient.proxyDisabled will always be false, because we don't
129     // use an application-level HTTP proxy.  We might tunnel through
130     // our http proxy, though.
131 
132 
133     // INSTANCE DATA
134 
135     // last negotiated SSL session
136     private SSLSession  session;
137 
getCipherSuites()138     private String [] getCipherSuites() {
139         //
140         // If ciphers are assigned, sort them into an array.
141         //
142         String ciphers [];
143         String cipherString = AccessController.doPrivileged(
144                 new GetPropertyAction("https.cipherSuites"));
145 
146         if (cipherString == null || "".equals(cipherString)) {
147             ciphers = null;
148         } else {
149             StringTokenizer     tokenizer;
150             Vector<String>      v = new Vector<String>();
151 
152             tokenizer = new StringTokenizer(cipherString, ",");
153             while (tokenizer.hasMoreTokens())
154                 v.addElement(tokenizer.nextToken());
155             ciphers = new String [v.size()];
156             for (int i = 0; i < ciphers.length; i++)
157                 ciphers [i] = v.elementAt(i);
158         }
159         return ciphers;
160     }
161 
getProtocols()162     private String [] getProtocols() {
163         //
164         // If protocols are assigned, sort them into an array.
165         //
166         String protocols [];
167         String protocolString = AccessController.doPrivileged(
168                 new GetPropertyAction("https.protocols"));
169 
170         if (protocolString == null || "".equals(protocolString)) {
171             protocols = null;
172         } else {
173             StringTokenizer     tokenizer;
174             Vector<String>      v = new Vector<String>();
175 
176             tokenizer = new StringTokenizer(protocolString, ",");
177             while (tokenizer.hasMoreTokens())
178                 v.addElement(tokenizer.nextToken());
179             protocols = new String [v.size()];
180             for (int i = 0; i < protocols.length; i++) {
181                 protocols [i] = v.elementAt(i);
182             }
183         }
184         return protocols;
185     }
186 
getUserAgent()187     private String getUserAgent() {
188         String userAgent = java.security.AccessController.doPrivileged(
189                 new sun.security.action.GetPropertyAction("https.agent"));
190         if (userAgent == null || userAgent.length() == 0) {
191             userAgent = "JSSE";
192         }
193         return userAgent;
194     }
195 
196     // should remove once HttpClient.newHttpProxy is putback
newHttpProxy(String proxyHost, int proxyPort)197     private static Proxy newHttpProxy(String proxyHost, int proxyPort) {
198         InetSocketAddress saddr = null;
199         final String phost = proxyHost;
200         final int pport = proxyPort < 0 ? httpsPortNumber : proxyPort;
201         try {
202             saddr = java.security.AccessController.doPrivileged(new
203                 java.security.PrivilegedExceptionAction<InetSocketAddress>() {
204                 public InetSocketAddress run() {
205                     return new InetSocketAddress(phost, pport);
206                 }});
207         } catch (java.security.PrivilegedActionException pae) {
208         }
209         return new Proxy(Proxy.Type.HTTP, saddr);
210     }
211 
212     // CONSTRUCTOR, FACTORY
213 
214 
215     /**
216      * Create an HTTPS client URL.  Traffic will be tunneled through any
217      * intermediate nodes rather than proxied, so that confidentiality
218      * of data exchanged can be preserved.  However, note that all the
219      * anonymous SSL flavors are subject to "person-in-the-middle"
220      * attacks against confidentiality.  If you enable use of those
221      * flavors, you may be giving up the protection you get through
222      * SSL tunneling.
223      *
224      * Use New to get new HttpsClient. This constructor is meant to be
225      * used only by New method. New properly checks for URL spoofing.
226      *
227      * @param URL https URL with which a connection must be established
228      */
229     private HttpsClient(SSLSocketFactory sf, URL url)
230     throws IOException
231     {
232         // HttpClient-level proxying is always disabled,
233         // because we override doConnect to do tunneling instead.
234         this(sf, url, (String)null, -1);
235     }
236 
237     /**
238      *  Create an HTTPS client URL.  Traffic will be tunneled through
239      * the specified proxy server.
240      */
241     HttpsClient(SSLSocketFactory sf, URL url, String proxyHost, int proxyPort)
242         throws IOException {
243         this(sf, url, proxyHost, proxyPort, -1);
244     }
245 
246     /**
247      *  Create an HTTPS client URL.  Traffic will be tunneled through
248      * the specified proxy server, with a connect timeout
249      */
250     HttpsClient(SSLSocketFactory sf, URL url, String proxyHost, int proxyPort,
251                 int connectTimeout)
252         throws IOException {
253         this(sf, url,
254              (proxyHost == null? null:
255                 HttpsClient.newHttpProxy(proxyHost, proxyPort)),
256                 connectTimeout);
257     }
258 
259     /**
260      *  Same as previous constructor except using a Proxy
261      */
262     HttpsClient(SSLSocketFactory sf, URL url, Proxy proxy,
263                 int connectTimeout)
264         throws IOException {
265         this.proxy = proxy;
266         setSSLSocketFactory(sf);
267         this.proxyDisabled = true;
268 
269         this.host = url.getHost();
270         this.url = url;
271         port = url.getPort();
272         if (port == -1) {
273             port = getDefaultPort();
274         }
275         setConnectTimeout(connectTimeout);
276         openServer();
277     }
278 
279 
280     // This code largely ripped off from HttpClient.New, and
281     // it uses the same keepalive cache.
282 
283     static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv,
284                           HttpURLConnection httpuc)
285             throws IOException {
286         return HttpsClient.New(sf, url, hv, true, httpuc);
287     }
288 
289     /** See HttpClient for the model for this method. */
290     static HttpClient New(SSLSocketFactory sf, URL url,
291             HostnameVerifier hv, boolean useCache,
292             HttpURLConnection httpuc) throws IOException {
293         return HttpsClient.New(sf, url, hv, (String)null, -1, useCache, httpuc);
294     }
295 
296     /**
297      * Get a HTTPS client to the URL.  Traffic will be tunneled through
298      * the specified proxy server.
299      */
300     static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv,
301                            String proxyHost, int proxyPort,
302                            HttpURLConnection httpuc) throws IOException {
303         return HttpsClient.New(sf, url, hv, proxyHost, proxyPort, true, httpuc);
304     }
305 
306     static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv,
307                            String proxyHost, int proxyPort, boolean useCache,
308                            HttpURLConnection httpuc)
309         throws IOException {
310         return HttpsClient.New(sf, url, hv, proxyHost, proxyPort, useCache, -1,
311                                httpuc);
312     }
313 
314     static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv,
315                           String proxyHost, int proxyPort, boolean useCache,
316                           int connectTimeout, HttpURLConnection httpuc)
317         throws IOException {
318 
319         return HttpsClient.New(sf, url, hv,
320                                (proxyHost == null? null :
321                                 HttpsClient.newHttpProxy(proxyHost, proxyPort)),
322                                useCache, connectTimeout, httpuc);
323     }
324 
325     static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv,
326                           Proxy p, boolean useCache,
327                           int connectTimeout, HttpURLConnection httpuc)
328         throws IOException
329     {
330         if (p == null) {
331             p = Proxy.NO_PROXY;
332         }
333         HttpsClient ret = null;
334         if (useCache) {
335             /* see if one's already around */
336             ret = (HttpsClient) kac.get(url, sf);
337             if (ret != null && httpuc != null &&
338                 httpuc.streaming() &&
339                 httpuc.getRequestMethod() == "POST") {
340                 if (!ret.available())
341                     ret = null;
342             }
343 
344             if (ret != null) {
345                 if ((ret.proxy != null && ret.proxy.equals(p)) ||
346                     (ret.proxy == null && p == null)) {
347                     synchronized (ret) {
348                         ret.cachedHttpClient = true;
349                         assert ret.inCache;
350                         ret.inCache = false;
351                         if (httpuc != null && ret.needsTunneling())
352                             httpuc.setTunnelState(TUNNELING);
353                         PlatformLogger logger = HttpURLConnection.getHttpLogger();
354                         if (logger.isLoggable(PlatformLogger.FINEST)) {
355                             logger.finest("KeepAlive stream retrieved from the cache, " + ret);
356                         }
357                     }
358                 } else {
359                     // We cannot return this connection to the cache as it's
360                     // KeepAliveTimeout will get reset. We simply close the connection.
361                     // This should be fine as it is very rare that a connection
362                     // to the same host will not use the same proxy.
363                     synchronized(ret) {
364                         ret.inCache = false;
365                         ret.closeServer();
366                     }
367                     ret = null;
368                 }
369             }
370         }
371         if (ret == null) {
372             ret = new HttpsClient(sf, url, p, connectTimeout);
373         } else {
374             SecurityManager security = System.getSecurityManager();
375             if (security != null) {
376                 if (ret.proxy == Proxy.NO_PROXY || ret.proxy == null) {
377                     security.checkConnect(InetAddress.getByName(url.getHost()).getHostAddress(), url.getPort());
378                 } else {
379                     security.checkConnect(url.getHost(), url.getPort());
380                 }
381             }
382             ret.url = url;
383         }
384         ret.setHostnameVerifier(hv);
385 
386         return ret;
387     }
388 
389     // METHODS
390     void setHostnameVerifier(HostnameVerifier hv) {
391         this.hv = hv;
392     }
393 
394     void setSSLSocketFactory(SSLSocketFactory sf) {
395         sslSocketFactory = sf;
396     }
397 
398     SSLSocketFactory getSSLSocketFactory() {
399         return sslSocketFactory;
400     }
401 
402     /**
403      * The following method, createSocket, is defined in NetworkClient
404      * and overridden here so that the socket facroty is used to create
405      * new sockets.
406      */
407     @Override
408     protected Socket createSocket() throws IOException {
409         try {
410             return sslSocketFactory.createSocket();
411         } catch (SocketException se) {
412             //
413             // bug 6771432
414             // javax.net.SocketFactory throws a SocketException with an
415             // UnsupportedOperationException as its cause to indicate that
416             // unconnected sockets have not been implemented.
417             //
418             Throwable t = se.getCause();
419             if (t != null && t instanceof UnsupportedOperationException) {
420                 return super.createSocket();
421             } else {
422                 throw se;
423             }
424         }
425     }
426 
427 
428     @Override
429     public boolean needsTunneling() {
430         return (proxy != null && proxy.type() != Proxy.Type.DIRECT
431                 && proxy.type() != Proxy.Type.SOCKS);
432     }
433 
434     @Override
435     public void afterConnect() throws IOException, UnknownHostException {
436         if (!isCachedConnection()) {
437             SSLSocket s = null;
438             SSLSocketFactory factory = sslSocketFactory;
439             try {
440                 if (!(serverSocket instanceof SSLSocket)) {
441                     s = (SSLSocket)factory.createSocket(serverSocket,
442                                                         host, port, true);
443                 } else {
444                     s = (SSLSocket)serverSocket;
445                     if (s instanceof SSLSocketImpl) {
446                         ((SSLSocketImpl)s).setHost(host);
447                     }
448                 }
449             } catch (IOException ex) {
450                 // If we fail to connect through the tunnel, try it
451                 // locally, as a last resort.  If this doesn't work,
452                 // throw the original exception.
453                 try {
454                     s = (SSLSocket)factory.createSocket(host, port);
455                 } catch (IOException ignored) {
456                     throw ex;
457                 }
458             }
459 
460             //
461             // Force handshaking, so that we get any authentication.
462             // Register a handshake callback so our session state tracks any
463             // later session renegotiations.
464             //
465             String [] protocols = getProtocols();
466             String [] ciphers = getCipherSuites();
467             if (protocols != null) {
468                 s.setEnabledProtocols(protocols);
469             }
470             if (ciphers != null) {
471                 s.setEnabledCipherSuites(ciphers);
472             }
473             s.addHandshakeCompletedListener(this);
474 
475             // We have two hostname verification approaches. One is in
476             // SSL/TLS socket layer, where the algorithm is configured with
477             // SSLParameters.setEndpointIdentificationAlgorithm(), and the
478             // hostname verification is done by X509ExtendedTrustManager when
479             // the algorithm is "HTTPS". The other one is in HTTPS layer,
480             // where the algorithm is customized by
481             // HttpsURLConnection.setHostnameVerifier(), and the hostname
482             // verification is done by HostnameVerifier when the default
483             // rules for hostname verification fail.
484             //
485             // The relationship between two hostname verification approaches
486             // likes the following:
487             //
488             //               |             EIA algorithm
489             //               +----------------------------------------------
490             //               |     null      |   HTTPS    |   LDAP/other   |
491             // -------------------------------------------------------------
492             //     |         |1              |2           |3               |
493             // HNV | default | Set HTTPS EIA | use EIA    | HTTPS          |
494             //     |--------------------------------------------------------
495             //     | non -   |4              |5           |6               |
496             //     | default | HTTPS/HNV     | use EIA    | HTTPS/HNV      |
497             // -------------------------------------------------------------
498             //
499             // Abbreviation:
500             //     EIA: the endpoint identification algorithm in SSL/TLS
501             //           socket layer
502             //     HNV: the hostname verification object in HTTPS layer
503             // Notes:
504             //     case 1. default HNV and EIA is null
505             //           Set EIA as HTTPS, hostname check done in SSL/TLS
506             //           layer.
507             //     case 2. default HNV and EIA is HTTPS
508             //           Use existing EIA, hostname check done in SSL/TLS
509             //           layer.
510             //     case 3. default HNV and EIA is other than HTTPS
511             //           Use existing EIA, EIA check done in SSL/TLS
512             //           layer, then do HTTPS check in HTTPS layer.
513             //     case 4. non-default HNV and EIA is null
514             //           No EIA, no EIA check done in SSL/TLS layer, then do
515             //           HTTPS check in HTTPS layer using HNV as override.
516             //     case 5. non-default HNV and EIA is HTTPS
517             //           Use existing EIA, hostname check done in SSL/TLS
518             //           layer. No HNV override possible. We will review this
519             //           decision and may update the architecture for JDK 7.
520             //     case 6. non-default HNV and EIA is other than HTTPS
521             //           Use existing EIA, EIA check done in SSL/TLS layer,
522             //           then do HTTPS check in HTTPS layer as override.
523             boolean needToCheckSpoofing = true;
524             String identification =
525                 s.getSSLParameters().getEndpointIdentificationAlgorithm();
526             if (identification != null && identification.length() != 0) {
527                 if (identification.equalsIgnoreCase("HTTPS")) {
528                     // Do not check server identity again out of SSLSocket,
529                     // the endpoint will be identified during TLS handshaking
530                     // in SSLSocket.
531                     needToCheckSpoofing = false;
532                 }   // else, we don't understand the identification algorithm,
533                     // need to check URL spoofing here.
534             } else {
535                 boolean isDefaultHostnameVerifier = false;
536 
537                 // We prefer to let the SSLSocket do the spoof checks, but if
538                 // the application has specified a HostnameVerifier (HNV),
539                 // we will always use that.
540                 if (hv != null) {
541                     String canonicalName = hv.getClass().getCanonicalName();
542                     if (canonicalName != null &&
543                     canonicalName.equalsIgnoreCase(defaultHVCanonicalName)) {
544                         isDefaultHostnameVerifier = true;
545                     }
546                 } else {
547                     // Unlikely to happen! As the behavior is the same as the
548                     // default hostname verifier, so we prefer to let the
549                     // SSLSocket do the spoof checks.
550                     isDefaultHostnameVerifier = true;
551                 }
552 
553                 if (isDefaultHostnameVerifier) {
554                     // If the HNV is the default from HttpsURLConnection, we
555                     // will do the spoof checks in SSLSocket.
556                     SSLParameters paramaters = s.getSSLParameters();
557                     paramaters.setEndpointIdentificationAlgorithm("HTTPS");
558                     s.setSSLParameters(paramaters);
559 
560                     needToCheckSpoofing = false;
561                 }
562             }
563 
564             s.startHandshake();
565             session = s.getSession();
566             // change the serverSocket and serverOutput
567             serverSocket = s;
568             try {
569                 serverOutput = new PrintStream(
570                     new BufferedOutputStream(serverSocket.getOutputStream()),
571                     false, encoding);
572             } catch (UnsupportedEncodingException e) {
573                 throw new InternalError(encoding+" encoding not found");
574             }
575 
576             // check URL spoofing if it has not been checked under handshaking
577             if (needToCheckSpoofing) {
578                 checkURLSpoofing(hv);
579             }
580         } else {
581             // if we are reusing a cached https session,
582             // we don't need to do handshaking etc. But we do need to
583             // set the ssl session
584             session = ((SSLSocket)serverSocket).getSession();
585         }
586     }
587 
588     // Server identity checking is done according to RFC 2818: HTTP over TLS
589     // Section 3.1 Server Identity
590     private void checkURLSpoofing(HostnameVerifier hostnameVerifier)
591             throws IOException {
592         //
593         // Get authenticated server name, if any
594         //
595         String host = url.getHost();
596 
597         // if IPv6 strip off the "[]"
598         if (host != null && host.startsWith("[") && host.endsWith("]")) {
599             host = host.substring(1, host.length()-1);
600         }
601 
602         Certificate[] peerCerts = null;
603         String cipher = session.getCipherSuite();
604         try {
605             HostnameChecker checker = HostnameChecker.getInstance(
606                                                 HostnameChecker.TYPE_TLS);
607 
608             // Use ciphersuite to determine whether Kerberos is present.
609             if (cipher.startsWith("TLS_KRB5")) {
610                 if (!HostnameChecker.match(host, getPeerPrincipal())) {
611                     throw new SSLPeerUnverifiedException("Hostname checker" +
612                                 " failed for Kerberos");
613                 }
614             } else { // X.509
615 
616                 // get the subject's certificate
617                 peerCerts = session.getPeerCertificates();
618 
619                 X509Certificate peerCert;
620                 if (peerCerts[0] instanceof
621                         java.security.cert.X509Certificate) {
622                     peerCert = (java.security.cert.X509Certificate)peerCerts[0];
623                 } else {
624                     throw new SSLPeerUnverifiedException("");
625                 }
626                 checker.match(host, peerCert);
627             }
628 
629             // if it doesn't throw an exception, we passed. Return.
630             return;
631 
632         } catch (SSLPeerUnverifiedException e) {
633 
634             //
635             // client explicitly changed default policy and enabled
636             // anonymous ciphers; we can't check the standard policy
637             //
638             // ignore
639         } catch (java.security.cert.CertificateException cpe) {
640             // ignore
641         }
642 
643         if ((cipher != null) && (cipher.indexOf("_anon_") != -1)) {
644             return;
645         } else if ((hostnameVerifier != null) &&
646                    (hostnameVerifier.verify(host, session))) {
647             return;
648         }
649 
650         serverSocket.close();
651         session.invalidate();
652 
653         throw new IOException("HTTPS hostname wrong:  should be <"
654                               + url.getHost() + ">");
655     }
656 
657     @Override
658     protected void putInKeepAliveCache() {
659         if (inCache) {
660             assert false : "Duplicate put to keep alive cache";
661             return;
662         }
663         inCache = true;
664         kac.put(url, sslSocketFactory, this);
665     }
666 
667     /*
668      * Close an idle connection to this URL (if it exists in the cache).
669      */
670     @Override
671     public void closeIdleConnection() {
672         HttpClient http = (HttpClient) kac.get(url, sslSocketFactory);
673         if (http != null) {
674             http.closeServer();
675         }
676     }
677 
678     /**
679      * Returns the cipher suite in use on this connection.
680      */
681     String getCipherSuite() {
682         return session.getCipherSuite();
683     }
684 
685     /**
686      * Returns the certificate chain the client sent to the
687      * server, or null if the client did not authenticate.
688      */
689     public java.security.cert.Certificate [] getLocalCertificates() {
690         return session.getLocalCertificates();
691     }
692 
693     /**
694      * Returns the certificate chain with which the server
695      * authenticated itself, or throw a SSLPeerUnverifiedException
696      * if the server did not authenticate.
697      */
698     java.security.cert.Certificate [] getServerCertificates()
699             throws SSLPeerUnverifiedException
700     {
701         return session.getPeerCertificates();
702     }
703 
704     /**
705      * Returns the X.509 certificate chain with which the server
706      * authenticated itself, or null if the server did not authenticate.
707      */
708     javax.security.cert.X509Certificate [] getServerCertificateChain()
709             throws SSLPeerUnverifiedException
710     {
711         return session.getPeerCertificateChain();
712     }
713 
714     /**
715      * Returns the principal with which the server authenticated
716      * itself, or throw a SSLPeerUnverifiedException if the
717      * server did not authenticate.
718      */
719     Principal getPeerPrincipal()
720             throws SSLPeerUnverifiedException
721     {
722         Principal principal;
723         try {
724             principal = session.getPeerPrincipal();
725         } catch (AbstractMethodError e) {
726             // if the provider does not support it, fallback to peer certs.
727             // return the X500Principal of the end-entity cert.
728             java.security.cert.Certificate[] certs =
729                         session.getPeerCertificates();
730             principal = (X500Principal)
731                 ((X509Certificate)certs[0]).getSubjectX500Principal();
732         }
733         return principal;
734     }
735 
736     /**
737      * Returns the principal the client sent to the
738      * server, or null if the client did not authenticate.
739      */
740     Principal getLocalPrincipal()
741     {
742         Principal principal;
743         try {
744             principal = session.getLocalPrincipal();
745         } catch (AbstractMethodError e) {
746             principal = null;
747             // if the provider does not support it, fallback to local certs.
748             // return the X500Principal of the end-entity cert.
749             java.security.cert.Certificate[] certs =
750                         session.getLocalCertificates();
751             if (certs != null) {
752                 principal = (X500Principal)
753                     ((X509Certificate)certs[0]).getSubjectX500Principal();
754             }
755         }
756         return principal;
757     }
758 
759     /**
760      * This method implements the SSL HandshakeCompleted callback,
761      * remembering the resulting session so that it may be queried
762      * for the current cipher suite and peer certificates.  Servers
763      * sometimes re-initiate handshaking, so the session in use on
764      * a given connection may change.  When sessions change, so may
765      * peer identities and cipher suites.
766      */
767     public void handshakeCompleted(HandshakeCompletedEvent event)
768     {
769         session = event.getSession();
770     }
771 
772     /**
773      * @return the proxy host being used for this client, or null
774      *          if we're not going through a proxy
775      */
776     @Override
777     public String getProxyHostUsed() {
778         if (!needsTunneling()) {
779             return null;
780         } else {
781             return super.getProxyHostUsed();
782         }
783     }
784 
785     /**
786      * @return the proxy port being used for this client.  Meaningless
787      *          if getProxyHostUsed() gives null.
788      */
789     @Override
790     public int getProxyPortUsed() {
791         return (proxy == null || proxy.type() == Proxy.Type.DIRECT ||
792                 proxy.type() == Proxy.Type.SOCKS)? -1:
793             ((InetSocketAddress)proxy.address()).getPort();
794     }
795 }
796