I am writing a program (with a server side and a client side) to establish a transport layer for existing remote control programs like ssh, remote desktop, vnc etc. to connect between client and server also if they are behind a proxy and or a firewall. To work with proxy, since i need to transport generic underling protocols, i need to establish a http tunnel through proxy between the client side and the server side of my system. So I need to gain a plain socket after establishing the tunnel, then construct over it an SSLSocket and transmit data as simple bytes. First I used commons-httpclient 3.0 because i used the example http://svn.apache.org/repos/asf/httpcomponents/oac.hc3x/branches/COOKIE_2_BRANCH/src/examples/ProxyTunnelDemo.java and it worked with squid and basic authentication in a test environment
Then, because in my production system there is a proxy with kerberos and ntlmv2 authentication, and httpclient version 3 dosn't support this kind of authentication, I decided to use version http-components 4.1.2. I started by the class OperatorConnectProxy in the exemples directory because it uses an OperatedClientConnection that has a method getSocket() that works well for me and that the ManagedConnections does not have it. I report the class OperatorConnectProxy here after. /* * ==================================================================== * 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 * <http://www.apache.org/>. * */ package org.apache.http.examples.conn; import org.apache.http.Header; import org.apache.http.HttpHost; import org.apache.http.HttpRequest; import org.apache.http.HttpResponse; import org.apache.http.HttpVersion; import org.apache.http.conn.ClientConnectionOperator; import org.apache.http.conn.OperatedClientConnection; import org.apache.http.conn.scheme.PlainSocketFactory; import org.apache.http.conn.scheme.Scheme; import org.apache.http.conn.scheme.SchemeRegistry; import org.apache.http.conn.ssl.SSLSocketFactory; import org.apache.http.impl.conn.DefaultClientConnectionOperator; import org.apache.http.message.BasicHttpRequest; import org.apache.http.params.HttpParams; import org.apache.http.params.HttpProtocolParams; import org.apache.http.params.SyncBasicHttpParams; import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.BasicHttpContext; /** * How to open a secure connection through a proxy using * {@link ClientConnectionOperator ClientConnectionOperator}. * This exemplifies the <i>opening</i> of the connection only. * The message exchange, both subsequently and for tunnelling, * should not be used as a template. * * @since 4.0 */ public class OperatorConnectProxy { public static void main(String[] args) throws Exception { // make sure to use a proxy that supports CONNECT HttpHost target = new HttpHost("issues.apache.org", 443, "https"); HttpHost proxy = new HttpHost("127.0.0.1", 8666, "http"); // some general setup // Register the "http" and "https" protocol schemes, they are // required by the default operator to look up socket factories. SchemeRegistry supportedSchemes = new SchemeRegistry(); supportedSchemes.register(new Scheme("http", 80, PlainSocketFactory.getSocketFactory())); supportedSchemes.register(new Scheme("https", 443, SSLSocketFactory.getSocketFactory())); // Prepare parameters. // Since this example doesn't use the full core framework, // only few parameters are actually required. HttpParams params = new SyncBasicHttpParams(); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setUseExpectContinue(params, false); // one operator can be used for many connections ClientConnectionOperator scop = new DefaultClientConnectionOperator(supportedSchemes); HttpRequest req = new BasicHttpRequest("OPTIONS", "*", HttpVersion.HTTP_1_1); // In a real application, request interceptors should be used // to add the required headers. req.addHeader("Host", target.getHostName()); HttpContext ctx = new BasicHttpContext(); OperatedClientConnection conn = scop.createConnection(); try { System.out.println("opening connection to " + proxy); scop.openConnection(conn, proxy, null, ctx, params); // Creates a request to tunnel a connection. // For details see RFC 2817, section 5.2 String authority = target.getHostName() + ":" + target.getPort(); HttpRequest connect = new BasicHttpRequest("CONNECT", authority, HttpVersion.HTTP_1_1); // In a real application, request interceptors should be used // to add the required headers. connect.addHeader("Host", authority); System.out.println("opening tunnel to " + target); conn.sendRequestHeader(connect); // there is no request entity conn.flush(); System.out.println("receiving confirmation for tunnel"); HttpResponse connected = conn.receiveResponseHeader(); System.out.println("----------------------------------------"); printResponseHeader(connected); System.out.println("----------------------------------------"); int status = connected.getStatusLine().getStatusCode(); if ((status < 200) || (status > 299)) { System.out.println("unexpected status code " + status); System.exit(1); } System.out.println("receiving response body (ignored)"); conn.receiveResponseEntity(connected); // Now we have a tunnel to the target. As we will be creating a // layered TLS/SSL socket immediately afterwards, updating the // connection with the new target is optional - but good style. // The scheme part of the target is already "https", though the // connection is not yet switched to the TLS/SSL protocol. conn.update(null, target, false, params); System.out.println("layering secure connection"); scop.updateSecureConnection(conn, target, ctx, params); // finally we have the secure connection and can send the request System.out.println("sending request"); conn.sendRequestHeader(req); // there is no request entity conn.flush(); System.out.println("receiving response header"); HttpResponse rsp = conn.receiveResponseHeader(); System.out.println("----------------------------------------"); printResponseHeader(rsp); System.out.println("----------------------------------------"); } finally { System.out.println("closing connection"); conn.close(); } } private final static void printResponseHeader(HttpResponse rsp) { System.out.println(rsp.getStatusLine()); Header[] headers = rsp.getAllHeaders(); for (int i=0; i<headers.length; i++) { System.out.println(headers[i]); } } } Starting from this class I also need to perform authentication over the firewall. I tried reading other examples and javadoc to suceed performing authentication, the following is the actual code where I tried to have the tunnel with authentication out of the box but it does not work. HttpHost target = new HttpHost(host, port, "https"); HttpHost proxy = new HttpHost(proxyHost, proxyPort); // some general setup // Register the "http" and "https" protocol schemes, they are // required by the default operator to look up socket factories. SchemeRegistry supportedSchemes = new SchemeRegistry(); supportedSchemes.register(new Scheme("http", 443, PlainSocketFactory.getSocketFactory())); //supportedSchemes.register(new Scheme("https", // 443, SSLSocketFactory.getSocketFactory())); // Prepare parameters. // Since this example doesn't use the full core framework, // only few parameters are actually required. HttpParams params = new SyncBasicHttpParams(); //here is where I try to have authentication params.setParameter(ClientPNames.HANDLE_AUTHENTICATION, true); HttpClientParams.setAuthenticating(params, true); HttpProtocolParams.setVersion(params, HttpVersion.HTTP_1_1); HttpProtocolParams.setUseExpectContinue(params, false); // one operator can be used for many connections ClientConnectionOperator scop = new DefaultClientConnectionOperator(supportedSchemes); HttpRequest req = new BasicHttpRequest("OPTIONS", "*", HttpVersion.HTTP_1_1); // In a real application, request interceptors should be used // to add the required headers. req.addHeader("Host", target.getHostName()); HttpContext ctx = new BasicHttpContext(); //also here is where I try to have authentication AuthCache authCache = new BasicAuthCache(); // Generate BASIC scheme object and add it to the local // auth cache BasicScheme basicAuth = new BasicScheme(); authCache.put(proxy, basicAuth); // Add AuthCache to the execution context BasicHttpContext localcontext = new BasicHttpContext(); localcontext.setAttribute(ClientContext.AUTH_CACHE, authCache); ClientContextConfigurer ccc = new ClientContextConfigurer(ctx); CredentialsProvider cp = new BasicCredentialsProvider(); cp.setCredentials( new AuthScope(proxyHost, proxyPort), new UsernamePasswordCredentials(proxyUser, proxyPassword)); ccc.setCredentialsProvider(cp); AuthSchemeRegistry asr = new AuthSchemeRegistry(); asr.register("basic", new BasicSchemeFactory()); localcontext.setAttribute(ClientContext.AUTHSCHEME_REGISTRY, asr); localcontext.setAttribute(ClientContext.CREDS_PROVIDER, cp); OperatedClientConnection conn = scop.createConnection(); System.out.println("opening connection to " + proxy); scop.openConnection(conn, proxy, null, ctx, params); // Creates a request to tunnel a connection. // For details see RFC 2817, section 5.2 String authority = target.getHostName() + ":" + target.getPort(); HttpRequest connect = new BasicHttpRequest("CONNECT", authority, HttpVersion.HTTP_1_1); // In a real application, request interceptors should be used // to add the required headers. connect.addHeader("Host", authority); RequestProxyAuthentication rpa = new RequestProxyAuthentication(); rpa.process(connect, ctx); System.out.println("opening tunnel to " + target); conn.sendRequestHeader(connect); // there is no request entity conn.flush(); System.out.println("receiving confirmation for tunnel"); HttpResponse connected = conn.receiveResponseHeader(); System.out.println("----------------------------------------"); System.out.println(connected.getStatusLine()); Header[] headers = connected.getAllHeaders(); for (int i=0; i<headers.length; i++) { System.out.println(headers[i]); } System.out.println("----------------------------------------"); int status = connected.getStatusLine().getStatusCode(); if ((status < 200) || (status > 299)) { System.out.println("unexpected status code " + status); } System.out.println("receiving response body (ignored)"); conn.receiveResponseEntity(connected); // Now we have a tunnel to the target. As we will be creating a // layered TLS/SSL socket immediately afterwards, updating the // connection with the new target is optional - but good style. // The scheme part of the target is already "https", though the // connection is not yet switched to the TLS/SSL protocol. conn.update(null, target, false, params); if (conn.getSocket() != null) { SSLSocketFactory factory = (SSLSocketFactory)SSLSocketFactory.getDefault(); Socket tunnel = conn.getSocket(); socket = (SSLSocket) factory.createSocket(tunnel, host, port, true); ((SSLSocket)socket).setEnabledCipherSuites(new String[] {"SSL_DH_anon_EXPORT_WITH_DES40_CBC_SHA"}); } else { // the proxy connect was not successful, check connect method for reasons why System.out.println("Connect failed: "); } Can you suggest me how do such a tunneling to the server through firewall with authentication ?
