While having a username and password is better than an open MQTT server, the credentials are still being sent in plain text, so anyone with a packet sniffer could intercept them and then gain access to our server.
TLS provides a chain of trust, allowing two computers that know nothing about each other to trust each other.
Let's look at how this works in the context of a browser hitting a secure website, as this is an example we should all be familiar with.
At a really high level, both your computer and the server have a Certificate Authority (CA) that they both implicitly trust (there is actually more than one, but let's keep it simple). When you hit a website that is secured the server sends it's certificate to the browser, which does some complicated maths and verifies the certificate against the CA - if the numbers work out, your browser can be confident that the certificate is legitimate and that the secure site is who it says it is.
Web servers generally don't care who the client is, so they rarely ask for a client certificate, but in other applications (like this garage door opener) the server will also verify the client against it's CAs.
After both computers are happy they are talking to the right people, they will exchange a set of symmetric keys that both computers will use to communicate. Symmetric keys are much faster to encrypt and decrypt that asymmetric keys, but it requires both sides to have the same key.
To do that securely, a symmetric key is generated, encrypted using the other computer's public key, sent securely across the wire and then decrypted - now both computers have the key and they can start talking.
Setting up our own trust network
Enough theory! The first thing to do is generate a CA. If you have OpenSSL installed on your computer (Linux and OSX users more than likely will), you can generate a CA on the command line:
openssl req -new -x509 -days 3650 -extensions v3_ca -keyout ca.key.pem -out ca.crt.pem
You'll be asked for a PEM password - make this something difficult to guess and then store it in your password manager.This is literally the key to the city. If someone gets hold of your ca.crt.pem and ca.key.pem, and can guess your password, then they can generate their own valid certificates - not good.Fill in the details as you are asked - these details aren't super important as this is a personal CA, but make the values something you will recognise. The common name can be whatever you want. Now that you have your CA certificate (ca.crt.pem) and your CA.key (ca.key.pem) you can generate some certificates.An aside: If you have a password manager that supports notes, I'd recommend saving these files in the there and deleting them from your file system after you have generated any certificates that you need.
Generate a server certificate
For this to work, it's best to use actual domain names for servers and clients - the easiest way to do this is setting up mDNS, via avahi (linux) or bonjour (OSX) - Windows probably has something too - let's assume that the name of the computer running MQTT is mqtt.local and the name of the garage door opener will be garage.local.
# Generate a key openssl genrsa -out mqtt.local.key.pem 2048 # Create a Certificate signing request openssl req -out mqtt.local.csr.pem -key mqtt.local.key.pem -new
Again, fill in the details as you are asked.
The MOST important one is Common Name. IT MUST MATCH THE DOMAIN. So in our case: mqtt.local
# Sign the certificate openssl x509 -req -in mqtt.local.csr.pem -CA ca.crt.pem -CAkey ca.key.pem -CAcreateserial -out mqtt.local.crt.pem -days 365
You will be asked for the password you entered when you created your CA.
Note that the certificate is valid for 365 days - you'll have to generate a new one in a years time. You can decide if you want to make it longer or shorter.
Generate the client certificate
Generating a client certificate looks a lot like the generation of a server certificate - just with different filenames.
# Generate a key openssl genrsa -out garage.local.key.pem 2048 # Create a Certificate signing request openssl req -out garage.local.csr.pem -key garage.local.key.pem -newThe common name this time should be garage.local
# Sign the certificate openssl x509 -req -in garage.local.csr.pem -CA ca.crt.pem -CAkey ca.key.pem -CAcreateserial -out garage.local.crt.pem -days 365
Setting up Mosquitto
Now we have all of the certificates that we need, let's setup mosquitto to use it.
Create a ca_certificates and certs directory in the mosquitto/etc docker folder
mkdir mosquitto/etc/ca_certificates mkdir mosquitto/etc/certsand copy ca.crt.pem to the ca_certificates folder and mqtt.local.crt.pem and mqtt.local.key.pem to the certs folder. Finally update the etc/mosquitto.conf file to look something like this:
persistence true persistence_location /var/lib/mosquitto/ password_file /etc/mosquitto/passwd allow_anonymous false cafile /etc/mosquitto/ca_certificates/ca.crt.pem certfile /etc/mosquitto/certs/mqtt.local.crt.pem keyfile /etc/mosquitto/certs/mqtt.local.key.pem port 8883 tls_version tlsv1.1 include_dir /etc/mosquitto/conf.d
Notice we have changed the port to 8883, which is the standard port for secure MQTT. We are also still authenticating via username and password.
We are explicitly forcing TLS version 1.1 because the ESP8266 implementation of 1.2 is buggy, and will fail.
Save the file, and run
docker-compose build mosquitto
Because we aren't verifying the certificates, we can use the garage.local certificate on the command line to test everything out.
Create the ca_certificates and certs directories, this time in mosquitto-client
mkdir mosquitto-client/ca_certificates mkdir mosquitto-client/certs
Copy ca.crt.pem to the ca_certifications directory and both the garage.local.crt.pem and garage.local.key.pem files to the certs directory.
Now, modify the Dockerfile.sub ENTRYPOINT to look like this:
ENTRYPOINT [ "mosquitto_sub", "-h", "mosquitto", "-p", "8883", "--tls-version", "tlsv1.1", "--cafile", "/ca_certificates/ca.crt.pem", "--insecure", "--cert", "/certs/garage.local.crt.pem", "--key", "/certs/garage.local.key.pem" ]and the Dockerfile.pub ENTRYPOINT to look like:
ENTRYPOINT [ "mosquitto_pub", "-h", "mosquitto", "-p", "8883", "--tls-version", "tlsv1.1", "--cafile", "/ca_certificates/ca.crt.pem", "--insecure", "--cert", "/certs/garage.local.crt.pem", "--key", "/certs/garage.local.key.pem" ]
docker-compose upAnd everything should connect again, but this time securely!
All of these instructions gratuitously stolen from: https://mosquitto.org/man/mosquitto-tls-7.html