Getting started with MQTT

At this point, I have a few IOT things on my home network. The first were a couple of EcoPlugs Wifi outlets that I use to control my gutter heaters in the winter, and the next was a custom garage door controller. For the former, I’m basically controlling them by using a replay attack (I hope to make a post about this soon)…re-sending packets that I observed the EcoPlugs app sending. For the latter, I’m using a custom http interface. Since both of these contain an ESP8266, I’d like re-program them and unify them both to use the MQTT protocol.

While browsing Hackaday, I came across their Minimal MQTT series. In this post, I’ll begin by walking through that series, noting my thoughts, then conclude by getting things set up the way I want.

Building a Broker

Link to article

Not really much to say here. On Ubuntu, I ran the following commands to install a recent version of mosquitto:

> sudo add-apt-repository ppa:mosquitto-dev/mosquitto-ppa
> sudo apt-get update
> sudo apt-get install mosquitto mosquitto-clients

The example mosquitto_sub and mosquitto_pub commands work…good.

Networked Nodes

Link to article

In my experience, NodeMCU sucks. It’s quick for prototyping, but it doesn’t have the fine-grained control that I want, and it’s unreliable. I’ll use the Arduino IDE instead.

This blogger recommends PubSubClient. The library doesn’t support publishing as QoS=1, but does support “retain”, which is really what I want anyway.

The library is really easy to install in Arduino IDE. In the menu, do Sketch->Include Library->Manage Libraries... then search for PubSubClient, select it, and click install.

The example code works perfectly.

  1. In the menu, File->Examples->PubSubClient->mqtt_esp8266
  2. change SSID, Password, and mqtt server
  3. upload the sketch
  4. start serial monitor
  5. On a linux terminal (not the serial monitor), run:
    1. mosquitto_sub -h localhost -v -t "outTopic"
    2. mosquitto_pub -h localhost -t "inTopic" -m "1"
    3. mosquitto_pub -h localhost -t "inTopic" -m "0"

The “inTopic” messages show up in the serial output, but don’t toggle the LED for me. I think I selected the wrong board… Yes, when I programmed as “WeMos D1 R2 & mini” instead of “Adafruit HUZZAH ESP8266” the LED works as expected!

Next, I modified the code to publish a “retain” message for the LED state, and it works great:

// Switch on the LED if an 1 was received as first character
if ((char)payload[0] == '1') {
digitalWrite(BUILTIN_LED, LOW); // Turn the LED on (Note that LOW is the voltage level
client.publish("LED_state", "ON", true);
// but actually the LED is on; this is because
// it is acive low on the ESP-01)
} else {
digitalWrite(BUILTIN_LED, HIGH); // Turn the LED off by making the voltage HIGH
client.publish("LED_state", "OFF", true);
}

And from a terminal:

> mosquitto_sub -h localhost -v -t "LED_state"
LED_state ON

Control and Clients

Link to Article

Probably the most useful part is the “MQTT Dashboard” Android app. There isn’t much else interesting to me here.

Power and Privacy

Link to Article

The power stuff isn’t too interesting…my devices will have constant power, not battery (for now).

Privacy is more interesting, but it doesn’t really describe what I want.

My custom setup – investigation

I want:

  1. anonymous access on the local network
  2. username/password required on a TLS port that I’ll port-forward to the internet

This section is kind of stream-of-consciousness notes of my trial-and-error in trying to accomplish these stated goals. If you just want to see when I ended up with, then skip to the next section.

Unfortunately, the allow_anonymous config option is a global option, not per-listener (see the mosquitto.conf man page).

Sounds like these folks bridge two brokers to accomplish this, but I’d rather just run a single instance of mosquitto.

Maybe ACLs?
The ACL description in acl_file (in the mosquitto-conf documentation linked above) doesn’t sound like you can ACL based on IP or anything.

This guy uses a PSK to accomplish it. Ok, so how do I use a PSK on my phone? “MQTT Dashboard” has something for a BSK file…

I guess what I really want isn’t “PSK”, but it’s the keyfile.

Use portecle to create a BKS file, and export the key to PEM format for mosquitto. NOTE: I had to use java-9-oracle, and I had to do this:

> sudo update-alternatives --config java
(select java-9-oracle)
> cd /usr/lib/jvm/java-9-oracle/lib/security/
> sudo mkdir limited_policy
> sudo cp *.jar limited_policy/
> sudo cp unlimited_policy/*.jar ./

my /etc/mosquitto/conf.d/local.conf:

port 1883

listener 8883
capath /etc/ssl/certs
certfile /etc/ssl/private/server_4096_2016.crt
keyfile /etc/mosquitto/certs/mqtt.pem
require_certificate true
use_identity_as_username true

After a reload, I get this error:

1479358332: Error: Unable to load server key file "/etc/mosquitto/certs/mqtt.pem". Check keyfile.

Duh, I realized that the “keyfile” isn’t the key from the client that I want to trust, it’s the SSL/TLS key that the server uses.

I also read that mosquitto may not have access to /etc/ssl/private (due to apparmor?), so I copied the keys into the mosquitto directory

port 1883

listener 8883
tls_version tlsv1.2
capath /etc/ssl/certs
certfile /etc/mosquitto/certs/server_4096_2016.crt
keyfile /etc/mosquitto/certs/server_4096.pem
require_certificate true
use_identity_as_username true

And it loads now!

So, now to create a client key…

Back in portecle, I created a new BKS keystore, generated a key pair, then generated a certification request for that key.

Now, I’m using the mosquitto-tls man page as a guide:

I ran this command to create a new CA for myself:

openssl req -new -x509 -days -extensions v3_ca -keyout ca.key -out ca.crt

Then, I already had the server side set up, and I already had the client through the certificate signing request, so I only had to run this command to sign the key (created by portecle) with my newly-created CA key:

openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt -days

Then, back in portecle, I imported the trusted certificate. It gave me some grief because my self-created CA isn’t trusted, but it gives you the option of manually trusting it anyway.

Now, I think I have to get mosquitto to trust my CA key as well. I’ll try adding it as a cafile…hopefully mosquitto will support having both a capath and a cafile at the same time. (Note: it does.)

mosquitto doesn’t barf, but the app won’t connect…I get this:

1479444878: New connection from 192.168.1.1 on port 8883.
1479444878: OpenSSL Error: error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown
1479444878: OpenSSL Error: error:140940E5:SSL routines:ssl3_read_bytes:ssl handshake failure
1479444878: Socket error on client , disconnecting.

Ok, so I’m getting somewhere…Let’s try this without involving the app.

client side:

> mosquitto_pub -h home.mblythe.net -p 8883 --cert ~/Downloads/mqtt.crt --key ~/Downloads/mqtt.pem --capath /etc/ssl/certs --cafile ~/Downloads/mblythe_ca.crt -t "inTopic" -m 1 -d
Enter PEM pass phrase:
Client mosqpub/23189-bruce sending CONNECT
Error: A TLS error occurred.

server error:

1479450665: New connection from 192.168.1.1 on port 8883.
1479450672: OpenSSL Error: error:14094418:SSL routines:ssl3_read_bytes:tlsv1 alert unknown ca


A new day…let’s start over a bit.

Let’s walk through the steps on the mosquitto-tls man page again:

> cd mqtt_key_stuff/
> openssl genrsa -des3 -out mqtt2.key 2048
> openssl req -out mqtt2.csr -key mqtt2.key -new
> sudo openssl x509 -req -in mqtt2.csr -CA mblythe_ca.crt -CAkey /etc/ssl/private/ca.key -CAcreateserial -out mqtt2.crt -days 90
> mosquitto_pub -h home.mblythe.net -p 8883 --cert ~/mqtt_key_stuff/mqtt2.crt --key ~/mqtt_key_stuff/mqtt2.key --capath /etc/ssl/certs --cafile ~/mqtt_key_stuff/mblythe_ca.crt -t "inTopic" -m 1 -d
> mosquitto_pub -h home.mblythe.net -p 8883 --cert ~/mqtt_key_stuff/mqtt2.crt --key ~/mqtt_key_stuff/mqtt2.key --capath /etc/ssl/certs --cafile ~/mqtt_key_stuff/mblythe_ca.crt -t "inTopic" -m 0 -d

It works!!!

Let’s remove this line from /etc/mosquitto/conf.d/local.conf and make sure it fails (i.e. we’re actually authenticating the client cert).

cafile /etc/mosquitto/certs/mblythe_ca.crt

Good, it fails:

> mosquitto_pub -h home.mblythe.net -p 8883 --cert ~/Downloads/mqtt2.crt --key ~/Downloads/mqtt2.key --capath /etc/ssl/certs --cafile ~/Downloads/mblythe_ca.crt -t "inTopic" -m 0 -d
Enter PEM pass phrase:
Client mosqpub/7388-bruce sending CONNECT
Error: A TLS error occurred.

Ok, let’s fire up portecle again and add this key & cert to a BKS keystore. Looks like it won’t import the key…it’s looking for a PKCS12 file type. Conversion command from here.

> openssl pkcs12 -export -inkey mqtt2.key -in mqtt2.key -out mqtt2.p12
Enter pass phrase for mqtt2.key:
unable to load certificates

This site clued me into the fact that the -in option should be the certificate, not the key again

> openssl pkcs12 -export -inkey mqtt2.key -in mqtt2.crt -out mqtt2.p12
Enter pass phrase for mqtt2.key:
Enter Export Password:
Verifying - Enter Export Password:

Great!

In the meantime, I’ve upgraded Ubuntu. For whatever reason, the changes I made above to enable the unlimited crypto stuff are no longer there, and there’s not even the unlimited_policy directory anymore. This lead me to this which lead me to this, which describes the change.

I made the following change to /etc/java-9-oracle/conf/security/java.security
Before:

crypto.policy=limited

After:

crypto.policy=unlimited

I made the BKS file (with both the key cert, and even the CA cert), and MQTT Dashboard still fails to connect. The server gives me this error:

1479961185: OpenSSL Error: error:14094416:SSL routines:ssl3_read_bytes:sslv3 alert certificate unknown
1479961185: OpenSSL Error: error:140940E5:SSL routines:ssl3_read_bytes:ssl handshake failure

I also found the log that the android app creates (from the app webpage):

You can find the error log in: External storage (SD-card) / mqtt-dashboard / log

In that logfile:

#ERROR at 21:20:05 Client failed to connect
MqttException (0) – javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at org.eclipse.paho.client.mqttv3.internal.ExceptionHelper.createMqttException(Unknown Source)
at org.eclipse.paho.client.mqttv3.internal.ClientComms$ConnectBG.run(Unknown Source)
at java.lang.Thread.run(Thread.java:818)
Caused by: javax.net.ssl.SSLHandshakeException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:322)
at org.eclipse.paho.client.mqttv3.internal.SSLNetworkModule.start(Unknown Source)
… 2 more
Caused by: java.security.cert.CertificateException: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
at com.android.org.conscrypt.TrustManagerImpl.checkTrusted(TrustManagerImpl.java:324)
at com.android.org.conscrypt.TrustManagerImpl.checkServerTrusted(TrustManagerImpl.java:225)
at com.android.org.conscrypt.Platform.checkServerTrusted(Platform.java:114)
at com.android.org.conscrypt.OpenSSLSocketImpl.verifyCertificateChain(OpenSSLSocketImpl.java:550)
at com.android.org.conscrypt.NativeCrypto.SSL_do_handshake(Native Method)
at com.android.org.conscrypt.OpenSSLSocketImpl.startHandshake(OpenSSLSocketImpl.java:318)
… 3 more
Caused by: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.
… 9 more

So, it looks like the app wants my client key cert to have a proper chain of trust. Lame.

I tried signing my client cert with my server’s private key, but I couldn’t figure out the right commands for openssl, and I’m not sure the app would accept it anyway. I also couldn’t find anyone who did free client certificate signing. (Well, StartSSL might, but most of the web browsers don’t trust that CA anymore.)

My custom setup – final configuration

As I said above, I originally wanted:

  1. anonymous access on the local network
  2. username/password required on a TLS port that I’ll port-forward to the internet

Unfortunately, mosquitto doesn’t support requiring username & password authentication on only one listener. Also, the other forms of SSL-based authentication didn’t pan out, so I think I’ll have to compromise on #1, and just require username & password for all MQTT clients (including my IOT devices themselves).

Ok, so what does that look like…

In the configuration file /etc/mosquitto/conf.d/local.conf (or just in /etc/mosquitto/mosquitto.conf, if you prefer):

port 1883
listener 8883
tls_version tlsv1.2
capath /etc/ssl/certs
certfile /etc/mosquitto/certs/mqtt_combined.crt
keyfile /etc/mosquitto/certs/server_4096.pem
allow_anonymous false
password_file /etc/mosquitto/users.passwd

To create the password file:

> sudo mosquitto_passwd -c /etc/mosquitto/users.passwd testuser
Password:
Reenter password:
> sudo chown mosquitto:mosquitto /etc/mosquitto/users.passwd
> sudo chmod 600 /etc/mosquitto/users.passwd

To add more MQTT users:

> sudo mosquitto_passwd /etc/mosquitto/users.passwd testuser2
Password:
Reenter password:

And let’s restart mosquitto to pick up these changes:

> sudo service mosquitto restart

In my Arduino code:

if (client.connect("ESP8266Client", mqtt_user, mqtt_pass)) {

On the command line:

mosquitto_sub -h localhost -t "LED_state" -u testuser -P testpass

or

mosquitto_sub -h home.mblythe.net -p 8883 -t "LED_state" -u testuser -P testpass --capath /etc/ssl/certs

And the config in MQTT-Dashboard is straightforward.

Also, my intention is to have the SSL/TLS protected port be exposed to the wider internet. This will vary from router to router, but I set up a port-forwarding rule to forward internet traffic on port 8883 to my MQTT server’s port 8883.


Leave a Reply