2

I am currently working on a client/server TLS tool that requires us to connect through firewalls. For reasons that are outside our control, we are only granted an outgoing TCP connection.

The problem is that our client's firewall blocks the client Hello v2 message (and possibly the whole SSL handshake).

Is there a way to obfuscate the stream in some manner? I was thinking about trying to use compression to make the stream unreadable to the firewall. (Maybe using JDK7's GzipOutputStream which now allows for syncFlush flushing)

I am no SSL expert but it seems to me it should be possible to translate the whole stream which should make it impossible for the firewall to pick up the connection and block it.

As far as I can see, there are a few (two?) ways to go about this :

  • Override the default implementation
  • Implement SSLServerSocketFactory

The first option didn't work out for me as I am unable to find the source code of com.sun.net.ssl.internal.ssl.SSLServerSocketFactoryImpl, which is the default implementation. I did browse the openJDK source code for it, but even there, the sources appear to be missing.

Implementing a SSLServerSocketFactory is beyond my capabilities. As I said I am no SSL expert.

Please note that the application does work fine through other, less agressive firewalls / firewall rules.

Charles V.G.
  • 169
  • 4
  • 11
  • can't you put the ssl socket inside another socket? The outside socket would care of the compression and the SSL socket would comunicate through it – woliveirajr Jul 18 '11 at 20:45
  • Is it specifically blocking SSLv2 and not SSLv3 or TLS 1.x? If so, why not upgrade to SSLv3 (or better, TLS 1.x), SSLv2 is insecure anyway. – Bruno Jul 18 '11 at 23:39
  • @Bruno unless he is using IBM JSSE he isn't using SSLv2 anyway, just the ClientHandshake message, for upgrading. Sun JSSE doesn't support SSLv2 beyond that. – user207421 Jul 19 '11 at 00:27
  • I've never heard of a firewall that blocks just SSLv2. To do the firewall would have to parse the SSL client hello. – President James K. Polk Jul 19 '11 at 01:09
  • Thank you all for sharing your insights. I am using TLSv1. I believe Bruno is right, the Sun implementation only uses the SSLv2 spec for the clientHandshake. – Charles V.G. Jul 19 '11 at 06:40
  • Gregs, I do believe the firewall parses the SSL client hello (I am working on proving it). A quick search yields [this](http://help.mysonicwall.com/sw/eng/6005/ui2/25201/Firewall_scSslControlView.html "this") example of a firewall that does so. – Charles V.G. Jul 19 '11 at 06:47
  • @Charles, from your link, it looks like other settings may also need to be taken into account: selection of cipher suites, using a certificate with a SHA-1 signature (not MD5) and/or a certificate trusted by the firewall itself. I would suggest asking your client's admin how it is configured. Note that by trying to get around these settings, you're might be breaking your client's policy, which may not be such a good idea. Of course, if they let plain TCP that the firewall doesn't understand go through, it's a bit like putting a reinforced gate on a park with short fences you can jump over. – Bruno Jul 19 '11 at 10:20

4 Answers4

4

Compressing an encrypted stream is not useful, where you actually only want some masking to avoid your firewall.

On the client side, you can use the SSLSocketFactory's method createSocket(socket, host, port, autoclose) to create a SSL socket based on another socket - and this another socket can get your special SocketImpl implementation, doing a simple XOR-masking on the first some bytes.

On the server side, it is more complicated, since the SSLServerSocketFactory has no such method.

In a answer to Java RMI + SSL + Compression = IMPOSSIBLE!, I described how to build a delegating Socket factory. There it was done for a Rmi(Client|Server)SocketFactory, but it would work in an analogous way for a ServerSocketFactory or SocketFactory.


But of course it could be that your firewall is not actually blocking SSL traffic, but blocking anything that is not whitelisted (like HTTP). Before building your wrapping socket implementation, try if a simple socket+serversocket which sends some random data and receives them back even works.

Community
  • 1
  • 1
Paŭlo Ebermann
  • 73,284
  • 20
  • 146
  • 210
  • Thank you for your answer, I will look into your previous answer in more depth (I remember reading that thread). I'm sorry I forgot to mention that plaintext TCP communication works. – Charles V.G. Jul 18 '11 at 21:55
  • 1
    @Paŭlo Ebermann: you can do this on the server side too. `createSocket(socket, host, port, autoclose)` isn't specific to the client side, you can still configure the converted socket on the server side as a server socket by using `setUseClientMode(false)`. See http://stackoverflow.com/questions/6559859/is-it-possible-to-change-plain-socket-to-sslsocket/6564086#6564086 – Bruno Jul 18 '11 at 23:47
  • @Bruno: nice idea, this will facilate everyting a bit. – Paŭlo Ebermann Jul 19 '11 at 00:05
  • @Charles: see Bruno's comment. – Paŭlo Ebermann Jul 19 '11 at 00:05
  • Thank you both, I succesfully applied Bruno's suggestion. I then went on applying Paŭlo's solution. I'm finally able to influence the underlying input and output streams! I think I will eventually undo Bruno's suggestion : it does make things more concise/shorter , but I find it makes the code harder to read (whereas without it I have more overrides, but the logic is easier to understand). I will report back with some code example when I'm done so that others may benefit from this. – Charles V.G. Jul 19 '11 at 09:39
2

It is possible to tunnel TCP through HTTP without additional development. There are various tools. Look at GNU httptunnel. httptunnel creates a bidirectional virtual data connection tunnelled in HTTP requests. The HTTP requests can be sent via an HTTP proxy if so desired.Httpc is quite interresting too.

zacheusz
  • 8,750
  • 3
  • 36
  • 60
  • Wouldn't this kind of tunneling infer a significant overhead? It certainly would add dependencies which implies more maintenance, downtime,... Additionally there is a chance those firewalls I'm battling against would still pick up the clientHellov2 inside the http tunnel (I assume those are plaintext). – Charles V.G. Jul 19 '11 at 06:55
  • Yes it can be overhead, but I don't think that it will be overkill. If it catch clientHellov2 inside http tunnel you can simply add xor or base64 encoding. This is quite simple coz the tools are open source. Check Brunos' solution below. It is good to try, but IMHO the firewall will catch it. – zacheusz Jul 19 '11 at 07:36
2

Perhaps slightly off-topic, but if the problem is specifically about SSLv2, and if SSLv3 or TLS 1.x handshakes work fine, you could disable the V2 ClientHello, by using SSLSocket.setEnabledProtocols(new String[] { "SSLv3", "TLSv1", "TLSv1.1" }).

See the JSSE Reference guide (section on SSLContext).

EDIT: For those who don't read comments, here is a link to @EJP's answer with more details on this topic: Why does Java's SSLSocket send a version 2 client hello?

Community
  • 1
  • 1
Bruno
  • 119,590
  • 31
  • 270
  • 376
  • There is additional information here too: http://stackoverflow.com/questions/4682957/why-does-javas-sslsocket-send-a-version-2-client-hello/4686924#4686924 – Bruno Jul 18 '11 at 23:58
  • +1 This is the correct solution. Sun Java doesn't actually support SSLv2, only the client handshake part, so the firewall's 'help' is pointless in this case anyway. IBM's JSSE does support SSLv2 for some reason. – user207421 Jul 19 '11 at 00:26
  • Thank you for your answer. This seems to be the best way of setting up my SSL connection. Nevertheless if the firewalls are indeed parsing the clientHello v2 handshake, isn't there a chance the firewalls will pick up the clientHello v3 (if there even is such a thing?) – Charles V.G. Jul 19 '11 at 07:01
  • I enabled debugging using -Djavax.net.debug=all . After only enabling SSLv3 and TLSv1 protocols I can see it uses the TLSv1 handshake. After only one or two seconds the connection is dropped (server:SSLHandshakeException , client:EOFException). When I used the SSLv2 client handshake, the data would never arrive at the server side, causing a timeout. It seems to me this means the firewall is picking up the TLSv1 handshake as well. It was a good suggestion though. – Charles V.G. Jul 19 '11 at 07:52
1

It appears the solution is to combine Bruno's suggestion and Paulo's solution.

Paulo's solution allows us to customize the behavior of our SSLSocket or SSLServerSocket using delegates.

Bruno's suggestion allows us to tell the default SSL implementation to use our modified SSLSocket or SSLServerSocket.

Here is what I did :

  • Create a delegate ServerSocket class ( MyServerSocket )
  • Create a delegate ServerSocketFactory class (MyServerSocketFactory)
  • Create a delegate SocketFactory class (MySocketFactory)
  • Create a delegate Socket class (MySocket)
  • Create XorInputStream (find it here)
  • Create XorOutputStream (find it here)

On the server side :

    // Initialisation as usual
    ...
    sslSocketFactory = sslContext.getSocketFactory();

    serverSocketFactory = ServerSocketFactory.getDefault();
    serverSocketFactory = new MyServerSocketFactory(serverSocketFactory);
    serverSocket = serverSocketFactory.createServerSocket(port);
    ...
    Socket s = (Socket) serverSocket.accept();
    sslSocket = (SSLSocket) sslSocketFactory.createSocket(s, null, s.getPort(), false);
    sslSocket.setUseClientMode(false);
    sslSocket.setEnabledCipherSuites(new String[]{"SSL_RSA_WITH_RC4_128_MD5"});
    sslSocket.setNeedClientAuth(true);
    ...

On the client side:

    Socket s = new MySocketFactory(SocketFactory.getDefault()).createSocket(host, port);
    SSLSocket socket = (SSLSocket) factory.createSocket(s, host, port, false);

Sources



    public class MyServerSocket extends ServerSocket {
    private ServerSocket baseSocket;

    public MyServerSocket(ServerSocket baseSocket) throws IOException {
        this.baseSocket = baseSocket;
    }

    @Override
    public Socket accept() throws IOException {
        return new MySocket(baseSocket.accept());
    }

    @Override
    public void bind(SocketAddress endpoint) throws IOException {
        baseSocket.bind(endpoint);
    }

    @Override
    public void bind(SocketAddress endpoint, int backlog) throws IOException {
        baseSocket.bind(endpoint, backlog);
    }

    @Override
    public void close() throws IOException {
        baseSocket.close();
    }

    @Override
    public ServerSocketChannel getChannel() {
        return baseSocket.getChannel();
    }

    @Override
    public InetAddress getInetAddress() {
        return baseSocket.getInetAddress();
    }

    @Override
    public int getLocalPort() {
        return baseSocket.getLocalPort();
    }

    @Override
    public SocketAddress getLocalSocketAddress() {
        return baseSocket.getLocalSocketAddress();
    }

    @Override
    public synchronized int getReceiveBufferSize() throws SocketException {
        return baseSocket.getReceiveBufferSize();
    }

    @Override
    public boolean getReuseAddress() throws SocketException {
        return baseSocket.getReuseAddress();
    }

    @Override
    public synchronized int getSoTimeout() throws IOException {
        return baseSocket.getSoTimeout();
    }

    @Override
    public boolean isBound() {
        return baseSocket.isBound();
    }

    @Override
    public boolean isClosed() {
        return baseSocket.isClosed();
    }

    @Override
    public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
        baseSocket.setPerformancePreferences(connectionTime, latency, bandwidth);
    }

    @Override
    public synchronized void setReceiveBufferSize(int size) throws SocketException {
        baseSocket.setReceiveBufferSize(size);
    }

    @Override
    public void setReuseAddress(boolean on) throws SocketException {
        baseSocket.setReuseAddress(on);
    }

    @Override
    public synchronized void setSoTimeout(int timeout) throws SocketException {
        baseSocket.setSoTimeout(timeout);
    }

    @Override
    public String toString() {
        return baseSocket.toString();
    }


    }


    public class MyServerSocketFactory extends ServerSocketFactory {

    private ServerSocketFactory baseFactory;

    public MyServerSocketFactory(ServerSocketFactory baseFactory) {
        this.baseFactory = baseFactory;
    }


    @Override
    public ServerSocket createServerSocket(int i) throws IOException {
        return new MyServerSocket(baseFactory.createServerSocket(i));
    }

    @Override
    public ServerSocket createServerSocket(int i, int i1) throws IOException {
        return new MyServerSocket(baseFactory.createServerSocket(i, i1));
    }

    @Override
    public ServerSocket createServerSocket(int i, int i1, InetAddress ia) throws IOException {
        return new MyServerSocket(baseFactory.createServerSocket(i, i1, ia));
    }


    }


    public class MySocket extends Socket {
    private Socket baseSocket;

    public MySocket(Socket baseSocket) {
        this.baseSocket = baseSocket;
    }

    private XorInputStream xorInputStream = null;
    private XorOutputStream xorOutputStream = null;
    private final byte pattern = (byte)0xAC;

    @Override
    public InputStream getInputStream() throws IOException {
        if (xorInputStream == null)
        {
            xorInputStream = new XorInputStream(baseSocket.getInputStream(), pattern);
        }
        return xorInputStream;
    }

    @Override
    public OutputStream getOutputStream() throws IOException {
        if (xorOutputStream == null)
        {
            xorOutputStream = new XorOutputStream(baseSocket.getOutputStream(), pattern);
        }
        return xorOutputStream;
    }

    @Override
    public void bind(SocketAddress bindpoint) throws IOException {
        baseSocket.bind(bindpoint);
    }

    @Override
    public synchronized void close() throws IOException {
        baseSocket.close();
    }

    @Override
    public void connect(SocketAddress endpoint) throws IOException {
        baseSocket.connect(endpoint);
    }

    @Override
    public void connect(SocketAddress endpoint, int timeout) throws IOException {
        baseSocket.connect(endpoint, timeout);
    }

    @Override
    public SocketChannel getChannel() {
        return baseSocket.getChannel();
    }

    @Override
    public InetAddress getInetAddress() {
        return baseSocket.getInetAddress();
    }

    @Override
    public boolean getKeepAlive() throws SocketException {
        return baseSocket.getKeepAlive();
    }

    @Override
    public InetAddress getLocalAddress() {
        return baseSocket.getLocalAddress();
    }

    @Override
    public int getLocalPort() {
        return baseSocket.getLocalPort();
    }

    @Override
    public SocketAddress getLocalSocketAddress() {
        return baseSocket.getLocalSocketAddress();
    }

    @Override
    public boolean getOOBInline() throws SocketException {
        return baseSocket.getOOBInline();
    }

    @Override
    public int getPort() {
        return baseSocket.getPort();
    }

    @Override
    public synchronized int getReceiveBufferSize() throws SocketException {
        return baseSocket.getReceiveBufferSize();
    }

    @Override
    public SocketAddress getRemoteSocketAddress() {
        return baseSocket.getRemoteSocketAddress();
    }

    @Override
    public boolean getReuseAddress() throws SocketException {
        return baseSocket.getReuseAddress();
    }

    @Override
    public synchronized int getSendBufferSize() throws SocketException {
        return baseSocket.getSendBufferSize();
    }

    @Override
    public int getSoLinger() throws SocketException {
        return baseSocket.getSoLinger();
    }

    @Override
    public synchronized int getSoTimeout() throws SocketException {
        return baseSocket.getSoTimeout();
    }

    @Override
    public boolean getTcpNoDelay() throws SocketException {
        return baseSocket.getTcpNoDelay();
    }

    @Override
    public int getTrafficClass() throws SocketException {
        return baseSocket.getTrafficClass();
    }

    @Override
    public boolean isBound() {
        return baseSocket.isBound();
    }

    @Override
    public boolean isClosed() {
        return baseSocket.isClosed();
    }

    @Override
    public boolean isConnected() {
        return baseSocket.isConnected();
    }

    @Override
    public boolean isInputShutdown() {
        return baseSocket.isInputShutdown();
    }

    @Override
    public boolean isOutputShutdown() {
        return baseSocket.isOutputShutdown();
    }

    @Override
    public void sendUrgentData(int data) throws IOException {
        baseSocket.sendUrgentData(data);
    }

    @Override
    public void setKeepAlive(boolean on) throws SocketException {
        baseSocket.setKeepAlive(on);
    }

    @Override
    public void setOOBInline(boolean on) throws SocketException {
        baseSocket.setOOBInline(on);
    }

    @Override
    public void setPerformancePreferences(int connectionTime, int latency, int bandwidth) {
        baseSocket.setPerformancePreferences(connectionTime, latency, bandwidth);
    }

    @Override
    public synchronized void setReceiveBufferSize(int size) throws SocketException {
        baseSocket.setReceiveBufferSize(size);
    }

    @Override
    public void setReuseAddress(boolean on) throws SocketException {
        baseSocket.setReuseAddress(on);
    }

    @Override
    public synchronized void setSendBufferSize(int size) throws SocketException {
        baseSocket.setSendBufferSize(size);
    }

    @Override
    public void setSoLinger(boolean on, int linger) throws SocketException {
        baseSocket.setSoLinger(on, linger);
    }

    @Override
    public synchronized void setSoTimeout(int timeout) throws SocketException {
        baseSocket.setSoTimeout(timeout);
    }

    @Override
    public void setTcpNoDelay(boolean on) throws SocketException {
        baseSocket.setTcpNoDelay(on);
    }

    @Override
    public void setTrafficClass(int tc) throws SocketException {
        baseSocket.setTrafficClass(tc);
    }

    @Override
    public void shutdownInput() throws IOException {
        baseSocket.shutdownInput();
    }

    @Override
    public void shutdownOutput() throws IOException {
        baseSocket.shutdownOutput();
    }

    @Override
    public String toString() {
        return baseSocket.toString();
    }



    }

    public class MySocketFactory extends SocketFactory {

    private SocketFactory baseFactory;


    public MySocketFactory(SocketFactory baseFactory) {
        this.baseFactory = baseFactory;
    }


    @Override
    public Socket createSocket() throws IOException {
        return baseFactory.createSocket();
    }


    @Override
    public boolean equals(Object obj) {
        return baseFactory.equals(obj);
    }



    @Override
    public int hashCode() {
        return baseFactory.hashCode();
    }

    @Override
    public String toString() {
        return baseFactory.toString();
    }



    @Override
    public Socket createSocket(String string, int i) throws IOException, UnknownHostException {
        return new MySocket(baseFactory.createSocket(string, i));
    }

    @Override
    public Socket createSocket(String string, int i, InetAddress ia, int i1) throws IOException, UnknownHostException {
        return baseFactory.createSocket(string, i, ia, i1);
    }

    @Override
    public Socket createSocket(InetAddress ia, int i) throws IOException {
        return baseFactory.createSocket(ia, i);
    }

    @Override
    public Socket createSocket(InetAddress ia, int i, InetAddress ia1, int i1) throws IOException {
        return baseFactory.createSocket(ia, i, ia1, i1);
    }

    }

Charles V.G.
  • 169
  • 4
  • 11
  • 1
    Unfortunaly, it appears the firewall won't allow for any data outside the ascii range to be transferred...So I will have to talk to the clien'ts network admins again... – Charles V.G. Jul 19 '11 at 15:51
  • you can still tunnel through HTTP base64 encoded stream - it will be in ascii range :) or maybe instead of using XOR use base64 – zacheusz Jul 20 '11 at 01:47
  • Base64 should do the trick indeed. What I did not tell you is that for some reasons that are beyond the scope of this question, the firewall is configured to let FTP connections through. More tests showed that it will not allow me to send binary data (as I posted earlier) nor will it send ascii data if it is not followed by a new-line character. I probably could get binary data sent if I were to mimic FTP transfers but this is way overkill. Anyway my question did get solved, so this is off-topic. Thank you all for your time. – Charles V.G. Jul 20 '11 at 08:44
  • It appears the firewall, in this case a WatchGuard firewall, uses a proxy FTP server to force the traffic to take the shape of the FTP protocol on any rule that is labeled "FTP". In the end, we had to ask clients to open an outgoing "TCP" connection rule on the firewall. It should also have worked to mimic an FTP connection. – Charles V.G. Aug 03 '11 at 17:23