Using Tor To Secure IoT Deployments

Aryan Thakur
8 min readMay 30, 2021

This project was an inspired replica (almost) from a project in a research paper written by Dr. Gregory Epiphaniou from the University of Warwick.

In this project, we will be using a Raspberry Pi to host an Nginx-based Tor server that serves a flask built webapp constantly updating you on the connectivity status of an ESP8266. This will allow us to simulate an environment where locally deployed smart devices are made securely accessible both locally and over WAN. Let’s get into it!

Tor & Hidden Services 🧅

When people think of Tor, more often than not their mind goes to the dark web and all its innate evils. All the .onion URLs where you can buy hitmen or pay a hacker to ruin someone’s life. While all that may be true, and be accessible via Tor, that isn’t its true purpose.

Tor’s purpose, as per the official website, is to provide anonymity and encryption services to users while on the internet. Now the TLS protocol does encrypt data exchange but it is not fully secure against eavesdroppers that may be listening onto the connection.

Tor provides location proxy & data encryption services so no one can tell the location of a request’s origin or destination across a network. Tor forms onion circuits and relays before connecting to the actual site in order to mask the original IP and location of the client.

Now you can use Tor to access everyday sites like Google or Facebook, but you can also use Tor to access websites that are only accessible on the Tor network. Instead of using Tor as a relay to a server outside the network, these websites are established inside the Tor network itself and have a .onion URL that only Tor client browsers have the ability to access. The server hosted inside the Tor network is a Tor hidden service. If you are not familiar with how Tor or Tor relays function, this video does a good job of explaining them.

A Tor hidden service has some additional steps that both the client and the user must follow in order to establish a successful connection.

Connecting To A Hidden Service 🔗

To begin with, the server that is running the Tor service creates onion circuits to three introduction points on the Tor network. These are the three locations within the Tor network that the contents of the server will be hosted upon. While the server connects to these introduction points, it forms onion chains between them, they are given the public key, signed with the private key and this signed descriptor is added to the Tor Hash Table which is similar to the large search index but for onion sites.

Upon entering the onion URL, the client selects one of the three introduction points at random and forms an onion circuit to that proxy. Upon connecting with the introduction point, the client and the server exchange information about a rendezvous point alongside a secret passphrase. This rendezvous point is another Tor proxy where the server and the client will “meet”.

The client and the server both form an onion circuit to this rendezvous point where the secret passphrase is exchanged, and if all checks out, the contents of the server are served to the client.

In our scenario, we’ll be adding an additional layer of security to this by creating an authenticated tor service. In this case alongside the passphrase, the rendezvous point will check back with the server to look at the authorized public keys. If the client does not posses a private key matching the authorized public key, the connection to the server will not be successful. Thus only authorized users are granted access to the server upon providing a successful public/private keypair match.

Tip: Watch this video to solidify your understanding of Tor Hidden Services

Flask & The Web App🌶️

First, let’s set up our application. I used HTML, CSS, and a bit of jQuery to quickly spin up a simplistic interface for the web app, JSON to get input from the python app to the HTML webpage, and subprocess to instruct bash to get me a list of all available devices in the network.

The ESP8266🔌

The ESP8266 broadcasts itself as a Wi-Fi network when powered on, thus I grepped for the ESP ESSID and used an if statement to check for the connection status and pipe it onto the HTML page. I also started a webserver on 8080 locally to allow local users to interact with the device through this webpage as well.

Tip: Ensure you use ufw to open ports that you’ll be running your local server on.

uWSGI + Flask

Now we have to move this web app onto the Pi. Once we have the code all set up, we’ll be setting up uWSGI to allow us to communicate with our Nginx service later on. We do this by creating an entry point to our python application, as such:

We can now spin up the uWSGI configuration or .ini file to tell uWSGI what to do when it is called upon.

Since the Nginx server and uWSGI will be operating on the same computer, we’ll simply create a Unix socket between them to allow for reliable and secure communication between these two processes.

Tip: Create your socket in the /tmp folder as trying to create it elsewhere might through an error your way as SE-Linux may prevent this for security reasons.

We then give this socket the required permissions on the system (664 in our case which only allows users belonging to only the specifically mentioned group permissions to change the file).

System Service

Once we have the configuration file setup, we’ll be creating a system service to automatically serve the flask app using uWSGI upon boot. The service file is told the description of the service, the group it belongs to, the user that the service is to be executed by, and the command that must be executed. We also use the [Install] command to specify that we want to run this service every time the normal system (pi) is up and running. Then one can just use systemctl to enable and start the service.

Nginx as a Hidden Service

Before we configure and connect the uWSGI and the Nginx processes, we must first set up the Nginx server as a Tor hidden service. To do this we must first change the average hash bucket size in the nginx.conf file to 125 to allow for the onion URL length.

Once we have that done, we must edit our torrc file (/etc/tor) and under the hidden services, tab add /var/lib/tor/nginx. This creates a hidden service directory that hosts our Nginx server.

Once you’re here you might want to read the hostnames file and save the onion URL for later, this is the URL of your onion website.

Nginx <==> uWSGI

With our current architecture, the uWSGI server is waiting for incoming requests in the specified socket location. Since we are using Nginx to handle our requests, we have to proxy those requests from Nginx to the uWSGI server. We do this by creating a new server block file at /etc/nginx/sites-available/{yourproject}.

Current Nginx & uWSGI Architecture

In this file, we set up our server ports and domain name as such:

This proxies the requests received by the Nginx server to our socket and the uWSGI server essentially allowing us to communicate with the web app.

Then we form a system link between the sites-available and the sites-enabled directories to tell Nginx that our configured server/site block is available to be hosted.

Client Authorization

We have a site up and running but we are not done yet. We must authenticate the created hidden directory to add a layer of security to our project.

In order to do this, we use an official Tor managed script to generate a public/private key pair for our authorized user. For more info on how to do that, you can follow this link.

The generated public key will go under the authorized_clients directory in /var/lib/tor/nginx and the generated private key must be configured by the client for their tor browser.

The final step in my journey was to use a bash script and cron to restart this service every two minutes to ensure the status being displayed on the app is upto date.

The Finale

If all the steps were done right & errors dealt with, only the authorized and local clients should be able to access the deployed dynamic web app.

The web app shown is a simple proof of concept which could be developed into a fully functional IoT hub/web app, where you could fully communicate and control your smart devices as you normally would but in a much secure manner.

Hacked IoT devices not only present an immediate risk to the device but by the likes of network pivot, all the users connected to the same network as the device are prone to be attacked, eavesdropped on, and possibly even have their identity stolen. IoT device security extends way beyond just the device itself.

Comparison Pictures (Configured Auth vs Normal Tor vs Local):

Onion Site on PC Configured With Private Keys & Tor Browser
Onion Site On Brave Tor Service (No Private Key Configured)
Site being served locally.

Hey! Thank you for stopping by. I am a 16-year-old innovator who wishes to drive change and leave an impact on the world by the means of technology. Feel free to connect with me on LinkedIn, via email, or check out my personal website.