You want to communicate over a secure channel but your client application doesn’t support it? That’s a job for a transparent SSL proxy.
This articles describes on how to use stunnel as a transparent proxy to encrypt communication. With a transparent proxy you do not need to change the client application.
You want to send data securely between two computers over the internet but your sender application does not support SSL.
We use stunnel to wrap the RTMP stream with SSL/TLS (“SSL proxy”) and deliver it to the RTMP server. The server uses stunnel again to decrypt the RTMP stream.
Now the sender acts as if it supports encryption and decryption. In the case of ffmpeg it would send an encrypted stream when the SSL proxy is active and an unencrypted one when the SSL proxy is inactive:
$ ffmpeg -re -i "~/some.mp3" -acodec copy -f flv 'rtmp://www.example.org:443'
The needed steps are:
- Install stunnel on a server called gateway
- Route the sender traffic to the gateway without changing the receiver destination
- Configure stunnel to re-route the traffic to the receiver address after SSL-wrapping it
1. Install stunnel on a server
Stunnel runs on all popular operating systems. To install it use your package manager, e.g. on macOS
brew install stunnel. Stunnel accepts an incoming connection on a specified port, encrypts it and send it to another specified address.
The default configuration location is
$(brew --prefix)/etc/stunnel/stunnel.conf on macOS). Change its content to
# /etc/stunnel/stunnel.conf foreground = yes debug = 5 [my-encryption-service] client = yes accept = 1234 connect = www.example.org:443
Stunnel now listens to traffic on port 1234, encrypts this traffic and routes it to www.example.org on port 443. For further explanation of the config parameters check
$ man stunnel. You can test the configuration by starting stunnel and on the same machine connecting to port 1234 (in another terminal window since stunnel is set to
foreground = yes):
$ sudo stunnel $ echo "hi" | nc 127.0.0.1 1234
The stunnel log should then show that you successfully connected to the remote server over a secure connection:
[…] LOG5: Service [my-encryption-service] accepted connection from 127.0.0.1:53870 LOG5: s_connect: connected 18.104.22.168:443 LOG5: Service [my-encryption-service] connected remote server from 192.168.0.1:53871 LOG5: Connection closed: 3 byte(s) sent to TLS, 0 byte(s) sent to socket
You could stop here if the destination addresses were known beforehand and could be written in the config. In our example that’s not the case so proceed to step 2:
2. Route the sender traffic to the gateway without changing the receiver destination
Now that stunnel works, all the to-be-encrypted traffic needs to be directed to the stunnel port transparently, i.e. without the sender knowing that its traffic gets redirected. This can be done by setting the stunnel server IP as a gateway for the client.
On the sender: Setting a gateway or router IP is usually done in the network configuration. Depending on the client application or operating system that is done in different ways. Search for “set default gateway” and add the operating system your sender runs on if you’re not sure. Then set the “gateway” or “router” IP to the stunnel server from step 1. Now all the traffic which is sent from the client is routed to the gateway address.
On the gateway: The gateway needs to be configured to not drop those packets but to route them to their destination. This is called “IP Forwarding” and should be enabled. Under Linux that can be done with
$ echo 1 > /proc/sys/net/ipv4/ip_forward. Search online for other operating systems.
Now the client computer should still be able to normally access the network but all its traffic is routed over the gateway before accessing the internet. Which enables us to do the last step:
3. Configure stunnel to re-route the traffic to its original destination after SSL-wrapping it
Instead of hard-coding a destination like we did in step 1, change the stunnel configuration to read like this:
# /etc/stunnel/stunnel.conf foreground = yes debug = 5 [my-encryption-service] client = yes accept = 443 transparent = destination
accept port should be equal to the port on the receiver side and we do not have a hard-coded destination anymore but instead use the original destination.
On the gateway: Route the incoming traffic which is directed to port 443 to the local port 443 of stunnel. To do this you need to know how your network is named (look at
$ ifconfig, in this example I use “eth0” and a Linux gateway):
$ iptables -I INPUT -i eth0 -p tcp --dport 443 -j ACCEPT $ iptables -t nat -I PREROUTING -p tcp --dport 443 -i eth0 -j DNAT --to-destination [youriphere]:443
Make it secure
Now you should be able to connect to any service with your sender over an encrypted connection as long as you use the configured port (443 in our example) in the destination address.
To prevent man in the middle attacks, tell stunnel which certificates to accept by concatenating all the valid certificates in a file and add this to the config:
# /etc/stunnel/stunnel.conf verify = 4 CAfile = /etc/stunnel/your_certificates.crt
Ask the receiver maintainer which certificates are valid or have a look yourself at the current certificate chain with this command:
$ openssl s_client -showcerts -connect www.example.org:443 </dev/null
If you need SNI this approach might not work with all protocols without additional work. The reason is that for SNI you need the destination domain which is written inside the application protocol and must be read by stunnel. But stunnel does not understand all protocols (it normally doesn’t need to for just encrypting it). It does understand some protocols, though, like imap, http, proxy, smtp and others.
The performance of stunnel is nice, but you need to pay attention to some settings like ulimit and which ciphers you use.