On Feb 12, 2008 11:58 AM, stephen joseph butler <[EMAIL PROTECTED]> wrote: > Hmmm... that's disappointing. But looking at the source, I might be > able to hack it in myself. Basically, I want to do what is already > done in HttpConnection#tunnelCreated(). So at least I know it's > possible! > > Thanks for the information.
For anyone wondering how solved my problem, here's the code I came up with. In the end, I didn't have to modify any of the HttpClient code, but my version isn't horribly robust. For instance, it only returns upgraded sockets (which is fine for IPP), but won't understand an upgrade request made via a user initiated method. Part of that problem is that HttpClient discards any responses in the 100 range as being irrelevant (from what I understand) to the success/failure of the method. I don't know how 4.x is progressing, but some general way to hook into the response stream and handle such requests would be nice. Anyway, I didn't have to solve that problem to make my code work. Class attached for the curious.
package foobar; import java.io.InputStream; import java.io.IOException; import java.io.OutputStream; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.Socket; import java.net.UnknownHostException; import javax.net.SocketFactory; import javax.net.ssl.HandshakeCompletedEvent; import javax.net.ssl.HandshakeCompletedListener; import javax.net.ssl.SSLSocket; import javax.net.ssl.SSLSocketFactory; import org.apache.commons.httpclient.ConnectTimeoutException; import org.apache.commons.httpclient.Header; import org.apache.commons.httpclient.HttpParser; import org.apache.commons.httpclient.HttpStatus; import org.apache.commons.httpclient.StatusLine; import org.apache.commons.httpclient.params.HttpConnectionParams; import org.apache.commons.httpclient.protocol.SecureProtocolSocketFactory; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * <p>Protocol factory to create an encrypted TLS connection over * the standard HTTP port. This factory will first create an unecrypted * connect, then ask the server for and upgrade to TLS. * * @since 1.0 * @author <a href="mailto:[EMAIL PROTECTED]">Stephen J Butler</a> */ public class TLSProtocolSocketFactory implements SecureProtocolSocketFactory { /** Our logger class. */ private static final Log LOG = LogFactory.getLog( TLSProtocolSocketFactory.class ); /** * <p>Callback class to check when SSL callbacks occur. You should call * wait on the instance of the callback object. * * @since 1.0 * @author <a href="mailto:[EMAIL PROTECTED]">Stephen J Butler</a> */ protected static class SSLCallback implements HandshakeCompletedListener { /** Our logger class. */ private static final Log LOG = LogFactory.getLog( TLSProtocolSocketFactory.class ); /** * <p>Construct a simple callback instance. */ public SSLCallback() { } /** * <p>Handshake has completed. Notify any waiters of this object. * * @param event Event that completed. */ public void handshakeCompleted( HandshakeCompletedEvent event ) { if (LOG.isDebugEnabled()) { LOG.debug( "SSL handshake completed." ); LOG.debug( "Cipher Suite: " + event.getCipherSuite() ); } synchronized (this) { notify(); } } } /** TLS version 1.0 string. */ public static final int VERSION_1_0 = 0; /** TLS HTTP strings. */ public static final String[] HTTP_VERSION = new String[] { "TLS/1.0" }; public static final String[] SSL_VERSION = new String[] { "TLSv1" }; /** New line for headers. */ private static final char[] CRLF = new char[] {(char) 13, (char) 10}; /** Instance of our factory. */ private static final TLSProtocolSocketFactory factory = new TLSProtocolSocketFactory(); /** TLS version we are to use. */ protected int tlsVersion; /** * Construct an instance of the protocol factory. */ public TLSProtocolSocketFactory() { this( VERSION_1_0 ); } /** * <p>Construct an instance of the protocol factory. * * @param tlsVersion TLS Version to upgrade to. */ public TLSProtocolSocketFactory( int tlsVersion ) { super(); this.tlsVersion = tlsVersion; } /** * <p>Get an instance of the factory. * * @return Shared instance of the socket factory. */ public static TLSProtocolSocketFactory getSocketFactory() { return factory; } /** * <p>Attempts to get a new socket connection to the given host, binding to any * local address and choosing a local port. There is no timeout on the connect. * * @param host The host name/IP. * @param port The port on the host. * @return A connected 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( String host, int port ) throws IOException, UnknownHostException { if (LOG.isDebugEnabled()) LOG.debug( "Connecting to " + host + ":" + port ); return tlsUpgrade( SocketFactory.getDefault().createSocket( host, port ), host, port ); } /** * <p>Attempts to get a new socket connection to the given host with the * specified local address and port. There is no timeout on the connect. * * @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. * @return A connected 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( String host, int port, InetAddress localAddress, int localPort ) throws IOException, UnknownHostException { if (LOG.isDebugEnabled()) LOG.debug( "Connecting to " + host + ":" + port + " from " + localAddress + ":" + localPort ); return tlsUpgrade( SocketFactory.getDefault().createSocket( host, port, localAddress, localPort ), host, port ); } /** * <p>Attempts to get a new socket connection to the given host within the given time limit. * * @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 [EMAIL PROTECTED] HttpConnectionParams} for the new socket. * @return A connected 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. * * @throws ConnectTimeoutException * If socket cannot be connected within the given time limit. */ public Socket createSocket( final String host, final int port, final InetAddress localAddress, final int localPort, final HttpConnectionParams params ) throws IOException, UnknownHostException, ConnectTimeoutException { int timeout; if (params == null) throw new IllegalArgumentException( "Parameters may not be null" ); timeout = params.getConnectionTimeout(); if (timeout == 0) return createSocket( host, port, localAddress, localPort ); else { Socket sock = SocketFactory.getDefault().createSocket(); if (LOG.isDebugEnabled()) LOG.debug( "Binding to " + localAddress + ":" + localPort ); sock.bind( new InetSocketAddress( localAddress, localPort ) ); if (LOG.isDebugEnabled()) LOG.debug( "Connecting to " + host + ":" + port + " with timeout " + timeout ); sock.connect( new InetSocketAddress( host, port ), timeout ); return tlsUpgrade( sock, host, port ); } } /** * <p>Takes an existing socket that is unsecure and issue the TLS upgrade commands * over it. * * @param socket Socket to upgrade. * @param host The host name/IP. * @param port The port on the host. * @return An upgraded, connected 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( Socket socket, String host, int port, boolean autoClose ) throws IOException, UnknownHostException { if (LOG.isDebugEnabled()) LOG.debug( "Reconnecting socket to " + host + ":" + port ); return tlsUpgrade( socket, host, port ); } /** * <p>Instances of TLSProtocolSocketFactory are the same if they upgrade to * the same TLS version. * * @return True if both objects have the same TLS version. */ public boolean equals( Object rhs ) { if (rhs instanceof TLSProtocolSocketFactory) { TLSProtocolSocketFactory other = (TLSProtocolSocketFactory)rhs; return tlsVersion == other.tlsVersion; } else return false; } /** * <p>Get the TLS version this protocol factory will use. * * @return TLS version. */ public int getTLSVersion() { return tlsVersion; } /** * <p>Instances of TLSProtocolSocketFactory have the same hash code. * * @return Hash code. */ public int hashCode() { return getClass().hashCode(); } /** * <p>Read a TLS response from the server, returning the status code. * All we really care about is the status line, so throw the headers * away. * * @param socket Socket to read from. * @return HTTP status line. */ protected StatusLine tlsReadResponse( Socket socket ) throws IOException { InputStream in = socket.getInputStream(); String line; Header[] headers; StatusLine status; // while the returned lines don't start with HTTP, read them in do { line = HttpParser.readLine( in, "US-ASCII" ); if (line == null) throw new IOException( "Unexpected null waiting for response" ); } while (!StatusLine.startsWithHTTP( line )); // get the status line and all the headers status = new StatusLine( line ); headers = HttpParser.parseHeaders( in, "US-ASCII" ); return status; } /** * <p>Take an unencrypted connection and force upgrade it to TLS via an * HTTP TLS upgrade request. This ammounts to sending the following to * the server: * * <pre>OPTIONS * HTTP/1.1 * Host: $REMOTE_HOST * Upgrade: $TLS_VERSION * Connection: Upgrade</pre> * * <p>A success is indicated by a status return of 101. If the connection * could not be upgraded, then an IOException is thrown. * * @param socket Socket to upgrade. * @param host Remote host the socket is connected to. * @param port Remote port the socket is connected to. * @return A new socket, this time upgraded to TLS. * * @throws IOException * If the socket had a problem during the upgrade. */ protected Socket tlsUpgrade( Socket socket, String host, int port ) throws IOException { StringBuilder sb = new StringBuilder(); OutputStream out = socket.getOutputStream(); SSLSocket sslSocket; SSLCallback callback = new SSLCallback(); StatusLine status; // build our options request sb.append( "OPTIONS * HTTP/1.1" ).append( CRLF ); sb.append( "Host: " ).append( host ).append( ":" ).append( port ).append( CRLF ); sb.append( "Upgrade: " ).append( HTTP_VERSION[ tlsVersion ] ).append( CRLF ); sb.append( "Connection: Upgrade" ).append( CRLF ); sb.append( CRLF ); // write it to the socket out.write( sb.toString().getBytes( "US-ASCII" ) ); // check to make sure the request to switch protocols was successful status = tlsReadResponse( socket ); if (status.getStatusCode() != HttpStatus.SC_SWITCHING_PROTOCOLS) throw new IOException( "Unable to upgrade connection to TLS (" + status.toString() + ")" ); // and now, create our secure socket LOG.debug( "Creating SSL socket" ); sslSocket = (SSLSocket)(((SSLSocketFactory)(SSLSocketFactory.getDefault())).createSocket( socket, host, port, true )); sslSocket.setEnabledProtocols( new String[] { SSL_VERSION[ tlsVersion ] } ); sslSocket.addHandshakeCompletedListener( callback ); sslSocket.startHandshake(); // wait for the callback to be signaled, which will tell us when the handshake // completed try { LOG.debug( "Waiting for handshake to complete" ); synchronized (callback) { callback.wait(); } } catch (InterruptedException interruptedException) { throw new IOException( "Could not complete SSL handshake" ); } // finally, read in the original OPTIONS request LOG.debug( "Reading original options request" ); status = tlsReadResponse( sslSocket ); if ((status.getStatusCode() < 200) || (status.getStatusCode() >= 300)) throw new IOException( "Server failed to respond to the options request (" + status.toString() + ")" ); LOG.debug( "Socket upgraded to TLS" ); return sslSocket; } }
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]
