At SPE, we’ve been quite involved in setting up and using MQTT, a messaging protocol that has been around since 1999, but was adopted widely with the introduction of the Internet of Things.
MQTT was originally designed as a machine-to-machine protocol, which makes ideal for low-power IoT devices. It also widely used in cloud applications with only few competitors such as AMQP.
Let’s begin with security…
Transport Layer Security (TLS)
MQTT is an application layer protocol and offers no security features. Instead the advice is to use MQTT along with TLS, resulting in “MQTTS”. The same procedure is used for many other application layer protocols, e.g. HTTP/HTTPS.
TLS is the most widely used encryption protocol to provide communication security for network connections. By utilizing public-key cryptography, it offers authentication for the connecting parties. In the most widely used case, HTTP, this happens typically only for the server.
Authentication is the proof that my connection partner is who he/she claims to be. In a typical HTTP use-case, most clients (website visitors) are not required to provide this proof when establishing a connection – the important part is that the client can trust the server.
The Authentication is realized by using a public key, also called certificate. A secure server provides a certificate when the connection is established.
Only providing a plain certificate doesn’t help the client. Validating a certificate happens cryptographically by checking its signature and is only possible when the client has the certificate that was used for signing.
Signing is an important part of public-key cryptography. By sharing a public key (lets call it “CA.cert” among many clients, each of them can make sure that a server’s certificate (“server.cert”) is valid as long as it was signed with CA.cert.
The process to sign a certificate is very simple, a server sends an unsigned certificate (also called a certificate signing request or CSR) to a CA and receives the signed certificate in return.
Now, the server can provide the certificate when a client wants to connect. Now, the client needs to able to validate this certificate. To do so, as mentioned above, the CA’s certificate must be available.
This is what most operating systems and web browsers provide to the user. Firefox, Chrome, Linux, OSX, etc. all provide a collection of big CA’s certificates to their users. Often, the web browsers use the certificates that the respective OS provides, but not always. An example for that would be the package “ca-certificates”, that many Linux distributions provide with their package manager.
As I mentioned before, the typical HTTP use case provides authentication only for the server. This is the case because many users that don’t need to be authenticated will visist a website. A secure connection is already established between the client and the server, so even if the client needs to authenticate in the next step, this happens in a secure way, usually with a standard username/password login.
Now, there are other cases than HTTP. For our MQTT setup, we want to uniquely and securely identify every client that connects to a broker. This means, a client connecting to the broker must be able to validate the broker’s certificate, but also vice-versa. To do so, each client needs an own certificate – the commonly called client certificate.
Don’t be confused by the name, this certificate doesn’t differ from a server’s certificate, the process of creating one is the same as above. The only difference is that a client certificate will usually not include a domain name/URL in its meta data (a server certificate usually does), since, well, most clients don’t have a (public) URL.
Most public CAs only sign certificates that are bound to a domain name, effectively restricting their scope to servers. This has security reasons: in the HTTP(s) use-case, a connecting client not only wants to validate a server certificate, but also wants to ensure that this certificate also belongs to the visited domain/URL.
That all makes client certificates a bit harder to obtain. But it is OK since most applications don’t need to rely on client certificates. In our case, we specifically want to sign client certificates – and to do so, we need to set up our own certificate authority!
Your own (root) certificate authority
The term “certificate authority” sounds very official, but is in reality nothing more than public/private TLS key-pair (certificate + private-key bundle). The difference to a normal bundle is that the certificate of a root CA is “self-signed”. That means that there is no other certificate “above” the CA’s certificate that could be used for signing.
Please note the term “root CA” that I’ve used. It comes from the structure that builds up when one certificates signs another, this one again signs the next, etc. – for each certificate, a chain of certificates it exist, each signed by the next one above. The uppermost certificate is the root of the chain.
Since every CA can sign multiple certificates, a tree of certificates develops after a while.
Please note that this is, of course, only very simple excourse to the world of certificate authorities. In reality, CAs restrict what they sign and not every certificate can be used as a CA (more precise: as an “intermediate” CA). For example, you wouldn’t use your domain’s key/cert-bundle to sign client certificates since those certificates would effectively also be valid for your domain.
This is also one of the reasons we need our own CA and we come back to topic. You will find example code below for creating your own root CA. I use openssl under linux, but the same process is possible under other OSes as well.
Please note the flags:
- x509 instructs openssl to create a certificate after the standard with the same name
- newkey rsa:4096 generates a new rsa key with 4096 bits
- keyout defines the filename to put the private key in
- out defines the filename to put the certificate in
- subj defines a string of meta data, in this case an Organization and a Common Name
You now have 2 files:
- The private key *_ROOT_CA_KEY.pem
- The certificate, self-signed, *_ROOT_CA_CERT.pem
You can now create new key-pairs for your server or any client and use the CA files to sign the certificates.
You will notice the similarity to the creation of the CA. The difference here is that we need two steps, one for creating the key-pair and one for signing.
- nodes tells openssl to create the key-pair without password protection (be aware that your files will not be protected)
- in takes in the certificate signing request (csr, or effectively an unsigned certificate)
- CA defines the signing CA’s certificate file
- CAkey defines the signing CA’s private key
- out again defines the filename to put the signed certificate in
- set_serial is a counter of certificates signed by this CA. This serial together with the CA is a unique identifier for the certificates, provided that the serial number is increased every time.
The resulting *_KEY.pem and *_CERT.pem files are your key-pairs for each client or broker.
Please note that all key-pairs, also for the CA, are valid only for 30 days. You can change this by adding the -days option.
Very simple setup without TLS
Make sure to install node.js, npm and use npm to install Mosca. This should work:
MQTTs config for Mosca
You will find a (now) working example for running Mosca with TLS below.
Notice the exact structure of the options object. The names of the keys are partially extracted from the source code since the documentation of Mosca is partially outdated.
Now, TLS is in the first step guaranteeing authentication – that means that a client needs to provide a certificate to be able to connect. We have a two-way authentication, the server against the clients and the clients against the server.
Since we’re issueing different certificates to different clients, we can use informations from these certificates to also provide authorization – granting permissions to the clients.
For Mosca, there is no built-in authorization/permission mechanism, but it provides two callbacks: authorizeSubscribe and authorizePublish. You can build you own logic on how to grant permission there, based on the Common Names of the client or the issuer, or the serial number of the certificate. The serial number is self-maintained (see the above steps where the certificates are generated), so if it is done correctly, a database based on this number is a possible implementation for saving/providing grants.
Grants should be based on the topics that are requested. So in the end, you would have to access a list of topic names (wildcards are possible, of course!) for each serial number (or Common Name).
Your server should now be good to run.
Testing with mqtt.js
You can test your server with this small mqtt.js example script:
If you can see the message
Hello from TestClient-########
Congratulations! You now have a simple setup for super-secure MQTT using client certificates.
Notes on MQTT 5
With MQTT 5, which is not yet supported by Mosca, a new feature was introduced: Shared Subscriptions.
Shared Subscriptions basically allow round-robin loadbalancing for subscribers. Let’s say you have 3 Clients Alice, Bob and Eve. All 3 want to subscribe to a topic, but only one of them needs to some work when a message is received, not all of them.
By prefixing a subscriper-group to the original topic, you can ensure that only one, but also guaranteed one (if available) client that subscribed within that group will receive messages published to the original topic.
For example, let’s say we have
- our original topic: test/topic
- a subscriber-group: somegroup
- our client Alice, Bob and Eve
Normally all clients would just subscribe to test/topic.
In our case, the subscription topic changes to the schema
As you can see, there is a special $share prefix, followed by a colon, followed by the name of the subscriber-group (choose whatever name you want), again a colon, and the original topic.
Now, if our clients all subscribe to $share:somegroup:test/topic, only one of them will receive any message published to test/topic.
Please note here that you still have to publish to the original topic. This is important since you can still have clients subscribing directly to test/topic, but also as many subscriber-groups as you wish.
This gives clients a powerful tool to decide when and how they want to receive messages in a loadbalanced or similar environment of many-similar-clients, where a message only needs to be delivered to one of those clients.