MITM attack on MQTT over TLS

I’m reviewing the Losant SDK code for Arduino.
I notice that while the device connects to Losant platform over TLS, it has no way to verify the certificate.

  • The CA certificate or server certificate is not specified in the code, and I doubt the device would have a complete CA certificate bundle.
  • Even if we put in a CA certificate or server certificate: The device does not have a reliable source of clock, and therefore cannot determine whether the certificate is expired.
  • If we set clock from NTP: the device cannot verify the identity of the NTP server.

Man-In-The-Middle attacker can use an invalid certificate and pretend as the Losant broker.
Therefore, the protocol design of MQTT over TLS is flawed.

I think a better design is to use symmetric encryption negotiated with ECDH.
The workflow would be:

  1. To register a device, the user generates an EC key pair locally, and enter the public key into Losant platform.
  2. When a device connects to Losant, it sends its deviceId in plaintext.
  3. The server queries the database to find the public key of the device.
  4. The server generates a temporary EC key pair, and sends the public key to the device. The device does not verify this public key.
  5. Both the server and the device calculate a symmetric key with ECDH algorithm. This algorithm, with 160-bit keys, is reasonable fast on a microcontroller.
  6. Subsequent messages are encrypted with the symmetric key.

Note that the device’s EC public key from step1 is not actually be public. It’s only shared between Losant and the device.
Since the device’s EC public key is only known by the device itself and the Losant platform, MITM attacker cannot calculate the correct symmetric key with ECDH.

A simpler design is to use a pre-shared symmetric key for all communication.
However, a drawback for this approach is that all traffic is using the same symmetric key, so the key has higher exposure.

Our SDK does not perform client certificate validation because the implementation differs based on board. The verification is left up to the user’s higher level code. We should provide this code in our examples as it is best practice.

Validation when using ESP8266 Arduino core:

// broker.losant.com's SHA1 fingerprint.
const char* fingerprint = "A8 3F A8 1A DE 10 E7 8F 71 AE 5F C4 0E 57 57 77 6D D3 91 DF";

// WiFi connection logic goes here.

client.connect("broker.losant.com", 8883)
if (client.verify(fingerprint, "broker.losant.com")) {
    Serial.println("certificate matches");
  } else {
    Serial.println("certificate doesn't match");
}

The Arduino WiFi101 has certificate validation built-in. The shield has a default set of root certificates it will accept, Digicert (ours) being one of them. There is also a tool to add and remove these if needed.

Certificate validation in this manner is a very typical and accepted authorization approach. In order for a MITM attacker to masquerade as Losant, they would have to get a certificate signed by Digicert with our common name. Good authorities, like Digicert, exist to make sure certificates like that cannot be created.

TLS certificates, including broker.losant.com's certificate, have limited validity period.
If we specify broker’s fingerprint in client code or configuration, when the broker has a certificate roll-over, the device would stop working.

It’s great to learn that WiFi101 could verify a certificate chain from CA certificates, which solves the above problem, as CA certificates are assumed to have a validity period longer than lifetime of client devices.
I agree that any reputable CA would not issue a certificate with SNA=broker.losant.com unless it has verified the requester’s identity.

However, since the device does not know the current time, it still cannot verify whether a certificate has expired.
Is there a way to solve this problem?

Note: This post is not targeting Losant platform. It’s about MQTT over TLS protocol in general. I have cleared “bug report” topic to reflect this.