IoT-Security mit MQTT

#Kotlin #IoT #MQTT

MQTT ist das de facto Standardprotokoll für das Internet-of-Things. Die Eclipse-Paho-MQTT-Client-Bibliothek und das Plugin-System des MQTT-Brokers HiveMQ ermöglichen die Integrationen mit Java. Dieser Artikel bietet eine kurze Einführung in MQTT und zeigt, wie MQTT sicher verwendet werden kann.

Das Internet-of-Things (IoT) ist heutzutage in aller Munde. Selbst kleinste Geräte verfügen dank moderner Technik über Möglichkeiten zur Datenübertragung. Diese Geräte werden im Allgemeinen als Smart-Devices bezeichnet. Waschmaschinen, Toaster oder elektrische Rollläden – kaum ein technisches Gerät ist nicht dazu in der Lage sich mit dem Internet der Dinge zu verbinden. Beispielsweise werden gesammelte Daten zur Weiterverarbeitung an ein Backend-System geschickt oder Geräte reagieren auf einen Befehl, welcher über eine Handy-App ausgelöst wurde.

Dieser Artikel stellt MQTT vor, das sich inzwischen als de facto Standard Protokoll für IoT-Anwendungen etabliert hat und betrachtet anhand der Beispiele Verschlüsselung, Authentifizierung und Autorisierung die Umsetzung von Sicherheitsmechanismen mit MQTT. Außerdem werden beispielhaft Wege aufgezeigt, diese Mechanismen mit Java zu implementieren.

MQTT – Geschichte und eine kurze Einleitung

Das MQTT-Protokoll wurde im Jahr 1999 von Andy Stanford-Clark (IBM) und Arlen Nipper (Arcom, jetzt Cirrus Link) entwickelt, um
die Übertragung von Daten mit möglichst geringen Anforderungen für Batterie- und Bandbreitenbedarf für die Verbindung von Öl-Pipelines über eine Satellitenverbindung zu realisieren. Folgende Merkmale, die das zukünftige Protokoll erfüllen sollte, wurden spezifiziert:

• Einfache Implementierung,
• mehrere Übertragungsgarantien für die Datenübertragung,
• leichtgewichtig und Effizient beim Verbrauch von Bandbreite,
• Datenagnostik,
• Informationen zu Sessions sollen serverseitig gespeichert werden, damit diese nicht beim Wiederaufbau einer Verbindung neu gesendet werden müssen.

Heutzutage stellen diese Merkmale noch immer den Kern von MQTT dar. Der Fokus des Protokolls hat sich inzwischen allerdings von proprietären eingebetteten Systemen hin zu offenen Anwendungsfällen im Internet der Dinge gewandelt.

Nachdem MQTT mehrere Jahre für unterschiedliche Anwendungsfälle intern von IBM genutzt wurde, veröffentlichte IBM das Protokoll im Jahre 2010 in Version 3.1 frei von Lizenzgebühren. Seither ist es jedem möglich das Protokoll kostenlos zu implementieren und zu nutzen. Ein weiterer wichtiger Meilenstein, der es MQTT ermöglichte eine so wichtige Rolle für den Anwendungsbereich Internet der Dinge einzunehmen, war die Gründung des Paho-Projekts unter dem Schirm der Eclipse Foundation.

Unter Paho wurden unterschiedlichste MQTT-Client-Implementierungen veröffentlicht, welche den Ausbau eines Ökosystems für das Protokoll ermöglicht und beschleunigt haben. Im Jahr 2013 wurde bekannt gegeben, dass MQTT unter OASIS standardisiert werden sollte. Ein gutes Jahr später, am 29. Oktober 2014, wurde MQTT-Version 3.1.1 offiziell als OASIS-Standard zugelassen. Der Übergang von 3.1 zu 3.1.1 symbolisiert, dass es sich nur um einen Minor-Change handelt und nur kleine Veränderungen am Protokoll vorgenommen wurden. Das Hauptziel war es, MQTT schnellstmöglich zu standardisieren und von diesem Stand aus stetig zu verbessern.

Grundprinzip von MQTT als Protokoll ist eine Publish-/Subscribe-Architektur. Anders als beispielsweise bei HTTP, mit seiner Request-/Response-Architektur, wird keine Ende-zu-Ende-Verbindung zwischen Sender und Empfänger aufgebaut. Sender und Empfänger von Nachrichten (Client) verbinden sich gleichermaßen mit einem zentralen Server (Broker). Das Senden (Publish) und Empfangen (Subscribe) von Nachrichten erfolgt über sogenannte Topics. Topics sind Strings, die vergleichbar mit URLs aufgebaut sind und den Betreff von Nachrichten darstellen. Beispielhaft könnte ein Temperaturfühler seine gemessenen Werte auf dem Topic temperature senden (publishen). Wichtig ist hierbei, dass es sich bei temperature nicht um die Adresse des Temperaturfühlers, sondern um einen Kommunikationskanal für alle Parteien handelt, die an der Temperatur interessiert sind (Abb. 1).

Publish-/ Subscribe-Architektur von MQTT. (Abb.1)

Dieses Prinzip von Publish und Subscribe ermöglicht eine Push-gesteuerte Kommunikation. Das heißt, interessierte Parteien können vom Broker aktiv über neue Nachrichten informiert werden und müssen diese nicht ständig nachfragen, wie es bei Request-/Response-basierten Protokollen der Fall ist. Die Kombination aus Leichtgewichtigkeit und den daraus resultierenden niedrigen Anforderungen an die Bandbreite, Übertragungsgarantien, welche die Zustellung von Nachrichten auch unter nicht optimalen Voraussetzungen ermöglichen, eine Fülle an Bibliotheken für beinahe alle erdenklichen Systeme und Programmiersprachen sowie die Möglichkeit von Push-Notifikationen, haben unter anderem dazu geführt, dass sich MQTT bis dato als de facto Standardprotokoll im IoT etabliert hat.
Interessierte finden beispielsweise in der Blog-Serie MQTT-Essentials detaillierte Informationen zu sämtlichen Features von MQTT.

Typische Anwendungsbereiche von MQTT umfassen die Vernetzung von Geräten beispielweise für sogenannte Smart-Home-Anwendungen. Auch beim Versenden von Sensordaten aus Regionen, welche schlecht vernetzt oder zugänglich sind, glänzt das Protokoll. Außerdem eignet sich die pushbasierte Kommunikation hervorragend für Chat-Anwendungen, weshalb beispielsweise der Facebook-Messenger in Teilen auf MQTT setzt.

IT-Security-Prinzipien

Sicherheit in der IT ist ein wichtiges Thema. Grundprinzipien wie das CIA-Triad-Principle gelten im Internet-of-Things natürlich ebenso wie in der restlichen IT. (Abb. 2) verdeutlicht, visualisiert durch die verschiedenen Schichten einer Zwiebel, dass Sicherheitsmechanismen auf unterschiedlichen Ebenen angebracht werden müssen, um höchsten Sicherheitsansprüchen gerecht zu werden.

Dieser Artikel wird sich damit beschäftigen, wie Verschlüsselung, Authentifizierung und Autorisierung auf der Ebene der MQTT-Anwendung
realisiert und unter Verwendung von Java auf Client- (Paho Java) oder Brokerseite (HiveMQ-Plugin-System) implementiert werden können. Die Beachtung von Sicherheitsstandards für weitere Teile des Gesamtsystems, durch beispielsweise standesgemäße Absicherung des Netzwerkes mit Firewalls oder auch des Betriebssystems der Host-Maschinen via SSH-Zugänge, werden vorausgesetzt.

Verschlüsselung mit SSL/TLS

Auf der Anwendungsschicht des in (Abb. 2) aufgezeigten Zwiebelmodells eignet sich Transport-Layer-Security (TLS) und sein Vorgänger Secure-Sockets-Layer (SSL). Es handelt sich dabei um kryptografische Protokolle, die eine sichere Kommunikation in einem Computernetzwerk ermöglichen. MQTT baut auf TCP/IP in der Transportschicht auf. Insofern werden keine Informationen unverschlüsselt versendet. SSL/TLS sorgt dafür, dass die Verbindung sicher wird, indem ein verschlüsselter Kommunikationskanal zwischen den Teilnehmern aufgebaut wird. Die Verwendung von SSL/TLS verhindert u.a. sogenannte Man-in-the-middle-Attacken, bei denen sich eine Dritte Partei, mit der Intention unverschlüsselte Daten auszulesen, als Kommunikationspartner ausgibt.

Verschiedene Schichten der Sicherheit. (Abb. 2)

Key- und TrustStores

Java verwendet Key- bzw. TrustStores (JKS) um SSL/TLS-Zertifikate aufzubewahren. Im KeyStore befindet sich das eigene Zertifikat,
welches für den SSL-Handshake angeboten wird, während im TrustStore die Zertifikate abgelegt sind, die als vertrauenswürdig eingestuft werden (vgl. (Abb. 3)). Wird ein Zertifikat vom Kommunikationspartner verwendet, welches nicht von einer vertrauenswürdigen CA ausgestellt wurde, dem aber vertraut werden soll, muss der Public-Key dieses Zertifikates in den TrustStore gelegt werden. (Abb. 3) visualisiert die Aufbewahrungsorte der Server- bzw. Clientzertifikate bei einer sogenannten Mutual-TLS-Verbindung. Also einer Verbindung, bei der sowohl Server- als auch Client-Zertifikate verwendet werden.

Key- und TrustStores. (Abb.3)

Um Zertifikate entsprechend der in (Abb. 3) abgebildeten Struktur aufzubewahren, gibt es unterschiedliche Tools, mit denen sich Key- und TrustStores erstellen bzw. editieren lassen. Beispielsweise das Kommandozeilentool Keytool oder grafische Programme wie der Keystore Explorer.

Implementierung mit Paho

Das folgende Beispiel basiert auf der Annahme, dass auf Seiten des MQTT-Broker ein Serverzertifikat mit dem public key aus broker.jks hinterlegt wurde und dem Zertifikat in client.jks vertraut wird.

Es zeigt den Aufbau einer Mutual-TLS-Verbindung. Für die Mqtt-ConnectOptions wird eine SocketFactory gesetzt, in welche der JavaKeyStore client.jks als KeyStore (Hier liegt das Client-Zertifikat, das vom Client für den TLS-Handshake angeboten wird) und der JavaKeyStore broker.jks als TrustStore (Hier ist der public key des Server-Zertifikats hinterlegt, wodurch der Client weiß, dass er diesem Zertifikat vertrauen kann) geladen werden.

(Listing 1)
public static void main(String… args) {
  try {
      String clientId = „sslTestWithCert“;
      MqttClient client = new MqttClient(„ssl://localhost:8883″, clientId, new MemoryPersistence());
      MqttConnectOptions mqttConnectOptions = new MqttConnectOptions();
  try {
      mqttConnectOptions.setSocketFactory(getTruststoreFactory());
  } catch (Exception e) {
      log.error(„Error while setting up TLS“, e);
  }
  client.connect(mqttConnectOptions);
  client.publish(„test“, „test“.getBytes(), 1, true);
  client.disconnect();
  } catch (MqttException e) {
      log.error(„MQTT Exception:“,e);
  }
}
public static SocketFactory getTruststoreFactory() throws Exception {

  //Create key store

  KeyStore keyStore = KeyStore.getInstance(„JKS“);
  InputStream inKey = new FileInputStream(„client.jks“);
  keyStore.load(inKey, „your-client-key-store-password“.toCharArray());

  KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
  kmf.init(keyStore,“your-client-key-password“.toCharArray());
  //Create trust store
  KeyStore trustStore = KeyStore.getInstance(„JKS“);
  InputStream in = new FileInputStream(„broker.jks“);
  trustStore.load(in, „your-client-trust-store-password“.toCharArray());

  TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.
getDefault
Algorithm());
  tmf.init(trustStore);
  // Build SSL context
  SSLContext sslCtx = SSLContext.getInstance(„TLSv1.2“);
  sslCtx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
  return sslCtx.getSocketFactory();
}

Authentifizierung mit MQTT

Während die Verschlüsselung auf der Transportschicht garantiert, dass die ausgetauschten Informationen nur von den Kommunikationspartnern gelesen werden können, stellt Authentifizierung die Identität dieser Kommunikationspartner auf Applikationsebene
sicher. Nicht nur in der Informationstechnologie sondern auch im echten Leben kennt man dieses Prinzip. Beim Check-In am Flughafen dient ein Reisepass als Authentifizierungsmittel. Am Computer weisen wir mehrmals täglich unsere Identität mit Hilfe einer Username-Passwort-Kombination nach. Das MQTT-Protokoll beinhaltet Felder für Username und Password, um die Identität des Clients am Broker überprüfen zu können. (Listing 2) zeigt wie die Verbindung eines Paho-Java-Clients, der Username und Passwort gesetzt hat, realisiert werden kann.

Listing 2
public void ConnectMQTTClientWithCredentials {
   MqttClient client = new MqttClient(
   „tcp://broker.mqttdashboard.com:1883“, //URI
   MqttClient.generateClientId(), //ClientId
   new MemoryPersistence()); //Persistence

   MqttConnectOptions options = new MqttConnectOptions();
   options.setUserName(„Fu“);
   options.setPassword(„Bar“.toCharArray());
   client.connect(options);
}

Diese vom Client bei der Verbindung mitübertragenen Anmeldedaten können nun vom Broker ausgelesen werden. Beispielsweise kommt der MQTT-Broker HiveMQ mit einem frei zugänglichen, auf Java basierten Plugin-System, mit dem der Nutzer individuell zusätzliche Geschäftslogik implementieren kann. Sogenannte Callbacks werden zu bestimmten Zeitpunkten der MQTT-Kommunikation oder durch bestimmte Ereignisse ausgelöst. Für die Authentifizierung existiert der sogenannte OnAuthenticationCallback. Dieser wird ausgelöst, sobald ein Client einen Verbindungsversuch startet und eignet sich optimal dafür die Identität des Clients zu überprüfen.

(Listing 3) zeigt ein Beispiel bei dem der Broker überprüft, ob die Username-Passwort-Kombination des Clients Fu bzw. Bar entspricht. Nur in diesem Fall ist der Rückgabewert true und der Broker erlaubt die Verbindung. In allen anderen Fällen wird false zurückgegeben und der Broker beendet die Verbindung. Eine solch hart codierte Herangehensweise dürfte allerdings wenig praxistauglich sein. Java bietet Möglichkeiten beispielsweise eine Datei oder Datenbank als Quelle für die Identitätsprüfung zu nutzen.

(Listing 3)
public class UserAuthentication implements OnAuthenticationCallback {

@Override
  public Boolean checkCredentials(ClientCredentialsData clientData)
  throws AuthenticationException {
  if (!clientData.getUsername().isPresent()) {
      return false; // kein Username
   }
  if (!clientData.getPassword().isPresent()) {
      return false; // kein Passwort
   }
  else {
        if (clientData.getUsername().get().equals(„Fu“) && clientData.getPassword().get().equals(„Bar“)) {
            return true; // richtige Kombination
       }
       return false; // falsche Kombination
   }
 }

}

Autorisierung mit MQTT

Authentifizierung und Verschlüsselung stellen sicher, dass Kommunikationspartner auch sind wer sie vorgeben zu sein und dass die Kommunikation nicht von Dritten abgehört werden kann. Autorisierung ist ein weiteres Sicherheitskonzept, das dafür sorgt, dass unterschiedliche Identitäten auch unterschiedliche Berechtigungen haben. Ähnlich wie beim Authentifizierungsbeispiel und dem Reisepass lässt sich auch die Autorisierung auf das echte Leben übertragen. Es zeigt sich, dass diese zusätzlich zur Authentifizierung sinnvoll sein kann. Zugang zu einem gesicherten Bereich wird beispielsweise erst gewährt, nachdem einerseits die Identität nachgewiesen wurde und andererseits sichergestellt wurde, dass diese Identität auch die nötige Berechtigung besitzt diesen Bereich zu betreten.

Für viele MQTT-Anwendungsfälle ist es ratsam, unterschiedlichen Teilnehmern auch unterschiedliche Zugriffe auf bestimmte Ressourcen zu gewähren. Unterschieden werden kann der Zugriff auf drei Ebenen:

• Topic,
• Art des Zugriffs (Publish und/oder Subscribe),
• Quality of Service Level (0,1,2).

Es wird zwischen zwei grundsätzlichen Prinzipien der Zugriffskontrolle unterschieden: Zwischen einem Blacklist- und einem Whitelist-Ansatz. Beim Whitelist-Ansatz ist alles verboten was nicht explizit erlaubt ist (auf der Whitelist steht). Beim Blacklist-Ansatz herrscht das umgekehrte Prinzip und alle Zugriffe, die nicht auf der Blacklist stehen, sind erlaubt. (Listing 4) zeigt einen Whitelist-Ansatz mit hart kodierten Werten für die Autorisierungsprüfung am MQTT-Broker-HiveMQ. Verwendet wird hierzu der OnAuthorizationCallback der Seitens des MQTT-Brokers jedes Mal ausgeführt wird, wenn ein Client auf einem Topic ein Subscribe oder Publish ausführen möchte.

(Listing 4)
public class WhitelistAuthorisation implements OnAuthorizationCallback {

   @Override
   public List<MqttTopicPermission> getPermissionsForClient(ClientData clientData) {

final List<MqttTopicPermission> permissions =
new ArrayList<>();
   permissions.add(new MqttTopicPermission(„client/“ + clientData.getClientId(), TYPE.ALLOW, QOS.ONE, ACTIVITY.SUBSCRIBE));
// Subscribe mit QoS=1 auf topic „client/
clientID“ erlaubt.
   permissions.add(new MqttTopicPermission(„client/#“, TYPE.ALLOW, QOS.ALL, ACTIVITY.PUBLISH));
// Publish mit allen
Quality of Service Leveln auf allen Topcis die mit „client/“ beginnen erlaubt.
     return permissions;
   }
   @Override
   public AuthorizationBehaviour getDefaultBehaviour() {
   return AuthorizationBehaviour.DENY; // Whitelist =
   Autorisierung wird per Default abgelehnt.
   }

}

Hinweis: Da der OnAuthorizationCallback in der Praxis sehr häufig aufgerufen wird ist es sehr empfehlenswert, dessen Ergebnisse zu cachen und so für eine erhöhte Performance am Broker zu sorgen.

Fazit:

Dieser Artikel behandelt nicht das gesamte Spektrum an Sicherheitsmechanismen. Um bestmögliche Sicherheitsstandards zu gewährleisten ist es unumgänglich nicht nur die Anwendungseben, in der sich MQTT wiederfindet, abzusichern. Die Beispiele Verschlüsselung, Authentifizierung und Autorisierung zeigen, dass es möglich ist gängige Sicherheitsmechanismen mit dem de facto Standardprotokoll für IoT-Anwendungen zu integrieren. Die Eclipse-Paho-MQTT-Client-Bibliothek und das Plugin-System des MQTT-Brokers HiveMQ ermöglichen diese Integrationen mit Java. MQTT ermöglicht die Implementierung weiterer Sicherheitsmechanismen. Beispielsweise ist es möglich die Payload der gesendeten Nachrichten gesondert zu verschlüsseln oder das Autorisierungsframework „OAuth2“ zu verwenden . Abschließend kann resümiert werden, dass eine sichere Verwendung des MQTT-Protokolls und Java für Anwendungen im Internet der
Dinge möglich ist.

Florian RaschbichlerFlorian Raschbichler hilft IoT-Interessierten bei Problemlösungen rund um die Themen MQTT und HiveMQ. Als MQTT-Experte schreibt er Artikel und hält regelmäßig Workshops rund um das Protokoll. Er ist Supportverantwortlicher bei der dc-square GmbH, die hochskalierbare IoT-Lösungen wie den MQTT-Broker HiveMQ entwickelt und steht daher in
ständigem Kundenkontakt und kennt die Herausforderungen aus erster Hand.

https://www.dc-square.de
https://www.linkedin.com/in/florian-raschbichler-46310413b
Twitter: @fraschbi

Carolyn Molski


Leave a Reply