Here is the source for Https Tunneling that I have gotten working.
It also does Basic Authentication with the proxy if necessary.
It is based on Pua Yeow Cheong's JavaWorld Tip 111.
Thanks to David Lord for providing the final breakthrough that I needed.
I have posted it here for anyone who wishes to use it.
If you find any bugs, or write any improvements, please tack them onto the end of this thread.
I have been trying to tackle this problem for quite some time, so I hope this helps a few of you out there.
Lots of Luck,
nightmask.
<----- Begin Copy and Paste -------->
import java.net.*;
import java.io.*;
import java.security.*;
import sun.misc.BASE64Encoder;
import javax.net.*;
import javax.net.ssl.*;
/*
* This example is based on JavaWorld Tip 111. Thanks to Pua Yeow Cheong for writing it.
* It tunnels through a proxy using the Https protocol.
* Thanks go to David Lord in the java forums for figuring out the main problem with Tip 111
* PLEASE NOTE: You need to have the JSSE 1.0.2 jars installed for this to work
*/
/**
* Downloads contents of a URL, using Proxy Tunneling and Basic Authentication
*/
public class URLReader {
/**
* The main program for the URLReader class
*/
public static void main(String[] args) throws Exception {
//set up strings for use in app. Change these to your own settings
String proxyPassword = "password";
String proxyUsername = "username";
String proxyHost = "myproxy.com";
String proxyPort = "3128";
String connectionURL = "https://www.verisign.com";
//set up system properties to indicate we are using a proxy
System.setProperty("https.proxyHost", proxyHost);
System.setProperty("https.proxyPort", proxyPort);
System.setProperty("proxyHost", proxyHost);
System.setProperty("proxyPort", proxyPort);
System.setProperty("proxySet", "true");
System.setProperty("http.proxyHost", proxyHost);
System.setProperty("http.proxyPort", proxyPort);
System.setProperty("http.proxySet", "true");
//set up handler for jsse
System.setProperty("java.protocol.handler.pkgs", "com.sun.net.ssl.internal.www.protocol");
java.security.Provider prov = new com.sun.net.ssl.internal.ssl.Provider();
Security.addProvider(prov);
//create the connection
URL myURL = new URL(connectionURL);
URLConnection myConnection = myURL.openConnection();
if (myConnection instanceof com.sun.net.ssl.HttpsURLConnection) {
((com.sun.net.ssl.HttpsURLConnection) myConnection).setSSLSocketFactory(new SSLTunnelSocketFactory(System.getProperty("proxyHost"), System.getProperty("proxyPort")));
}
myConnection.setDoInput(true);
myConnection.setDoOutput(true);
BufferedReader in;
try {
System.err.println("opening Input stream1");
in = new BufferedReader(
new InputStreamReader(
myConnection.getInputStream()));
String inputLine;
System.err.println("Input stream is Open1");
while ((inputLine = in.readLine()) != null) {
System.err.println(inputLine);
}
in.close();
System.err.println("Input stream is Closed1");
} catch (Exception e) {
e.printStackTrace(System.err);
String tmp = e.getMessage().toLowerCase().trim();
System.err.println("tmp *" + tmp + "*");
if (tmp.indexOf("http") > -1) {
//http error message to be parsed
tmp = tmp.substring(tmp.indexOf("http")).trim();
System.err.println("tmp *" + tmp + "*");
tmp = tmp.substring(8).trim();
System.err.println("tmp *" + tmp + "*");
if (tmp.startsWith("407")) {
//proxy authentication required
myURL = new URL(connectionURL);
myConnection = myURL.openConnection();
if (myConnection instanceof com.sun.net.ssl.HttpsURLConnection) {
((com.sun.net.ssl.HttpsURLConnection) myConnection).setSSLSocketFactory(new SSLTunnelSocketFactory(System.getProperty("proxyHost"), System.getProperty("proxyPort"), proxyUsername, proxyPassword));
}
myConnection.setDoInput(true);
myConnection.setDoOutput(true);
try {
System.err.println("opening Input stream 2");
in = new BufferedReader(
new InputStreamReader(
myConnection.getInputStream()));
String inputLine;
System.err.println("Input stream is Open 2");
while ((inputLine = in.readLine()) != null) {
System.out.println(inputLine);
}
in.close();
System.err.println("Input stream is closed 2");
} catch (Exception ex) {
System.err.println(ex.getMessage());
ex.printStackTrace(System.err);
}
}
}
}
}
}
/**
* SSLSocket used to tunnel through a proxy
*/
class SSLTunnelSocketFactory extends SSLSocketFactory {
private String tunnelHost;
private int tunnelPort;
private SSLSocketFactory dfactory;
private String tunnelPassword;
private String tunnelUserName;
private boolean socketConnected = false;
private int falsecount = 0;
/**
* Constructor for the SSLTunnelSocketFactory object
*
*@param proxyHost The url of the proxy host
*@param proxyPort the port of the proxy
*/
public SSLTunnelSocketFactory(String proxyHost, String proxyPort) {
System.err.println("creating Socket Factory");
tunnelHost = proxyHost;
tunnelPort = Integer.parseInt(proxyPort);
dfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
}
/**
* Constructor for the SSLTunnelSocketFactory object
*
*@param proxyHost The url of the proxy host
*@param proxyPort the port of the proxy
*@param proxyUserName username for authenticating with the proxy
*@param proxyPassword password for authenticating with the proxy
*/
public SSLTunnelSocketFactory(String proxyHost, String proxyPort, String proxyUserName, String proxyPassword) {
System.err.println("creating Socket Factory with password/username");
tunnelHost = proxyHost;
tunnelPort = Integer.parseInt(proxyPort);
tunnelUserName = proxyUserName;
tunnelPassword = proxyPassword;
dfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
}
/**
* Sets the proxyUserName attribute of the SSLTunnelSocketFactory object
*
*@param proxyUserName The new proxyUserName value
*/
public void setProxyUserName(String proxyUserName) {
tunnelUserName = proxyUserName;
}
/**
* Sets the proxyPassword attribute of the SSLTunnelSocketFactory object
*
*@param proxyPassword The new proxyPassword value
*/
public void setProxyPassword(String proxyPassword) {
tunnelPassword = proxyPassword;
}
/**
* Gets the supportedCipherSuites attribute of the SSLTunnelSocketFactory
* object
*
*@return The supportedCipherSuites value
*/
public String[] getSupportedCipherSuites() {
return dfactory.getSupportedCipherSuites();
}
/**
* Gets the defaultCipherSuites attribute of the SSLTunnelSocketFactory
* object
*
*@return The defaultCipherSuites value
*/
public String[] getDefaultCipherSuites() {
return dfactory.getDefaultCipherSuites();
}
/**
* Gets the socketConnected attribute of the SSLTunnelSocketFactory object
*
*@return The socketConnected value
*/
public synchronized boolean getSocketConnected() {
return socketConnected;
}
/**
* Creates a new SSL Tunneled Socket
*
*@param s Ignored
*@param host destination host
*@param port destination port
*@param autoClose wether to close the socket automaticly
*@return proxy tunneled socket
*@exception IOException raised by an IO error
*@exception UnknownHostException raised when the host is unknown
*/
public Socket createSocket(Socket s, String host, int port, boolean autoClose)
throws IOException, UnknownHostException {
Socket tunnel = new Socket(tunnelHost, tunnelPort);
doTunnelHandshake(tunnel, host, port);
SSLSocket result = (SSLSocket) dfactory.createSocket(tunnel, host, port, autoClose);
result.addHandshakeCompletedListener(
new HandshakeCompletedListener() {
public void handshakeCompleted(HandshakeCompletedEvent event) {
System.out.println("Handshake Finished!");
System.out.println("\t CipherSuite :" + event.getCipherSuite());
System.out.println("\t SessionId: " + event.getSession());
System.out.println("\t PeerHost: " + event.getSession().getPeerHost());
setSocketConnected(true);
}
});
// thanks to David Lord in the java forums for figuring out this line is the problem
// result.startHandshake(); //this line is the bug which stops Tip111 from working correctly
return result;
}
/**
* Creates a new SSL Tunneled Socket
*
*@param host destination host
*@param port destination port
*@return tunneled SSL Socket
*@exception IOException raised by IO error
*@exception UnknownHostException raised when the host is unknown
*/
public Socket createSocket(String host, int port)
throws IOException, UnknownHostException {
return createSocket(null, host, port, true);
}
/**
* Creates a new SSL Tunneled Socket
*
*@param host Destination Host
*@param port Destination Port
*@param clientHost Ignored
*@param clientPort Ignored
*@return SSL Tunneled Socket
*@exception IOException Raised when IO error occurs
*@exception UnknownHostException Raised when the destination host is
* unknown
*/
public Socket createSocket(String host, int port, InetAddress clientHost,
int clientPort)
throws IOException, UnknownHostException {
return createSocket(null, host, port, true);
}
/**
* Creates a new SSL Tunneled Socket
*
*@param host destination host
*@param port destination port
*@return tunneled SSL Socket
*@exception IOException raised when IO error occurs
*/
public Socket createSocket(InetAddress host, int port)
throws IOException {
return createSocket(null, host.getHostName(), port, true);
}
/**
* Creates a new SSL Tunneled Socket
*
*@param address destination host
*@param port destination port
*@param clientAddress ignored
*@param clientPort ignored
*@return tunneled SSL Socket
*@exception IOException raised when IO exception occurs
*/
public Socket createSocket(InetAddress address, int port,
InetAddress clientAddress, int clientPort)
throws IOException {
return createSocket(null, address.getHostName(), port, true);
}
/**
* Sets the socketConnected attribute of the SSLTunnelSocketFactory object
*
*@param b The new socketConnected value
*/
private synchronized void setSocketConnected(boolean b) {
socketConnected = b;
}
/**
* Description of the Method
*
*@param tunnel tunnel socket
*@param host destination host
*@param port destination port
*@exception IOException raised when an IO error occurs
*/
private void doTunnelHandshake(Socket tunnel, String host, int port) throws IOException {
OutputStream out = tunnel.getOutputStream();
//generate connection string
String msg = "CONNECT " + host + ":" + port + " HTTP/1.0\n"
+ "User-Agent: "
+ sun.net.www.protocol.http.HttpURLConnection.userAgent;
if (tunnelUserName != null && tunnelPassword != null) {
//add basic authentication header for the proxy
sun.misc.BASE64Encoder enc = new sun.misc.BASE64Encoder();
String encodedPassword = enc.encode((tunnelUserName + ":" + tunnelPassword).getBytes());
msg = msg + "\nProxy-Authorization: Basic " + encodedPassword;
}
msg = msg + "\nContent-Length: 0";
msg = msg + "\nPragma: no-cache";
msg = msg + "\r\n\r\n";
System.err.println(msg);
byte b[];
try {
//we really do want ASCII7 as the http protocol doesnt change with locale
b = msg.getBytes("ASCII7");
} catch (UnsupportedEncodingException ignored) {
//If ASCII7 isn't there, something is seriously wrong!
b = msg.getBytes();
}
out.write(b);
out.flush();
byte reply[] = new byte[200];
int replyLen = 0;
int newlinesSeen = 0;
boolean headerDone = false;
InputStream in = tunnel.getInputStream();
boolean error = false;
while (newlinesSeen < 2) {
int i = in.read();
if (i < 0) {
throw new IOException("Unexpected EOF from Proxy");
}
if (i == '\n') {
headerDone = true;
++newlinesSeen;
} else
if (i != '\r') {
newlinesSeen = 0;
if (!headerDone && replyLen < reply.length) {
reply[replyLen++] = (byte) i;
}
}
}
//convert byte array to string
String replyStr;
try {
replyStr = new String(reply, 0, replyLen, "ASCII7");
} catch (UnsupportedEncodingException ignored) {
replyStr = new String(reply, 0, replyLen);
}
//we check for connection established because our proxy returns http/1.1 instead of 1.0
if (replyStr.toLowerCase().indexOf("200 connection established") == -1) {
System.err.println(replyStr);
throw new IOException("Unable to tunnel through " + tunnelHost + ":" + tunnelPort + ". Proxy returns\"" + replyStr + "\"");
}
//tunneling hanshake was successful
}
}
<----- End Copy and Paste -------->