Implementing a Simple HTTPS Proxy Application with Java?
Asked Answered
M

2

7

I'm writing a simple HTTPS proxy program with Java for educational purposes. My program listens on a port (say 7443) for incoming HTTPS requests from a browser (say Firefox), parses the request and forwards it to the desired destination (say https://www.comodo.com).

Firefox's proxy settings are set to use my port for SSL connections ( 127.0.0.1 : 7443 ).

My code is short and simple:

static // initializer
{
    System.setProperty("javax.net.ssl.keyStore", "MyKeyStore");
    System.setProperty("javax.net.ssl.keyStorePassword", "password");
}

SSLServerSocketFactory ssFactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();

try {
    SSLServerSocket listener = (SSLServerSocket) ssFactory.createServerSocket(port, 64);
    listener.setUseClientMode(false);
    listener.setWantClientAuth(false);
    listener.setNeedClientAuth(false);

    SSLSocket connection = (SSLSocket) listener.accept();
    browser.startHandshake();  /*  <<==  Exception throws at this line  */

} catch (IOException ex) {
    ex.printStackTrace(System.err);
}

But I'm catching the following exception:

    javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?

The exception says that the connection could be plain-text, but only HTTPS connections from Firefox are set to use this port. I have logged what Firefox is sending to my application which is this:

CONNECT www.comodo.com:443 HTTP/1.1
User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:20.0) Gecko/20100101 Firefox/20.0
Proxy-Connection: keep-alive
Connection: keep-alive
Host: www.comodo.com

Firefox is talking palin-text, and I think CONNECT is a SOCKS command (I'm not sure though), where I haven't set anything in Firefox's SOCKS settings. Below is a screenshot of Firefox's proxy settings:

Firefox Proxy Settings

What am I missing here ?! What I need to do to make this work with Firefox or any other browser ?!

------------------------------------------------------------------------------

For those who think this is a duplicate of another question and that it has been answered in the other one I have to say: Yes, both questions have roots in a similar problem but the only answer in the cited question directs at using SSL Sockets which turned out to be misleading and resulted in this new question. So although they are aimed at a similar problem, this question shows a completely different and yet mislead path to go for solving the problem and so it could provide useful guidance for future persons facing such a problem.

Mollie answered 3/5, 2013 at 12:7 Comment(2)
This is not a duplicate of the cited question. The two questions are clearly different and there is no acceptable solution in the cited question.Mollie
For those who ended up in this thread: you might be interested in the working example of TheConstructor here: #16351913Acme
R
7

Get rid of all the SSL. Just process the incoming CONNECT command, make a plaintext connection to the upstream server, and then start copying bytes. The browser and the server will speak SSL but you don't need to at all.

Refreshing answered 4/5, 2013 at 18:25 Comment(6)
I don't think this is correct ... I have previously tried this solution and it didn't work ... You can check out my previous question about this solution at #14154162Mollie
Certainly it's correct. See the RFC. Obviously the browser is sending the CONNECT command in plaintext, and obviously you can infer that it is expecting the reply in plaintext, and obviously if you transfer all other bytes in both directions as they are, without further intervention, any problems remainining must be at the server or browser end. You need to be more specific than "it didn't work". The other question you cited doesn't provide any further information either.Refreshing
As I said, I have tried exactly what you are suggesting and actually this was the first method that came to my mind, but it failed. As mentioned in the other question I provided, forwarding the browser's CONNECT request to the server always results in a connection reset error, which means that the server doesn't respond and closes the connection ... This behavior is obvious, because the server is listening for an SSL/TLS HELLO message on 443, and it gets a CONNECT, which is not an expected SSL/TLS message.Mollie
@SeyedMohammad, your proxy shouldn't forward the CONNECT request, but process it, that is, establish a plain TCP connection to the server and return 200 OK. Then, your proxy server should simply relay the traffic between the client and the server. The SSL/TLS layer will be on top of this relayed connection, but the proxy itself doesn't get involved in this.Pga
@Pga Thanks for the tip ... I'll give this a try and let you know if it worked ... Just to make sure I've understood correctly, when I receive the browser's CONNECT command, I should establish a connection to the destination server, and upon successful connection I should return a 200 OK to the browser and after that just forward the bytes between the browser and the server ... Right?Mollie
That's exactly what both of us have been saying. Nobody said to forward the CONNECT command. So you didn't 'try exactly this' at all. And it's no good just repeating that you've 'tried exactly this' without showing us the code.Refreshing
F
4

Your setup is using HTTP tunneling, where the initial request sent to the proxy is not SSL encrypted; since the SSL-enabled socket is expecting an SSL handshake, it throws an exception.

In this mechanism, the client asks an HTTP Proxy server to forward the TCP connection to the desired destination using the "CONNECT" HTTP method. The server then proceeds to make the connection on behalf of the client. Once the connection has been established by the server, the Proxy server continues to proxy the TCP stream to and from the client. Note that only the initial connection request is HTTP - after that, the server simply proxies the established TCP connection.

You can read more about it at HTTP Tunneling wiki page. To see this in action, you can start off a netcat server and set the Firefox proxy to point to that port:

nc -l 8000

Now in Firefox type in https://www.google.com, and examine the nc output:

CONNECT www.google.com:443 HTTP/1.1
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:21.0) 
Proxy-Connection: keep-alive
Connection: keep-alive
Host: www.google.com

And this is completely in plaintext. Below diagram demonstrates how Firefox proxy expects to communicate.

enter image description here

Forename answered 6/6, 2013 at 5:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.