Implementing a Simple HTTPS Proxy Application with Java? - java

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:
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.

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.

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.

Related

HTTPS redirects get lost through tunneling

I experience a problem while doing integration tests of our system, that usage of a JSF application via https always returns a 400 (forbidden) when coming back from a redirect after POST because of conversion to http. This only happens, if the request is done through a tunneled connection via jumphost.
So here's the setup:
Server has a WildFly running with an JSF application and an nginx which cares about incoming https requests from 443 and transfer them to the WildFly port
Server is in a closed network which is accessable via a jumphost which again can be accessed from my machine
An SSH connection establishes a tunnel between a local port on my machine and the 443 port on the server
The browser requests https://localhost:myport
Now, whenever I post something, e.g. the login, the redirected answer comes back with http scheme and thus gives me a 400. If I manually add https in the browser, the requests gets answered correctly.
A curl of the same URL gives me this:
curl -i -k https://localhost:8426
HTTP/1.1 302 Found
Server: nginx
Date: Fri, 23 Oct 2020 15:03:52 GMT
Content-Length: 0
Connection: keep-alive
Set-Cookie: INCENTCONTROL_JSESSIONID=[....]; path=/
Location: http://localhost:8426/login.xhtml
If I do the very same directly from a machine within the network of the server, everything is fine.
What has the tunneling to do with the problem and does anyone have an idea how to overcome it?

Reverse proxying HTTP/2 from h2 to h2c

We have a java web server which is able to serve content over h2c (HTTP/2 clear text)
We would like to reverse proxy connections established using h2 (i.e. standard HTTP/2 over SSL) to the java server in h2c.
Enabling HTTP/2 on nginx is simple enough and handling incoming h2 connections works fine.
How do we tell nginx to proxy the connection using h2c rather than http/1.1 ?
Note: a non-nginx solution may be acceptable
server {
listen 443 ssl http2 default_server;
server_name localhost;
ssl_certificate /opt/nginx/certificates/???.pem;
ssl_certificate_key /opt/nginx/certificates/???.pk8.key.pem;
ssl_session_cache shared:SSL:1m;
ssl_session_timeout 5m;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
location / {
proxy_pass http://localhost:8080/; ## <---- h2c here rather than http/1.1
}
}
CONCLUSION (June 2016)
This can be done with haproxy using a configuration file as simple as the one below.
Querying (HttpServletRequest) req.getProtocol() clearly returns HTTP/2.0
global
tune.ssl.default-dh-param 1024
defaults
timeout connect 10000ms
timeout client 60000ms
timeout server 60000ms
frontend fe_http
mode http
bind *:80
# Redirect to https
redirect scheme https code 301
frontend fe_https
mode tcp
bind *:443 ssl no-sslv3 crt mydomain.pem ciphers TLSv1.2 alpn h2,http/1.1
default_backend be_http
backend be_http
mode tcp
server domain 127.0.0.1:8080
HAProxy does support that.
HAProxy can offload TLS and forward to a backend that speaks h2c.
Details on how to setup this configuration are available in this blog post.

How to make a HTTPS request via TOR?

I use SilverTunnel NG Netlib Java-library for connecting to TOR from Java.
HTTP connection via TOR to port 80 and a direct HTTPS connection to port 443 connection works fine.
But, when I'm trying to establish a HTTPS connection via TOR to 443 port,
I get answer from server
400 The plain HTTP request was sent to HTTPS port.
How do I fix this?

Plaintext Connection

I am creating a simple HTTPS connection and returning a response
URL google = new URL("https://www.google.com/");
HttpsURLConnection connection = (HttpsURLConnection)google.openConnection();
System.out.println( "Response: " + connection.getResponseCode());
This works fine and I receive a 200 response (I have all the keystores and truststores etc. defined)
I then proxy the request through a Jetty server by using https.proxyPort=8443 and https.proxyHost-localhost
The server has connectors that look like this:
Server server = new Server();
SelectChannelConnector connector0 = new SelectChannelConnector();
connector0.setPort(8080);
SslSelectChannelConnector sslConnector = new SslSelectChannelConnector();
sslConnector.setPort(8443);
SslContextFactory cf = sslConnector.getSslContextFactory();
cf.setKeyStorePath(keyStoreHome);
cf.setKeyStorePassword(keyStorePassword);
cf.setTrustStore(trustStoreHome);
cf.setTrustStorePassword(trustStorePassword);
cf.setCertAlias("jetty");
server.setConnectors(new Connector[] {connector0, sslConnector});
server.setHandler(new HelloHandler());
server.start();
server.join();
Which gives me the error below:
DEBUG: org.eclipse.jetty.io.nio - created SCEP#33589e56{l(/127.0.0.1:62348)<->r(/127.0.0.1:8443),d=false,open=true,ishut=false,oshut=false,rb=false,wb=false,w=true,i=0}-{SslConnection#7f033a6f SSL NOT_HANDSHAKING i/o/u=-1/-1/-1 ishut=false oshut=false {AsyncHttpConnection#26c623af,g=HttpGenerator{s=0,h=-1,b=-1,c=-1},p=HttpParser{s=-14,l=0,c=0},r=0}}
DEBUG: org.eclipse.jetty.io.nio.ssl - [Session-1, SSL_NULL_WITH_NULL_NULL] SslConnection#7f033a6f SSL NOT_HANDSHAKING i/o/u=180/0/0 ishut=false oshut=false {AsyncHttpConnection#26c623af,g=HttpGenerator{s=0,h=-1,b=-1,c=-1},p=HttpParser{s=-14,l=0,c=0},r=0} NOT_HANDSHAKING filled=180/180 flushed=0/0
DEBUG: org.eclipse.jetty.io.nio.ssl - SCEP#33589e56{l(/127.0.0.1:62348)<->r(/127.0.0.1:8443),d=true,open=true,ishut=false,oshut=false,rb=false,wb=false,w=true,i=0r}-{SslConnection#7f033a6f SSL NEED_WRAP i/o/u=180/0/0 ishut=false oshut=false {AsyncHttpConnection#26c623af,g=HttpGenerator{s=0,h=-1,b=-1,c=-1},p=HttpParser{s=-14,l=0,c=0},r=0}}
javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
at com.sun.net.ssl.internal.ssl.EngineInputRecord.bytesInCompletePacket(EngineInputRecord.java:152)
at com.sun.net.ssl.internal.ssl.SSLEngineImpl.readNetRecord(SSLEngineImpl.java:806)
at com.sun.net.ssl.internal.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:721)
at javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:607)
at org.eclipse.jetty.io.nio.SslConnection.unwrap(SslConnection.java:519)
at org.eclipse.jetty.io.nio.SslConnection.process(SslConnection.java:354)
at org.eclipse.jetty.io.nio.SslConnection.access$900(SslConnection.java:43)
at org.eclipse.jetty.io.nio.SslConnection$SslEndPoint.fill(SslConnection.java:661)
at org.eclipse.jetty.http.HttpParser.fill(HttpParser.java:1030)
at org.eclipse.jetty.http.HttpParser.parseNext(HttpParser.java:275)
at org.eclipse.jetty.http.HttpParser.parseAvailable(HttpParser.java:230)
at org.eclipse.jetty.server.AsyncHttpConnection.handle(AsyncHttpConnection.java:77)
at org.eclipse.jetty.io.nio.SslConnection.handle(SslConnection.java:191)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint.handle(SelectChannelEndPoint.java:620)
at org.eclipse.jetty.io.nio.SelectChannelEndPoint$1.run(SelectChannelEndPoint.java:46)
at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:603)
at org.eclipse.jetty.util.thread.QueuedThreadPool$3.run(QueuedThreadPool.java:538)
at java.lang.Thread.run(Thread.java:662)
DEBUG: org.eclipse.jetty.io.nio.ChannelEndPoint - close SCEP#33589e56{l(/127.0.0.1:62348)<->r(/127.0.0.1:8443),d=true,open=true,ishut=false,oshut=false,rb=false,wb=false,w=true,i=0!}-{SslConnection#7f033a6f SSL NEED_WRAP i/o/u=180/0/0 ishut=false oshut=false {AsyncHttpConnection#26c623af,g=HttpGenerator{s=0,h=-1,b=-1,c=-1},p=HttpParser{s=-14,l=0,c=0},r=0}}
DEBUG: org.eclipse.jetty.http.HttpParser -
javax.net.ssl.SSLException: Unrecognized SSL message, plaintext connection?
Can anybody please tell me what might be wrong with this?
By the looks of it the exception is occurring when the proxy is connecting the HTTP - 8080 and HTTPS - 8443. Does JETTY do decryption between those ports because if not the exception is about the 8080 being plain text.
This is more or less the same problem as in this question.
When you use https.proxyPort=8443 and https.proxyHost=localhost, the connection between your client and the proxy host/port isn't done using SSL/TLS, but in plain text HTTP. The client then sends the proxy a CONNECT request with the target host name (and this is way you get this "plain text" exception).
Then, the proxy makes a request to that target server and establishes a tunnel that relays all the raw traffic back and forth. From the client's point of view (HttpsUrlConnection), the socket that is connected to the proxy is upgraded to SSL/TLS (i.e. it initiate an handshake, which is relayed to the target server via that tunnel.
There aren't many clients that also support having the connection between the client and the proxy itself using HTTPS. This isn't generally very useful, since an attacker could only see the host name and port anyway.

Connect to FTPS through proxy

The initial problem we encountered was that a regular FTPs download started failing due to an untrusted server certificate. This prompted us to wonder whether the certificate had been updated without the counterparty notifying us so we wanted to download the current certificate and compare it to the one we have in our keystore.
This seems to be a trickier problem than we had anticipated. The usual suspects (firefox, filezilla, ...) did not seem up to the task of connecting to an FTPs server through an FTP proxy so out of curiosity I started playing around with a more low level java approach. I can not for the life of me get it to work though.
First (overly simplistic) java attempt:
// create proxy connection
SocketFactory plainFactory = SocketFactory.getDefault();
Socket proxy = plainFactory.createSocket(proxyServer, proxyPort);
// create ssl connection on top of it?
SSLSocketFactory sslFactory = getSocketFactory();
SSLSocket socket = (SSLSocket) sslFactory.createSocket(proxy, server, port, true);
This approach obviously does not work.
Next I started playing around with ftp4j (http://www.sauronsoftware.it/projects/ftp4j/) it seems to have a clean and accessible codebase:
FTPClient client = new FTPClient();
client.setConnector(new FTPProxyConnector(proxyHost, proxyPort));
client.getConnector().setConnectionTimeout(0);
client.getConnector().setReadTimeout(0);
client.setSSLSocketFactory(getSocketFactory());
// also tried SECURITY_FTPS
client.setSecurity(FTPClient.SECURITY_FTPES);
client.connect(server, port);
This outputs:
REPLY: 220 Blue Coat FTP Service
SEND: USER anonymous
REPLY: 530-User Access denied.
REPLY: 530-
REPLY: 530-Usage: USER username#hostname
REPLY: 331 PASS userpassword
Exception in thread "main" java.io.IOException: Invalid proxy response
The proxy server has optional authentication and on our development servers we generally use "user#host" without proxy authentication. As such I assume the username, hostname and password are those of the remote server?
So I tried adding the remote parameters, this does not work:
REPLY: 220 Blue Coat FTP Service
SEND: USER test#ftps.example.com
Exception in thread "main" java.io.IOException: FTPConnection closed
Adding the proxy user to match the bluecoat format does not seem to work either:
USER %u#%h %s
PASS %p
ACCT %w
Any help with either of these two problems would be most welcome:
how to retrieve the server certificate from an ftps server through an ftp proxy
how to connect to an ftps server through an ftp proxy in java
You might want to try Apache Net commons libs.
Here is a similar thread that uses that Net Commons library
Net commons also has a fully functional FTP Client Example so you can test with something you know works.

Categories

Resources