I’ve used stunnel in the past for wrapping my raw TCP connections with an encrypted tunnel for traversing public or unknown networks. It’s a nice tool that interoperates smoothly with other software, and relieves the TCP application developer from needing to correctly implement secure TLS connections.

Recently I found it can also be configured to handle client side HTTP CONNECT proxy connections. This is probably not something you’d want to rely on indefinitely as a replacement for implementing HTTP CONNECT in your TCP application, but for validating an approach or when you need support in a pinch for software you can’t edit yourself, it’s a valuable trick.

First, what is an HTTP CONNECT proxy? Basically it’s a web server that will listen for a CONNECT verb request and if permissioned to do so, will establish a TLS connection between the requested destination host and requesting client. Once the tunnel is established, you no longer need to send HTTP requests, you can just send direct TCP messages.

Apache is an example web server that can act as an HTTP CONNECT proxy. Here’s some sample configuration that will listen for CONNECT requests:

<VirtualHost *:80>
    ProxyRequests On
    ProxyVia On
    <Proxy *>
      Order allow,deny
      Allow from all
    </Proxy>
    AllowCONNECT 8080
</VirtualHost>

You could connect to this proxy using curl like this:

curl -v -s -X GET --proxy http://proxyserver.local:80 privateserver.local:8080

If the service you’re running behind the proxy isn’t an HTTP server this command will fail. Instead you could validate the tunnel is being established through the proxy using openssl like this:

openssl s_client -proxy proxyserver.local:80 -connect privateserver.local:8080

You’d want to example the certificate response here to validate your private server is responding to the request.

So where does stunnel come in?

Well - what if your client application doesn’t know how to speak HTTP?

Generally stunnel expects you to initiate a connection to stunnel with a raw TCP connection, and have the connection destination be a TLS listener ready to make a handshake. But in the case where your client is initiating a TLS connection, you can run stunnel with two listeners - one in client mode and one in server mode.

In this configuration you’ll connect your TCP client to the server mode listener, and then the server mode listener will connect to the stunnel client mode listener, which is configured to send a CONNECT request to your proxy.

Finally, the client listener will connect to your proxy server running a raw TCP listener (no TLS), which will in turn establish a TLS tunnel with your original client and the proxied destination host.

Here’s what your stunnel conf file could look like with the client and server mode listerners:

cert = /home/vagrant/stunnel.pem
key = /home/vagrant/stunnel.pem
client = yes

verify = 0
CApath = /etc/ssl/certs/ca-bundle.crt
debug = 7

fips = no
foreground = yes

socket = l:TCP_NODELAY=1
socket = r:TCP_NODELAY=1

[client_tls_to_tcp]
client = no
accept = 0.0.0.0:8080
connect = localhost:9090

[tcp_to_server_tls]
client = yes
accept = localhost:9090
connect = proxyserver.local:80
protocol = connect
protocolHost = privateserver.local:8080