797 lines
30 KiB
Java
797 lines
30 KiB
Java
/*
|
|
* Copyright (c) 2001, 2013, 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 java.security.AccessController;
|
|
|
|
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 = AccessController.doPrivileged(
|
|
new GetPropertyAction("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 = AccessController.doPrivileged(
|
|
new GetPropertyAction("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 = java.security.AccessController.doPrivileged(
|
|
new sun.security.action.GetPropertyAction("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() &&
|
|
"POST".equals(httpuc.getRequestMethod())) {
|
|
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 void closeServer() {
|
|
try {
|
|
// SSLSocket.close may block up to timeout. Make sure it's short.
|
|
serverSocket.setSoTimeout(1);
|
|
} catch (Exception e) {}
|
|
super.closeServer();
|
|
}
|
|
|
|
@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 X.509 certificate chain with which the server
|
|
* authenticated itself, or null if the server did not authenticate.
|
|
*/
|
|
javax.security.cert.X509Certificate [] getServerCertificateChain()
|
|
throws SSLPeerUnverifiedException
|
|
{
|
|
return session.getPeerCertificateChain();
|
|
}
|
|
|
|
/**
|
|
* 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();
|
|
}
|
|
}
|