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
28package sun.net.www.protocol.https;
29
30import java.io.IOException;
31import java.io.UnsupportedEncodingException;
32import java.io.PrintStream;
33import java.io.BufferedOutputStream;
34import java.net.InetAddress;
35import java.net.Socket;
36import java.net.SocketException;
37import java.net.URL;
38import java.net.UnknownHostException;
39import java.net.InetSocketAddress;
40import java.net.Proxy;
41import java.security.Principal;
42import java.security.cert.*;
43import java.util.StringTokenizer;
44import java.util.Vector;
45import java.security.AccessController;
46
47import javax.security.auth.x500.X500Principal;
48
49import javax.net.ssl.*;
50import sun.net.www.http.HttpClient;
51import sun.net.www.protocol.http.HttpURLConnection;
52import sun.security.action.*;
53
54import sun.security.util.HostnameChecker;
55import sun.security.ssl.SSLSocketImpl;
56
57import sun.util.logging.PlatformLogger;
58import 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
109final 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
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
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
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
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
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