| /* |
| * Copyright (c) 2001, 2015, Oracle and/or its affiliates. All rights reserved. |
| * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. |
| * |
| * This code is free software; you can redistribute it and/or modify it |
| * under the terms of the GNU General Public License version 2 only, as |
| * published by the Free Software Foundation. Oracle designates this |
| * particular file as subject to the "Classpath" exception as provided |
| * by Oracle in the LICENSE file that accompanied this code. |
| * |
| * This code is distributed in the hope that it will be useful, but WITHOUT |
| * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or |
| * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License |
| * version 2 for more details (a copy is included in the LICENSE file that |
| * accompanied this code). |
| * |
| * You should have received a copy of the GNU General Public License version |
| * 2 along with this work; if not, write to the Free Software Foundation, |
| * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. |
| * |
| * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA |
| * or visit www.oracle.com if you need additional information or have any |
| * questions. |
| */ |
| |
| |
| package sun.net.www.protocol.https; |
| |
| import java.io.IOException; |
| import java.io.UnsupportedEncodingException; |
| import java.io.PrintStream; |
| import java.io.BufferedOutputStream; |
| import java.net.InetAddress; |
| import java.net.Socket; |
| import java.net.SocketException; |
| import java.net.URL; |
| import java.net.UnknownHostException; |
| import java.net.InetSocketAddress; |
| import java.net.Proxy; |
| import java.security.Principal; |
| import java.security.cert.*; |
| import java.util.StringTokenizer; |
| import java.util.Vector; |
| |
| import javax.security.auth.x500.X500Principal; |
| |
| import javax.net.ssl.*; |
| import sun.net.www.http.HttpClient; |
| import sun.net.www.protocol.http.HttpURLConnection; |
| import sun.security.action.*; |
| |
| import sun.security.util.HostnameChecker; |
| import sun.security.ssl.SSLSocketImpl; |
| |
| import sun.util.logging.PlatformLogger; |
| import static sun.net.www.protocol.http.HttpURLConnection.TunnelState.*; |
| |
| |
| /** |
| * This class provides HTTPS client URL support, building on the standard |
| * "sun.net.www" HTTP protocol handler. HTTPS is the same protocol as HTTP, |
| * but differs in the transport layer which it uses: <UL> |
| * |
| * <LI>There's a <em>Secure Sockets Layer</em> between TCP |
| * and the HTTP protocol code. |
| * |
| * <LI>It uses a different default TCP port. |
| * |
| * <LI>It doesn't use application level proxies, which can see and |
| * manipulate HTTP user level data, compromising privacy. It uses |
| * low level tunneling instead, which hides HTTP protocol and data |
| * from all third parties. (Traffic analysis is still possible). |
| * |
| * <LI>It does basic server authentication, to protect |
| * against "URL spoofing" attacks. This involves deciding |
| * whether the X.509 certificate chain identifying the server |
| * is trusted, and verifying that the name of the server is |
| * found in the certificate. (The application may enable an |
| * anonymous SSL cipher suite, and such checks are not done |
| * for anonymous ciphers.) |
| * |
| * <LI>It exposes key SSL session attributes, specifically the |
| * cipher suite in use and the server's X509 certificates, to |
| * application software which knows about this protocol handler. |
| * |
| * </UL> |
| * |
| * <P> System properties used include: <UL> |
| * |
| * <LI><em>https.proxyHost</em> ... the host supporting SSL |
| * tunneling using the conventional CONNECT syntax |
| * |
| * <LI><em>https.proxyPort</em> ... port to use on proxyHost |
| * |
| * <LI><em>https.cipherSuites</em> ... comma separated list of |
| * SSL cipher suite names to enable. |
| * |
| * <LI><em>http.nonProxyHosts</em> ... |
| * |
| * </UL> |
| * |
| * @author David Brownell |
| * @author Bill Foote |
| */ |
| |
| // final for export control reasons (access to APIs); remove with care |
| final class HttpsClient extends HttpClient |
| implements HandshakeCompletedListener |
| { |
| // STATIC STATE and ACCESSORS THERETO |
| |
| // HTTPS uses a different default port number than HTTP. |
| private static final int httpsPortNumber = 443; |
| |
| // default HostnameVerifier class canonical name |
| private static final String defaultHVCanonicalName = |
| "javax.net.ssl.HttpsURLConnection.DefaultHostnameVerifier"; |
| |
| /** Returns the default HTTPS port (443) */ |
| @Override |
| protected int getDefaultPort() { return httpsPortNumber; } |
| |
| private HostnameVerifier hv; |
| private SSLSocketFactory sslSocketFactory; |
| |
| // HttpClient.proxyDisabled will always be false, because we don't |
| // use an application-level HTTP proxy. We might tunnel through |
| // our http proxy, though. |
| |
| |
| // INSTANCE DATA |
| |
| // last negotiated SSL session |
| private SSLSession session; |
| |
| private String [] getCipherSuites() { |
| // |
| // If ciphers are assigned, sort them into an array. |
| // |
| String ciphers []; |
| String cipherString = |
| GetPropertyAction.getProperty("https.cipherSuites"); |
| |
| if (cipherString == null || "".equals(cipherString)) { |
| ciphers = null; |
| } else { |
| StringTokenizer tokenizer; |
| Vector<String> v = new Vector<String>(); |
| |
| tokenizer = new StringTokenizer(cipherString, ","); |
| while (tokenizer.hasMoreTokens()) |
| v.addElement(tokenizer.nextToken()); |
| ciphers = new String [v.size()]; |
| for (int i = 0; i < ciphers.length; i++) |
| ciphers [i] = v.elementAt(i); |
| } |
| return ciphers; |
| } |
| |
| private String [] getProtocols() { |
| // |
| // If protocols are assigned, sort them into an array. |
| // |
| String protocols []; |
| String protocolString = |
| GetPropertyAction.getProperty("https.protocols"); |
| |
| if (protocolString == null || "".equals(protocolString)) { |
| protocols = null; |
| } else { |
| StringTokenizer tokenizer; |
| Vector<String> v = new Vector<String>(); |
| |
| tokenizer = new StringTokenizer(protocolString, ","); |
| while (tokenizer.hasMoreTokens()) |
| v.addElement(tokenizer.nextToken()); |
| protocols = new String [v.size()]; |
| for (int i = 0; i < protocols.length; i++) { |
| protocols [i] = v.elementAt(i); |
| } |
| } |
| return protocols; |
| } |
| |
| private String getUserAgent() { |
| String userAgent = GetPropertyAction.getProperty("https.agent"); |
| if (userAgent == null || userAgent.length() == 0) { |
| userAgent = "JSSE"; |
| } |
| return userAgent; |
| } |
| |
| // CONSTRUCTOR, FACTORY |
| |
| |
| /** |
| * Create an HTTPS client URL. Traffic will be tunneled through any |
| * intermediate nodes rather than proxied, so that confidentiality |
| * of data exchanged can be preserved. However, note that all the |
| * anonymous SSL flavors are subject to "person-in-the-middle" |
| * attacks against confidentiality. If you enable use of those |
| * flavors, you may be giving up the protection you get through |
| * SSL tunneling. |
| * |
| * Use New to get new HttpsClient. This constructor is meant to be |
| * used only by New method. New properly checks for URL spoofing. |
| * |
| * @param URL https URL with which a connection must be established |
| */ |
| private HttpsClient(SSLSocketFactory sf, URL url) |
| throws IOException |
| { |
| // HttpClient-level proxying is always disabled, |
| // because we override doConnect to do tunneling instead. |
| this(sf, url, (String)null, -1); |
| } |
| |
| /** |
| * Create an HTTPS client URL. Traffic will be tunneled through |
| * the specified proxy server. |
| */ |
| HttpsClient(SSLSocketFactory sf, URL url, String proxyHost, int proxyPort) |
| throws IOException { |
| this(sf, url, proxyHost, proxyPort, -1); |
| } |
| |
| /** |
| * Create an HTTPS client URL. Traffic will be tunneled through |
| * the specified proxy server, with a connect timeout |
| */ |
| HttpsClient(SSLSocketFactory sf, URL url, String proxyHost, int proxyPort, |
| int connectTimeout) |
| throws IOException { |
| this(sf, url, |
| (proxyHost == null? null: |
| HttpClient.newHttpProxy(proxyHost, proxyPort, "https")), |
| connectTimeout); |
| } |
| |
| /** |
| * Same as previous constructor except using a Proxy |
| */ |
| HttpsClient(SSLSocketFactory sf, URL url, Proxy proxy, |
| int connectTimeout) |
| throws IOException { |
| PlatformLogger logger = HttpURLConnection.getHttpLogger(); |
| if (logger.isLoggable(PlatformLogger.Level.FINEST)) { |
| logger.finest("Creating new HttpsClient with url:" + url + " and proxy:" + proxy + |
| " with connect timeout:" + connectTimeout); |
| } |
| this.proxy = proxy; |
| setSSLSocketFactory(sf); |
| this.proxyDisabled = true; |
| |
| this.host = url.getHost(); |
| this.url = url; |
| port = url.getPort(); |
| if (port == -1) { |
| port = getDefaultPort(); |
| } |
| setConnectTimeout(connectTimeout); |
| openServer(); |
| } |
| |
| |
| // This code largely ripped off from HttpClient.New, and |
| // it uses the same keepalive cache. |
| |
| static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, |
| HttpURLConnection httpuc) |
| throws IOException { |
| return HttpsClient.New(sf, url, hv, true, httpuc); |
| } |
| |
| /** See HttpClient for the model for this method. */ |
| static HttpClient New(SSLSocketFactory sf, URL url, |
| HostnameVerifier hv, boolean useCache, |
| HttpURLConnection httpuc) throws IOException { |
| return HttpsClient.New(sf, url, hv, (String)null, -1, useCache, httpuc); |
| } |
| |
| /** |
| * Get a HTTPS client to the URL. Traffic will be tunneled through |
| * the specified proxy server. |
| */ |
| static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, |
| String proxyHost, int proxyPort, |
| HttpURLConnection httpuc) throws IOException { |
| return HttpsClient.New(sf, url, hv, proxyHost, proxyPort, true, httpuc); |
| } |
| |
| static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, |
| String proxyHost, int proxyPort, boolean useCache, |
| HttpURLConnection httpuc) |
| throws IOException { |
| return HttpsClient.New(sf, url, hv, proxyHost, proxyPort, useCache, -1, |
| httpuc); |
| } |
| |
| static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, |
| String proxyHost, int proxyPort, boolean useCache, |
| int connectTimeout, HttpURLConnection httpuc) |
| throws IOException { |
| |
| return HttpsClient.New(sf, url, hv, |
| (proxyHost == null? null : |
| HttpClient.newHttpProxy(proxyHost, proxyPort, "https")), |
| useCache, connectTimeout, httpuc); |
| } |
| |
| static HttpClient New(SSLSocketFactory sf, URL url, HostnameVerifier hv, |
| Proxy p, boolean useCache, |
| int connectTimeout, HttpURLConnection httpuc) |
| throws IOException |
| { |
| if (p == null) { |
| p = Proxy.NO_PROXY; |
| } |
| PlatformLogger logger = HttpURLConnection.getHttpLogger(); |
| if (logger.isLoggable(PlatformLogger.Level.FINEST)) { |
| logger.finest("Looking for HttpClient for URL " + url + |
| " and proxy value of " + p); |
| } |
| HttpsClient ret = null; |
| if (useCache) { |
| /* see if one's already around */ |
| ret = (HttpsClient) kac.get(url, sf); |
| if (ret != null && httpuc != null && |
| httpuc.streaming() && |
| httpuc.getRequestMethod() == "POST") { |
| if (!ret.available()) |
| ret = null; |
| } |
| |
| if (ret != null) { |
| if ((ret.proxy != null && ret.proxy.equals(p)) || |
| (ret.proxy == null && p == Proxy.NO_PROXY)) { |
| synchronized (ret) { |
| ret.cachedHttpClient = true; |
| assert ret.inCache; |
| ret.inCache = false; |
| if (httpuc != null && ret.needsTunneling()) |
| httpuc.setTunnelState(TUNNELING); |
| if (logger.isLoggable(PlatformLogger.Level.FINEST)) { |
| logger.finest("KeepAlive stream retrieved from the cache, " + ret); |
| } |
| } |
| } else { |
| // We cannot return this connection to the cache as it's |
| // KeepAliveTimeout will get reset. We simply close the connection. |
| // This should be fine as it is very rare that a connection |
| // to the same host will not use the same proxy. |
| synchronized(ret) { |
| if (logger.isLoggable(PlatformLogger.Level.FINEST)) { |
| logger.finest("Not returning this connection to cache: " + ret); |
| } |
| ret.inCache = false; |
| ret.closeServer(); |
| } |
| ret = null; |
| } |
| } |
| } |
| if (ret == null) { |
| ret = new HttpsClient(sf, url, p, connectTimeout); |
| } else { |
| SecurityManager security = System.getSecurityManager(); |
| if (security != null) { |
| if (ret.proxy == Proxy.NO_PROXY || ret.proxy == null) { |
| security.checkConnect(InetAddress.getByName(url.getHost()).getHostAddress(), url.getPort()); |
| } else { |
| security.checkConnect(url.getHost(), url.getPort()); |
| } |
| } |
| ret.url = url; |
| } |
| ret.setHostnameVerifier(hv); |
| |
| return ret; |
| } |
| |
| // METHODS |
| void setHostnameVerifier(HostnameVerifier hv) { |
| this.hv = hv; |
| } |
| |
| void setSSLSocketFactory(SSLSocketFactory sf) { |
| sslSocketFactory = sf; |
| } |
| |
| SSLSocketFactory getSSLSocketFactory() { |
| return sslSocketFactory; |
| } |
| |
| /** |
| * The following method, createSocket, is defined in NetworkClient |
| * and overridden here so that the socket facroty is used to create |
| * new sockets. |
| */ |
| @Override |
| protected Socket createSocket() throws IOException { |
| try { |
| return sslSocketFactory.createSocket(); |
| } catch (SocketException se) { |
| // |
| // bug 6771432 |
| // javax.net.SocketFactory throws a SocketException with an |
| // UnsupportedOperationException as its cause to indicate that |
| // unconnected sockets have not been implemented. |
| // |
| Throwable t = se.getCause(); |
| if (t != null && t instanceof UnsupportedOperationException) { |
| return super.createSocket(); |
| } else { |
| throw se; |
| } |
| } |
| } |
| |
| |
| @Override |
| public boolean needsTunneling() { |
| return (proxy != null && proxy.type() != Proxy.Type.DIRECT |
| && proxy.type() != Proxy.Type.SOCKS); |
| } |
| |
| @Override |
| public void afterConnect() throws IOException, UnknownHostException { |
| if (!isCachedConnection()) { |
| SSLSocket s = null; |
| SSLSocketFactory factory = sslSocketFactory; |
| try { |
| if (!(serverSocket instanceof SSLSocket)) { |
| s = (SSLSocket)factory.createSocket(serverSocket, |
| host, port, true); |
| } else { |
| s = (SSLSocket)serverSocket; |
| if (s instanceof SSLSocketImpl) { |
| ((SSLSocketImpl)s).setHost(host); |
| } |
| } |
| } catch (IOException ex) { |
| // If we fail to connect through the tunnel, try it |
| // locally, as a last resort. If this doesn't work, |
| // throw the original exception. |
| try { |
| s = (SSLSocket)factory.createSocket(host, port); |
| } catch (IOException ignored) { |
| throw ex; |
| } |
| } |
| |
| // |
| // Force handshaking, so that we get any authentication. |
| // Register a handshake callback so our session state tracks any |
| // later session renegotiations. |
| // |
| String [] protocols = getProtocols(); |
| String [] ciphers = getCipherSuites(); |
| if (protocols != null) { |
| s.setEnabledProtocols(protocols); |
| } |
| if (ciphers != null) { |
| s.setEnabledCipherSuites(ciphers); |
| } |
| s.addHandshakeCompletedListener(this); |
| |
| // We have two hostname verification approaches. One is in |
| // SSL/TLS socket layer, where the algorithm is configured with |
| // SSLParameters.setEndpointIdentificationAlgorithm(), and the |
| // hostname verification is done by X509ExtendedTrustManager when |
| // the algorithm is "HTTPS". The other one is in HTTPS layer, |
| // where the algorithm is customized by |
| // HttpsURLConnection.setHostnameVerifier(), and the hostname |
| // verification is done by HostnameVerifier when the default |
| // rules for hostname verification fail. |
| // |
| // The relationship between two hostname verification approaches |
| // likes the following: |
| // |
| // | EIA algorithm |
| // +---------------------------------------------- |
| // | null | HTTPS | LDAP/other | |
| // ------------------------------------------------------------- |
| // | |1 |2 |3 | |
| // HNV | default | Set HTTPS EIA | use EIA | HTTPS | |
| // |-------------------------------------------------------- |
| // | non - |4 |5 |6 | |
| // | default | HTTPS/HNV | use EIA | HTTPS/HNV | |
| // ------------------------------------------------------------- |
| // |
| // Abbreviation: |
| // EIA: the endpoint identification algorithm in SSL/TLS |
| // socket layer |
| // HNV: the hostname verification object in HTTPS layer |
| // Notes: |
| // case 1. default HNV and EIA is null |
| // Set EIA as HTTPS, hostname check done in SSL/TLS |
| // layer. |
| // case 2. default HNV and EIA is HTTPS |
| // Use existing EIA, hostname check done in SSL/TLS |
| // layer. |
| // case 3. default HNV and EIA is other than HTTPS |
| // Use existing EIA, EIA check done in SSL/TLS |
| // layer, then do HTTPS check in HTTPS layer. |
| // case 4. non-default HNV and EIA is null |
| // No EIA, no EIA check done in SSL/TLS layer, then do |
| // HTTPS check in HTTPS layer using HNV as override. |
| // case 5. non-default HNV and EIA is HTTPS |
| // Use existing EIA, hostname check done in SSL/TLS |
| // layer. No HNV override possible. We will review this |
| // decision and may update the architecture for JDK 7. |
| // case 6. non-default HNV and EIA is other than HTTPS |
| // Use existing EIA, EIA check done in SSL/TLS layer, |
| // then do HTTPS check in HTTPS layer as override. |
| boolean needToCheckSpoofing = true; |
| String identification = |
| s.getSSLParameters().getEndpointIdentificationAlgorithm(); |
| if (identification != null && identification.length() != 0) { |
| if (identification.equalsIgnoreCase("HTTPS")) { |
| // Do not check server identity again out of SSLSocket, |
| // the endpoint will be identified during TLS handshaking |
| // in SSLSocket. |
| needToCheckSpoofing = false; |
| } // else, we don't understand the identification algorithm, |
| // need to check URL spoofing here. |
| } else { |
| boolean isDefaultHostnameVerifier = false; |
| |
| // We prefer to let the SSLSocket do the spoof checks, but if |
| // the application has specified a HostnameVerifier (HNV), |
| // we will always use that. |
| if (hv != null) { |
| String canonicalName = hv.getClass().getCanonicalName(); |
| if (canonicalName != null && |
| canonicalName.equalsIgnoreCase(defaultHVCanonicalName)) { |
| isDefaultHostnameVerifier = true; |
| } |
| } else { |
| // Unlikely to happen! As the behavior is the same as the |
| // default hostname verifier, so we prefer to let the |
| // SSLSocket do the spoof checks. |
| isDefaultHostnameVerifier = true; |
| } |
| |
| if (isDefaultHostnameVerifier) { |
| // If the HNV is the default from HttpsURLConnection, we |
| // will do the spoof checks in SSLSocket. |
| SSLParameters paramaters = s.getSSLParameters(); |
| paramaters.setEndpointIdentificationAlgorithm("HTTPS"); |
| s.setSSLParameters(paramaters); |
| |
| needToCheckSpoofing = false; |
| } |
| } |
| |
| s.startHandshake(); |
| session = s.getSession(); |
| // change the serverSocket and serverOutput |
| serverSocket = s; |
| try { |
| serverOutput = new PrintStream( |
| new BufferedOutputStream(serverSocket.getOutputStream()), |
| false, encoding); |
| } catch (UnsupportedEncodingException e) { |
| throw new InternalError(encoding+" encoding not found"); |
| } |
| |
| // check URL spoofing if it has not been checked under handshaking |
| if (needToCheckSpoofing) { |
| checkURLSpoofing(hv); |
| } |
| } else { |
| // if we are reusing a cached https session, |
| // we don't need to do handshaking etc. But we do need to |
| // set the ssl session |
| session = ((SSLSocket)serverSocket).getSession(); |
| } |
| } |
| |
| // Server identity checking is done according to RFC 2818: HTTP over TLS |
| // Section 3.1 Server Identity |
| private void checkURLSpoofing(HostnameVerifier hostnameVerifier) |
| throws IOException { |
| // |
| // Get authenticated server name, if any |
| // |
| String host = url.getHost(); |
| |
| // if IPv6 strip off the "[]" |
| if (host != null && host.startsWith("[") && host.endsWith("]")) { |
| host = host.substring(1, host.length()-1); |
| } |
| |
| Certificate[] peerCerts = null; |
| String cipher = session.getCipherSuite(); |
| try { |
| HostnameChecker checker = HostnameChecker.getInstance( |
| HostnameChecker.TYPE_TLS); |
| |
| // Use ciphersuite to determine whether Kerberos is present. |
| if (cipher.startsWith("TLS_KRB5")) { |
| if (!HostnameChecker.match(host, getPeerPrincipal())) { |
| throw new SSLPeerUnverifiedException("Hostname checker" + |
| " failed for Kerberos"); |
| } |
| } else { // X.509 |
| |
| // get the subject's certificate |
| peerCerts = session.getPeerCertificates(); |
| |
| X509Certificate peerCert; |
| if (peerCerts[0] instanceof |
| java.security.cert.X509Certificate) { |
| peerCert = (java.security.cert.X509Certificate)peerCerts[0]; |
| } else { |
| throw new SSLPeerUnverifiedException(""); |
| } |
| checker.match(host, peerCert); |
| } |
| |
| // if it doesn't throw an exception, we passed. Return. |
| return; |
| |
| } catch (SSLPeerUnverifiedException e) { |
| |
| // |
| // client explicitly changed default policy and enabled |
| // anonymous ciphers; we can't check the standard policy |
| // |
| // ignore |
| } catch (java.security.cert.CertificateException cpe) { |
| // ignore |
| } |
| |
| if ((cipher != null) && (cipher.indexOf("_anon_") != -1)) { |
| return; |
| } else if ((hostnameVerifier != null) && |
| (hostnameVerifier.verify(host, session))) { |
| return; |
| } |
| |
| serverSocket.close(); |
| session.invalidate(); |
| |
| throw new IOException("HTTPS hostname wrong: should be <" |
| + url.getHost() + ">"); |
| } |
| |
| @Override |
| protected void putInKeepAliveCache() { |
| if (inCache) { |
| assert false : "Duplicate put to keep alive cache"; |
| return; |
| } |
| inCache = true; |
| kac.put(url, sslSocketFactory, this); |
| } |
| |
| /* |
| * Close an idle connection to this URL (if it exists in the cache). |
| */ |
| @Override |
| public void closeIdleConnection() { |
| HttpClient http = kac.get(url, sslSocketFactory); |
| if (http != null) { |
| http.closeServer(); |
| } |
| } |
| |
| /** |
| * Returns the cipher suite in use on this connection. |
| */ |
| String getCipherSuite() { |
| return session.getCipherSuite(); |
| } |
| |
| /** |
| * Returns the certificate chain the client sent to the |
| * server, or null if the client did not authenticate. |
| */ |
| public java.security.cert.Certificate [] getLocalCertificates() { |
| return session.getLocalCertificates(); |
| } |
| |
| /** |
| * Returns the certificate chain with which the server |
| * authenticated itself, or throw a SSLPeerUnverifiedException |
| * if the server did not authenticate. |
| */ |
| java.security.cert.Certificate [] getServerCertificates() |
| throws SSLPeerUnverifiedException |
| { |
| return session.getPeerCertificates(); |
| } |
| |
| /** |
| * Returns the principal with which the server authenticated |
| * itself, or throw a SSLPeerUnverifiedException if the |
| * server did not authenticate. |
| */ |
| Principal getPeerPrincipal() |
| throws SSLPeerUnverifiedException |
| { |
| Principal principal; |
| try { |
| principal = session.getPeerPrincipal(); |
| } catch (AbstractMethodError e) { |
| // if the provider does not support it, fallback to peer certs. |
| // return the X500Principal of the end-entity cert. |
| java.security.cert.Certificate[] certs = |
| session.getPeerCertificates(); |
| principal = ((X509Certificate)certs[0]).getSubjectX500Principal(); |
| } |
| return principal; |
| } |
| |
| /** |
| * Returns the principal the client sent to the |
| * server, or null if the client did not authenticate. |
| */ |
| Principal getLocalPrincipal() |
| { |
| Principal principal; |
| try { |
| principal = session.getLocalPrincipal(); |
| } catch (AbstractMethodError e) { |
| principal = null; |
| // if the provider does not support it, fallback to local certs. |
| // return the X500Principal of the end-entity cert. |
| java.security.cert.Certificate[] certs = |
| session.getLocalCertificates(); |
| if (certs != null) { |
| principal = ((X509Certificate)certs[0]).getSubjectX500Principal(); |
| } |
| } |
| return principal; |
| } |
| |
| /** |
| * This method implements the SSL HandshakeCompleted callback, |
| * remembering the resulting session so that it may be queried |
| * for the current cipher suite and peer certificates. Servers |
| * sometimes re-initiate handshaking, so the session in use on |
| * a given connection may change. When sessions change, so may |
| * peer identities and cipher suites. |
| */ |
| public void handshakeCompleted(HandshakeCompletedEvent event) |
| { |
| session = event.getSession(); |
| } |
| |
| /** |
| * @return the proxy host being used for this client, or null |
| * if we're not going through a proxy |
| */ |
| @Override |
| public String getProxyHostUsed() { |
| if (!needsTunneling()) { |
| return null; |
| } else { |
| return super.getProxyHostUsed(); |
| } |
| } |
| |
| /** |
| * @return the proxy port being used for this client. Meaningless |
| * if getProxyHostUsed() gives null. |
| */ |
| @Override |
| public int getProxyPortUsed() { |
| return (proxy == null || proxy.type() == Proxy.Type.DIRECT || |
| proxy.type() == Proxy.Type.SOCKS)? -1: |
| ((InetSocketAddress)proxy.address()).getPort(); |
| } |
| } |