
org.jsslutils.extra.apachehttpclient.SslContextedSecureProtocolSocketFactory Maven / Gradle / Ivy
Show all versions of jsslutils-extra-apachehttpclient3 Show documentation
/*
* ====================================================================
*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with this
* work for additional information regarding copyright ownership. The ASF
* licenses this file to You under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations under
* the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many individuals on
* behalf of the Apache Software Foundation. For more information on the Apache
* Software Foundation, please see .
*
* [Additional notices, if required by prior licensing conditions]
*
* Alternatively, the contents of this file may be used under the terms of the
* GNU Lesser General Public License Version 2 or later (the "LGPL"), in which
* case the provisions of the LGPL are applicable instead of those above. See
* terms of LGPL at . If you wish to
* allow use of your version of this file only under the terms of the LGPL and
* not to allow others to use your version of this file under the Apache
* Software License, indicate your decision by deleting the provisions above and
* replace them with the notice and other provisions required by the LGPL. If
* you do not delete the provisions above, a recipient may use your version of
* this file under either the Apache Software License or the LGPL.
*/
package org.jsslutils.extra.apachehttpclient;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.StringTokenizer;
import javax.net.SocketFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.security.auth.x500.X500Principal;
import org.apache.commons.httpclient.ConnectTimeoutException;
import org.apache.commons.httpclient.params.HttpConnectionParams;
import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory;
/**
* This is a SecureProtocolSocketFactory for with the SSLContext can be
* configured. It is based on Sebastian Hauer's StrictSSLProtocolSocketFactory,
* available in the contribution directory of the Apache HTTP client library
* 3.1. The main difference is that the SSLContext can be set, which means that
* the use of client certificates or CRLs may be configured this way. The intent
* was to use it in conjunction with jSSLutils, but it is not a
* dependency.
*
* If no SSLContext is set up, the default SSLSocketFactory is used.
*
* @author Bruno Harbulot
* @author Sebastian Hauer
*
* DISCLAIMER: HttpClient developers DO NOT actively support this
* component. The component is provided as a reference material, which
* may be inappropriate for use without additional customization.
*
*/
public class SslContextedSecureProtocolSocketFactory implements
SecureProtocolSocketFactory {
private SSLContext sslContext;
/** Host name verify flag. */
private boolean verifyHostname = true;
/**
* Constructor for SslContextedSecureProtocolSocketFactory.
*
* @param sslContext
* The SSLContext to use for building the SSLSocketFactory. If
* this is null, then the default SSLSocketFactory is used.
* @param verifyHostname
* The host name verification flag. If set to true
* the SSL sessions server host name will be compared to the host
* name returned in the server certificates "Common Name" field
* of the "SubjectDN" entry. If these names do not match a
* Exception is thrown to indicate this. Enabling host name
* verification will help to prevent from man-in-the-middle
* attacks. If set to false
host name verification
* is turned off.
*
* Code sample:
*
* Protocol stricthttps = new Protocol( "https", new
* SslContextedSecureProtocolSocketFactory(sslContext,true),
* 443);
*
* HttpClient client = new HttpClient();
* client.getHostConfiguration().setHost("localhost", 443,
* stricthttps);
*
*/
public SslContextedSecureProtocolSocketFactory(SSLContext sslContext,
boolean verifyHostname) {
this.sslContext = sslContext;
this.verifyHostname = verifyHostname;
}
/**
* Constructor for SslContextedSecureProtocolSocketFactory. Host name
* verification will be enabled by default.
*
* @param sslContext
* The SSLContext to use for building the SSLSocketFactory. If
* this is null, then the default SSLSocketFactory is used.
*/
public SslContextedSecureProtocolSocketFactory(SSLContext sslContext) {
this(sslContext, true);
}
/**
* Constructor for SslContextedSecureProtocolSocketFactory. The default
* SSLSocketFactory will be used by default.
*
* @param verifyHostname
* The host name verification flag. If set to true
* the SSL sessions server host name will be compared to the host
* name returned in the server certificates "Common Name" field
* of the "SubjectDN" entry. If these names do not match a
* Exception is thrown to indicate this. Enabling host name
* verification will help to prevent from man-in-the-middle
* attacks. If set to false
host name verification
* is turned off.
*/
public SslContextedSecureProtocolSocketFactory(boolean verifyHostname) {
this(null, verifyHostname);
}
/**
* Constructor for SslContextedSecureProtocolSocketFactory. By default, the
* default SSLSocketFactory will be used and host name verification will be
* enabled.
*/
public SslContextedSecureProtocolSocketFactory() {
this(null, true);
}
/**
* Set the host name verification flag.
*
* @param verifyHostname
* The host name verification flag. If set to true
* the SSL sessions server host name will be compared to the host
* name returned in the server certificates "Common Name" field
* of the "SubjectDN" entry. If these names do not match a
* Exception is thrown to indicate this. Enabling host name
* verification will help to prevent from man-in-the-middle
* attacks. If set to false
host name verification
* is turned off.
*/
public synchronized void setHostnameVerification(boolean verifyHostname) {
this.verifyHostname = verifyHostname;
}
/**
* Gets the status of the host name verification flag.
*
* @return Host name verification flag. Either true
if host
* name verification is turned on, or false
if host
* name verification is turned off.
*/
public synchronized boolean getHostnameVerification() {
return verifyHostname;
}
/**
* @see SecureProtocolSocketFactory#createSocket(java.lang.String,int,java.net.InetAddress,int)
*/
public Socket createSocket(String host, int port, InetAddress clientHost,
int clientPort) throws IOException, UnknownHostException {
SSLSocketFactory sf = (SSLSocketFactory) getSslSocketFactory();
SSLSocket sslSocket = (SSLSocket) sf.createSocket(host, port,
clientHost, clientPort);
verifyHostname(sslSocket);
return sslSocket;
}
/**
* Attempts to get a new socket connection to the given host within the
* given time limit.
*
* This method employs several techniques to circumvent the limitations of
* older JREs that do not support connect timeout. When running in JRE 1.4
* or above reflection is used to call Socket#connect(SocketAddress
* endpoint, int timeout) method. When executing in older JREs a controller
* thread is executed. The controller thread attempts to create a new socket
* within the given limit of time. If socket constructor does not return
* until the timeout expires, the controller terminates and throws an
* {@link ConnectTimeoutException}
*
*
* @param host
* the host name/IP
* @param port
* the port on the host
* @param localAddress
* the local host name/IP to bind the socket to
* @param localPort
* the port on the local machine
* @param params
* {@link HttpConnectionParams Http connection parameters}
*
* @return Socket a new socket
*
* @throws IOException
* if an I/O error occurs while creating the socket
* @throws UnknownHostException
* if the IP address of the host cannot be determined
*/
public Socket createSocket(final String host, final int port,
final InetAddress localAddress, final int localPort,
final HttpConnectionParams params) throws IOException,
UnknownHostException, ConnectTimeoutException {
if (params == null) {
throw new IllegalArgumentException("Parameters may not be null");
}
int timeout = params.getConnectionTimeout();
Socket socket = null;
SocketFactory socketfactory = getSslSocketFactory();
if (timeout == 0) {
socket = socketfactory.createSocket(host, port, localAddress,
localPort);
} else {
socket = socketfactory.createSocket();
SocketAddress localaddr = new InetSocketAddress(localAddress,
localPort);
SocketAddress remoteaddr = new InetSocketAddress(host, port);
socket.bind(localaddr);
socket.connect(remoteaddr, timeout);
}
verifyHostname((SSLSocket) socket);
return socket;
}
/**
* @see SecureProtocolSocketFactory#createSocket(java.lang.String,int)
*/
public Socket createSocket(String host, int port) throws IOException,
UnknownHostException {
SSLSocketFactory sf = (SSLSocketFactory) getSslSocketFactory();
SSLSocket sslSocket = (SSLSocket) sf.createSocket(host, port);
verifyHostname(sslSocket);
return sslSocket;
}
/**
* @see SecureProtocolSocketFactory#createSocket(java.net.Socket,java.lang.String,int,boolean)
*/
public Socket createSocket(Socket socket, String host, int port,
boolean autoClose) throws IOException, UnknownHostException {
SSLSocketFactory sf = (SSLSocketFactory) getSslSocketFactory();
SSLSocket sslSocket = (SSLSocket) sf.createSocket(socket, host, port,
autoClose);
verifyHostname(sslSocket);
return sslSocket;
}
/**
* Describe verifyHostname
method here.
*
* @param socket
* a SSLSocket
value
* @exception SSLPeerUnverifiedException
* If there are problems obtaining the server certificates
* from the SSL session, or the server host name does not
* match with the "Common Name" in the server certificates
* SubjectDN.
* @exception UnknownHostException
* If we are not able to resolve the SSL sessions returned
* server host name.
* @throws CertificateParsingException
*/
private void verifyHostname(SSLSocket socket) throws IOException,
UnknownHostException {
synchronized (this) {
if (!verifyHostname)
return;
}
SSLSession session = socket.getSession();
String hostname = session.getPeerHost();
try {
InetAddress.getByName(hostname);
} catch (UnknownHostException uhe) {
throw new UnknownHostException("Could not resolve SSL sessions "
+ "server hostname: " + hostname);
}
X509Certificate[] certs = (X509Certificate[]) session
.getPeerCertificates();
if (certs == null || certs.length == 0)
throw new SSLPeerUnverifiedException(
"No server certificates found!");
try {
List cns = new ArrayList();
boolean foundDnsSan = false;
Collection> subjectAltNames = certs[0]
.getSubjectAlternativeNames();
if (subjectAltNames != null) {
for (List> san : subjectAltNames) {
if (((Integer) san.get(0)).intValue() == 2) {
foundDnsSan = true;
String sanDns = (String) san.get(1);
cns.add(sanDns);
if (hostname.equalsIgnoreCase(sanDns)) {
return;
}
}
}
}
if (!foundDnsSan) {
// get the common names from the first cert
X500Principal subjectDN = certs[0].getSubjectX500Principal();
cns = getCNs(subjectDN);
for (String cn : cns) {
if (hostname.equalsIgnoreCase(cn)) {
return;
}
}
}
throw new SSLPeerUnverifiedException(
"HTTPS hostname invalid: expected '" + hostname
+ "', received '" + cns + "'");
} catch (CertificateParsingException e) {
throw new IOException(e);
}
}
/**
* Parses a X.500 distinguished name for the values of the "Common Name"
* fields. This is done a bit sloppy right now and should probably be done a
* bit more according to RFC 2253
.
*
* @param subjectDN
* an X.500 Principal from an X.509 certificate.
* @return the values of the "Common Name" fields.
*/
private List getCNs(X500Principal subjectDN) {
List cns = new ArrayList();
StringTokenizer st = new StringTokenizer(subjectDN.getName(), ",");
while (st.hasMoreTokens()) {
String cnField = st.nextToken();
if (cnField.startsWith("CN=")) {
cns.add(cnField.substring(3));
}
}
return cns;
}
/**
* Returns the SSLSocketFactory to use to create the sockets. If the
* sslContext is non-null, this is built from the sslContext; otherwise,
* this is the default SSLSocketFactory.
*
* @return the SSLSocketFactory to use to create the sockets.
*/
protected SSLSocketFactory getSslSocketFactory() {
SSLSocketFactory sslSocketFactory = null;
synchronized (this) {
if (this.sslContext != null) {
sslSocketFactory = this.sslContext.getSocketFactory();
}
}
if (sslSocketFactory == null) {
sslSocketFactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
}
return sslSocketFactory;
}
/**
* Sets the SSLContext to use.
*
* @param sslContext
* SSLContext to use.
*/
public synchronized void setSSLContext(SSLContext sslContext) {
this.sslContext = sslContext;
}
}