User:Sith98/sandbox

In diesem HSP entwickeln wir ein Türschloss, welches mit möglichst günstiger Hardware ein sicheres Öffnen der Wohnungstür ermöglicht. Das Öffnen erfolgt mithilfe von DESfire-Chipkarten sowie von Android-Geräten per NFC. Um verschiedene Verschlüsselungsverfahren auszuprobieren, kommt bei DESfire-Karten ein Challenge-Reponse-Verfahren mittels AES zum Einsatz. Bei Android-Geräten wird dagegen ein Public-Key-Signaturverfahren (ECDSA) verwendet. In der ersten Version wurde auch das Öffnen mit Ultralight-C Karten implementiert. Aus Sicherheitsgründen wurde das aber wieder verworfen (siehe ). Zum Öffnen des Schlosses wird die Karte bzw. das Android-Gerät an ein NFC-Lesegerät (Reader) gehalten, welches mit einem ESP32-Mikrocontroller verbunden ist. Ein Server im selben Wi-Fi-Netz wie der Reader entscheidet, ob das vorgehaltene Gerät zulässig ist, und öffnet in diesem Fall die Tür. Hierbei gewährleisten wir, dass ein sicheres Öffnen möglich ist, selbst wenn ein potentieller Angreifer den Netzwerkverkehr mitlesen und/oder manipulieren kann. Karten/Geräte können über einen dedizierten NFC-Writer registriert werden, welcher ein Webfrontend zu Verfügung stellt und Geräte am Server hinzufügt bzw. wieder löscht. Die Kommunikation zwischen Writer und Server wird dabei gesondert gesichert. Wir verwenden ein NFC-Lesegerät, welches von Haus aus nur die Kommunikation mit (älteren) Ultralight-C-Karten unterstützt. Um die Kommunikation mit DESfire-Karten zu ermöglichen, müssen wir Modifikationen an der Hardware vornehmen sowie das DESfire-Kommunkationsprotokoll zum Teil selbst implementieren. Dafür erweitern wir die Library MFRC522 von GitHub-Nutzer miguelbalboa.

Einführung und Architektur
Kommerziell erhältliche Türschlösser mit NFC-Authentifizierung sind teuer. In diesem HSP entwickeln wir ein System basierend auf möglichst günstiger Hardware, ohne dass in Sachen Sicherheit Einbußen gemacht werden müssen. Konkret soll der Nutzer mit einem Android-Gerät und einer entsprechenden App oder mit einer DESFire-NFC-Karte das Türschloss sicher öffnen können. Android-Geräte und NFC-Karten werden im Folgenden als Geräte bezeichnet.

Das entwickelte System besteht aus vier Komponenten: Zusätzlich zu diesen vier Komponenten wird ein Wi-Fi-Netzwerk benötigt, über das Reader und Writer mit dem Server kommunizieren können. Außerdem wird für Authentifizieren von Android-Geräte eine von uns entwickelte App auf dem Gerät benötigt.
 * Der Server ist ein Raspberry Pi, welcher einen TCP-Server anbietet und Geräte-Schlüssel in einer Datenbank speichert. Bei ihm müssen sich Geräte zum Öffnen der Tür authentifizieren und er ist es auch, der das Schloss letztendlich öffnet.
 * Der Reader ist außen am Türschloss angebracht und dient als von dort zugängliche Schnittstelle, über die Geräte durch Vorhalten das Schloss öffnen können. Hardwareseitig besteht er aus einem ESP32-Mikrocontroller sowie einem RC522-RFID-Modul.
 * Mit dem Writer kann der Nutzer seine registrierten Geräte verwalten. Er stellt einen Web-Server zur Verfügung, über den Nutzer registrierte Geräte hinzufügen, anzeigen und löschen können. Zum Registrieren eines Gerätes muss dieses per NFC mit dem Writer kommunizieren. Daher besteht der Writer genau wie der Reader neben einem ESP32-Mikrocontroller auch aus einem RC522-RFID-Modul.
 * Das Schloss muss über ein Signal auf den GPIO-Pins des Servers geöffnet werden können. Hierbei gibt es zwei Möglichkeiten. Bei der ersten muss das Schloss selbst das Steuern mit einem Signal unterstützen (wie beim gebauten Demonstrator (siehe ). Bei der zweiten wird der bei Haustüren meist existierende Türöffner genutzt. Der Vorteil dieser Variante ist, dass der normale Haustürschlüssel weiter normal genutzt werden kann und keine weiteren Modifikationen nötig sind.

Reader/Writer
Im Github-Repository befinden sich zwei Ordner Reader und Writer, die zum Deployment der Anwendung auf einem ESP32-Mikrocontroller verwendet werden können. In den beiden Ordnern befindet sich jeweils unter code/config.json eine Konfigurationsdatei. Als ersten Schritt des Deployments müssen die Zugangsdaten des verwendeten Wifi-Netzwerks in diese Dateien eingetragen werden. Optional kann man außerdem IP-Adresse und Port des Servers (Raspi) eintragen. Falls man dies nicht tut, versucht der Reader/Writer, den Server automatisch via mDNS zu finden. In der Konfigurationsdatei des Writers muss ein Shared Key in Form eines 16-Byte-langen Hex-Strings (z.B.  ) hinterlegt werden, den der Writer nutzt um verschlüsselt, mit dem Server zu kommunizieren (derselbe Schlüssel muss in der Konfigurationsdatei des Servers angegeben werden).

Zum eigentlichen Deployment wird ein Windows-PC benötigt, an den der ESP32 per USB angeschlossen wird. Um den ESP32 zu "flashen", muss ein PowerShell-Terminal im entsprechenden Ordner (Reader/Writer) gestartet werden und von dort das Skript  ausgeführt werden.

Server
Zum Deployen des Servers auf dem Raspi ist ein User mit Sudo-Rechten erforderlich. Die folgenden Schritte müssen nacheinander ausgeführt werden:


 * 1) Lege neuen User an:
 * 2) Lege sicheres Passwort fest:
 * 3) Gib dem User GPIO-Berechtigungen:
 * 4) Installiere Abhängigkeiten:
 * 5) Wechsel zum neuen User:
 * 6) Klone das Server-Git-Repo:
 * 7) Erstelle neue Python-Umgebung:
 * 8) Installiere Abhängigkeiten für die Umgebung:
 * 9) Wechsel zurück zum User mit Sudo-Rechten:
 * 10) Bearbeite Konfigurationsdatei: Lege insbesondere Wifi-Zugangsdaten und Shared Key fest
 * 11) Lege Service an:
 * 12) Starte Service:
 * 13) Registriere Service:

Alternativ kann das Skript  im Server-Repo ausgeführt werden. Dabei muss aber nach Ausführung des Skripts die Konfigurationsdatei angepasst und der Service neu gestartet werden.

Reader/Writer
Zur Entwicklung und zum Deployment auf dem ESP32-Mikrocontroller wird die Arduino-IDE inklusive der entsprechenden ESP32-Plugins benötigt. Dazu findet man zahlreiche Tutorials online. Außerdem verwenden wir den ESP32 Filesystem Uploader zum Hochladen der Website auf den Writer (Tutorial).

Der Reader/Writer-Code befindet sich in diesem Github-Repo.

Folgende Packages müssen installiert werden:
 * ArduinoJson (6.19.4)
 * CRC32 (2.9.0)
 * LittleFS_esp32 (1.0.6)
 * MFRC522 (von uns gepatchte Version)
 * ESPAsyncWebserver (Github-Repo)
 * WifiManager (2.0.5)

Server
Der Server ist ein Python-Projekt. Zum Deployen auf dem Raspberry Pi existiert ein Deploy-Skript, mit dem der Server in 5 Minuten auf einem Raspi aufgesetzt werden kann. Dieses Skript legt einen neuen Linux-User an. Dieser User erhält als einziger (außer root) Zugriff auf die Schlüssel-Datenbank. Außerdem weist das Skript ihm die nötigen Berechtigungen zu (z.B. GPIO-Zugriff) und installiert alle notwenigen Abhängigkeiten.

Website
Der Website-Code befindet sich in diesem Github-Repo.

Ist der ESP32 Filesystem Uploader installiert, kann in der Arduino IDE unter Sketch > Show Sketch Folder der data-Ordner des Repositories abgelegt werden.

Anschließend muss der Sketch-Folder über Tools > ESP32 Data Sketch Upload hochgeladen werden.

Grundlegende Annahmen und Vorgaben
Ein potentieller Angreifer hat selbstverständlich Zugriff auf den Reader, da dieser am Türschloss angebracht werden muss. Außerdem gehen wir davon aus, dass der Angreifer ebenfalls Zugriff auf das Netzwerk hat, über das die drei Komponenten kommunizieren. Dagegen nehmen wir an, dass Writer und Server physisch durch das Türschloss gesichert sind und ein Angreifer somit keinen Zugriff auf sie hat. Sollte er doch Zugriff auf sie erhalten, ist die Sicherung des Türschlosses hinfällig. Wir müssen also zwei Dinge sicherstellen: Erstens darf kein Angreifer die Tür öffnen können, ohne Zugriff auf auf ein registriertes Gerät zu haben. Und zweitens darf kein Angreifer ein Gerät registrieren können, ohne Zugriff auf den Server und/oder Writer zu haben. Zusätzlich fordern wir, dass kein Angreifer ohne Zugriff auf den Server und/oder Writer ein Gerät aus der Liste der registrierten Geräte entfernen darf. In diesem Kapitel beschreiben wir, wie diese drei Aspekte erreicht werden.

Authentifizierung
Wir nehmen an, dass ein Gerät bereits registriert wurde und der entsprechende Schlüssel auf dem Server vorliegt. Um die Tür zu öffnen, muss sich dieses Gerät nun am Reader authentifizieren. Die Authentifizierung läuft dabei über den Server. Der Reader leitet nur die Nachrichten zur Authentifizierung zwischen Gerät und Server weiter. Die Art der Authentifizierung unterscheidet sich, je nach dem, ob es sich um eine Desfire-Karte oder ein Android-Gerät handelt.

DESFire-Karten
MIFARE-DESFire-Karten beherrschen nativ die Verschlüsselung mittels DES (in den Varianten 1DES, 2DES, 3DES) und AES-128. Die Karten unterstützen zur Authentifizierung ein dreistufiges Challenge-Response-Verfahren, bei dem sich Karte und Kartenleser bzw. Server gegenseitig vom Besitz desselben (symmetrischen) Schlüssels überzeugen können, ohne dass ein potentieller Angreifer Zugriff auf diesen erlangen kann. Das Verfahren wird sowohl mit DES als auch mit AES angeboten. Wir nutzen die AES-Variante, um DESFire-Karten zu authentifizieren. Das Verfahren läuft mit AES wie folgt ab:

Karte und Server verwenden den gleichen Schlüssel. Die Ver- und Entschlüsselung erfolgt im CBC-Modus. Der Initialization Vector (IV) ist sowohl karten- als auch serverseitig zu Beginn null. Der Wert des IV nach jeder Ver/-Entschlüsselung wird als neuer Start-IV für die nächste Ver-/Entschlüsselung (karten- bzw. serverseitig) verwendet. Das Verfahren verläuft bei der Verwendung von DES analog. Allerdings sind  und   nur 8 Byte lang, wenn 1DES oder 2DES verwendet werden. Bei 3DES sind es wie bei AES 16 Byte.
 * 1) Die Karte generiert einen zufälligen, 16-Byte langen Byte-String , verschlüsselt diesen mit dem gemeinsamen Schlüssel und schickt den verschlüsselten String   an den Server.
 * 2) Der Server entschlüsselt   zu  . Er bestimmt   (Rotation um ein Byte nach links). Anschließend generiert er den zufälligen, 16-Byte langen Byte-String   und schickt   an die Karte.
 * 3) Die Karte entschlüsselt   zu   und  . Sie rotiert   ein Byte nach rechts und vergleicht es mit  . Bei Nicht-Übereinstimmung ist die Authentifizierung fehlgeschlagen. Bei Übereinstimmung rotiert sie   ein Byte nach links (→ ) und schickt   an den Server.
 * 4) Der Server entschlüsselt   zu  . Er rotiert   ein Byte nach rechts und vergleicht es mit  . Bei Übereinstimmung war die Authentifizierung erfolgreich.

Unter Annahme, dass AES sicher ist, gibt es keinen erkennbaren Zusammenhang zwischen  und. Daher kann die Karte  nur bestimmen, wenn sie   tatsächlich entschlüsseln konnte, und muss somit im Besitz des Schlüssels sein. Das gleiche gilt analog auch für den Server und. Auf diese Weise können sich Karte und Server gegenseitig von dem Besitz des gemeinsamen Schlüssels überzeugen. Ein Angreifer kann jedoch die Nachrichten nicht entschlüsseln und somit  und   nicht ermitteln, vorausgesetzt, dass   und   mithilfe eines guten Zufallszahlengenerators erzeugt wurden. Außerdem werden durch die bei jeder Authentifikation neu gewählten Zufallszahlen Replay-Angriffe verhindert.

Android-Geräte
Selbstverständlich wäre es möglich gewesen, den obigen Challenge-Response-Ansatz auch für Android-Geräte zu verwenden. Allerdings ist eines unserer Ziele in diesem HSP, praktische Erfahrungen mit verschiedenen kryptographischen Verfahren zu machen. Daher implementieren wir stattdessen einen anderen Ansatz, basierend auf einem Public-Key-Signaturverfahren, bei dem das Android-Gerät bei der Registrierung ein Schlüsselpaar generiert und dem Server nur den öffentlichen Schlüssel bekanntmacht. Konkret verwenden wir das ECDSA-Verfahren (Eliptic Curve Digital Signature Algorithm) mit der Kurve P-256. Diese erreicht mit 128 Bit die gleiche Sicherheit wie das AES-Verfahren für DESFire-Karten.

Im Vergleich zu Verfahren wie RSA oder DSA, die das Faktorisierungsproblem bzw. den diskreten Logarithmus verwenden, ist der Vorteil von Signaturverfahren, die auf elliptischen Kurven basieren, dass deutlich kürzere Schlüssel verwendet werden können, um die gleiche Sicherheit zu gewährleisten. Dies liegt daran, dass für RSA und DSA effizientere Algorithmen zum Brechen der Schlüssel bekannt sind als bei elliptischen Kurven. Für unser Projekt bedeutet das, dass deutlicher weniger Daten über das relativ unzuverlässige NFC-Übertragungsprotokoll gesendet werden müssen.

Wir gehen davon aus, dass das Android-Gerät bereits registriert wurde und der öffentliche Schlüssel auf dem Server hinterlegt ist. Das Authentifizierungsverfahren läuft dann wie folgt ab:
 * 1) Das Android-Gerät sendet eine Authentifizierungsanfrage an den Server.
 * 2) Der Server generiert eine zufällige, 16-Byte-lange Nonce und schickt diese an das Gerät.
 * 3) Das Gerät hasht die Nonce mittels SHA-256 und signiert den Hash mit ECDSA und seinem privaten Schlüssel. Die resultierende Signatur schickt er an den Server.
 * 4) Der Server berechnet ebenfalls den Hash der Nonce und verifiziert die Signatur mit dem zugehörigen, öffentlichen Schlüssel. Falls die Signatur valide ist, ist die Authentifizierung erfolgreich.

Public-Key-Signaturverfahren basieren auf der Annahme, dass nur mithilfe des privaten Schlüssels effizient eine gültige Signatur erstellt werden kann und dass es nicht effizient möglich ist, aus dem öffentlichen Schlüssel oder einer Signatur den privaten Schlüssel zu ermitteln. Wenn wir voraussetzen, dass ECDSA mit P-256 sicher ist und dass die Nonce mithilfe eines guten Zufallszahlengenerators erzeugt wird, kann also nur das Android-Gerät eine gültige Signatur generieren, da es als einziges über den privaten Schlüssel verfügt. Ein Angreifer, der die Nonce kennt, kann also keine gültige Signatur anfertigen und sich somit authentifizieren. Dass die Nonce jedes Mal zufällig vom Server bestimmt wird, verhindert zudem Replay-Attacken.

Dass die Nonce gehasht wird, hat vor allem Implementierungsgründe. Da die Nonce bereits zufällig ist, hat das Verwenden von SHA256 nach unserem Wissen keine sicherheitsrelevanten Vorteile, wenn man von dem Strecken der 16-Byte-langen Nonce auf 32 Byte absieht. Wenn das genutzte Android-Gerät ein sogenanntes Trusted Plattform Modul besitzt, wird dieses zur Verwaltung der Schlüssel genutzt, da dies den Zugriff auf den privaten Schlüssel verhindert.

Ultralight-C-Karten
Ultralight-C Karten unterstützen das gleiche Challenge-Response-Protokoll wie DESfire Karten, allerdings nur mit dem veralteten 3DES Verschlüsselungsalgorithmus. Dieser gilt zwar grundsätzlich immernoch als sicher, aber die effektive Schlüssellänge kann durch einen Meet-in-the-middle-Angriff von 168 auf 112 eingeschränkt werden. Das BSI empfiehlt diesen Algorithmus daher in moderneren Systemen nicht mehr. Da auch der Preisunterschied zu DESfire-Karten relativ gering ist, haben wir uns aus Sicherheitsgründen entschieden, in späteren Versionen keine Ultralight-C-Karten zu unterstützen.

Registrierung/Löschen von Geräten
Bevor sich ein Gerät per NFC über den Reader authentifizieren kann, muss es sich zunächst am Writer registrieren. Der Writer leitet den Schlüssel an den Server weiter. Hierbei sind die folgenden Schutzziele zu beachten:
 * 1) Integrität/Authenzität: Kein Angreifer soll ohne Zugriff auf Writer und/oder Server in der Lage sein, ein Gerät zu registieren oder zu löschen.
 * 2) Vertraulichkeit: Kein Angreifer soll in der Lage sein, aus den bei der Registrierung übertragenen Daten zwischen Writer und Server den (privaten) Schlüssel des registrierten Geräts zu extrahieren.

Da für DESFire-Karten symmetrische Schlüssel, für Android-Geräte aber öffentliche Schlüssel verwendet werden, unterscheidet sich der Ablauf, wie Geräte hinzugefügt werden, zwischen DESFire-Karten und Android-Geräten. Im folgenden beschreiben wir, wie die Schutzziele Integrität, Authenzität und Vertraulichkeit jeweils umgesetzt sind.

Integrität/Authenzität
Die Integrität/Authentizität von Nachrichten vom Writer an den Server, die dafür sorgen, dass ein neues Gerät angelegt oder ein bestehendes gelöscht wird, muss sicher gestellt werden. Dies hat drei Gründe:
 * Die Registrierungsnachricht bei Android-Geräten enthält den öffentlichen Schlüssel des Geräts. Würde nicht sichergestellt werden, dass diese Nachricht vom Writer kommt, könnte ein Angreifer ohne Zugriff auf den Writer beliebige öffentliche Schlüssel registrien.
 * Ein Angreifer könnte so beliebige Geräte entfernen. Dies würde dazu führen, dass das Türschloss nicht mehr mit registrierten Geräten geöffnet werden kann
 * Ein Angreifer könnte beliebige Desfire-Karten registrieren. Der Angreifer würde war zwar keinen Zugriff auf den Kartenschlüssel erhalten, da dieser vom Server generiert wird und der Schlüssel vertraulich an den Writer geschickt wird (siehe ), jedoch könnte er beispielsweise tausende falsche Karten registrieren und so den Speicher des Servers füllen.

Bei der Einrichtung des Systems wird auf Writer und Server derselbe (symmetrische) Schlüssel hinterlegt: Shared-Key. Dieser Shared-Key dient als Basis für ein Protokoll, mit dem die Integrität und Authenzität von Nachrichten sichergestellt wird. Im Folgenden wird examplarisch der Ablauf dieses Protokolls für eine Nachricht vom Writer an den Server dargestellt: Für die HMAC wird der zwischen Server und Writer geteilte Schlüssel verwendet. Als Hashfunktion wird SHA-256 verwendet.
 * 1) Der Server sendet eine 256-Bit-lange   an den Writer.
 * 2) Der Writer sendet   an den Server.   enthält je nach Befehl z.B. die Geräte-ID, den Schlüssel etc.
 * 3) Der Server überprüft die HMAC. Bei korrekter HMAC fährt er in der normalen Ausführung der aktuellen Aktion fort. Bei fehlerhafter HMAC bricht er die Ausführung der aktuellen Aktion ab.

Unter der Annahme, dass der Sahred-Key auf sichere Art und Weise auf Writer und Server hinterlegt wurde und niemand sonst Zugriff auf ihn hat, kann niemand außer dem Writer die korrekte HMAC generieren und sich als dieser ausgeben. Durch die zufällig generierte Nonce, die Teil der HMAC ist, werden Replay-Attacken verhindert.

Vertraulichkeit
Da für Android-Geräte ein Public-Key-Verfahren verwendet wird, muss der öffentliche Schlüssel, den der Writer an den Server schickt, nicht geheim gehalten werden und wird daher im Klartext gesendet. Bei DESFire-Karten funktioniert dies nicht, da der Schlüssel vom Server generiert und an den Writer verschickt wird. Nachrichten vom Server an den Writer, die vertrauliche Schlüsselinformationen erhalten, werden daher mittels des Shared-Keys verschlüsselt. Dabei wird AES-CBC mit IV Null verwendet. Die Wahl fiel auf den Betriebsmodus "CBC", um die zu Verfügung stehenden Modi der Krypto-Bibliothek des ESP32-Microcontrollers zu berücksichtigen. Da sämtliche vom Server an den Writer verschickten Schlüssel zufällig generiert und nie mehr als einmal gesendet werden, stellt das mehrfache Verwenden desselben IV kein Sicherheitsproblem dar.

Öffnen mehrerer Schlösser mit dem gleichen Gerät
Ein wesentliches Konzept bei Desfire-Karten sind die sogenannten Applications. Abgesehen vom Master-Key der Karte werden sämtliche Schlüssel oder verschlüsselte Dateien innerhalb von Applications verwaltet. Jede Application wird über eine ID (0x000001 bis 0xffffff) identfiziert. Da mehrere Applications auf einer Karte angelegt werden können, wäre es in der Theorie problemlos möglich dieselbe Karte für mehrere Anwendungszwecke (Uni, Arbeit, Schwimmbad, Bibliothek etc.) zu verwenden. In der Praxis sind jedoch die meisten Karten so konfiguriert, dass ohne den Master-Key keine Applications angelegt werden können. Hierfür gibt es aus Security-Sicht jedoch keinen Grund, solange das Löschen und Modifizieren von bestehenden Anwendungen nur mithilfe des Master-Keys möglich ist. Wir erlauben daher bei unseren Karten das Anlegen von mehreren Applications, selbst wenn man keinen Zugriff auf den Master-Key hat.

Bei der Registrierung einer Karte am Writer wird zunächst überprüft, ob es sich um eine neue Karte handelt. Falls ja, wird der Master-Key der Karte auf einen zufällig generierten Schlüssel geändert und die Karte so konfiguriert, dass ohne Kenntnis des Master-Keys zwar Applications aneglegt, aber keine bestehenden modifziert oder entfert werden können. Anschließend wird eine neue Anwendung angelegt, in der der Schlüssel, der später zur Öffnung des Türschlosses verwendet werden kann, abgelegt wird. Wird dagegen eine bereits verwendete Karte registriert, auf der bereits ein oder mehrere Applications angelegt wurden, wird einfach eine neue Application zum Öffnen des Türschlosses angelegt. Auf diese Weise ist es möglich, dieselbe Karte bei verschiedenen Writern zu registrieren und so verschiedene Schlösser mit derselben Karte zu öffnen. Der Server speichert demnach für jede Karte die entsprechende Anwendungs-ID und den Schlüssel. Der Master-Key der Karte wird dagegen nur in dem Server gespeichert, bei dem die Karte zum ersten Mal registriert wurde.

Während bei Desfire-Karten aufgrund des symmetrischen Authentifizierungsverfahrens für jedes Türschloss ein anderer Schlüssel gespeichert werden muss, wird bei Android-Geräten ein Public-Key-Verfahren verwendet. Das bedeutet, dass der gleiche öffentliche Schlüssel problemlos bei mehreren Schlössern hinterlegt werden kann. Somit erstellen Android-Geräte nur einmal einen Schlüssel, der bei jeder Registrierung verwendet wird.

NFC-Kartenleser
Reader als auch Writer verwenden die gleichen Hardwarekomponenten: einen ESP32-Mikrocontroller sowie einen MFRC522-Kartenleser. Anders als teurere Kartenleser (wie etwa der PN532) ist der MFRC522 nativ nicht zum Lesen von Ultralight-C- oder DESfire-Karten geeignet. Da bei diesem Projekt vor allem der Kostenpunkt im Vordergrund stand, fiel die Wahl trotzdem auf den MFRC522, welcher bereits für 1,10€ pro Stück erhältlich ist. Das Magnetfeld, das der MFRC522 induziert, ist zu schwach, um Ultralight-C- oder DESfire-Karten betreiben zu. Daher müssen zunächst Hardware-Modifikation am Kartenleser vorgenommen werden. Hierzu gibt es einen ausführlichen Blogartikel, an dem wir uns orientiert haben. Zuerst müssen die Induktionsspulen L1 und L2 durch Spulen mit einer Induktivität von 2.2mH ersetzt werden. Zusätzlich müssen die beiden Kondensatoren C4 und C5 abgelötet werden. Stattdessen wurden Keramik-Kondensatoren mit einer Kapazität von 33pF angelötet. Während der PN532 das für DESfire-Karten nötige ISO/IEC 14443-4 Kommunikationsprotokoll bereits auf Hardwareebene unterstützt, muss dieses für den MFRC522 auf Softwarelevel implementiert werden. Siehe hierzu den entsprechenden Abschnitt im Implementierungsteil dieses Artikels.

Demonstrator
In der OTH ist es leider nicht möglich das Projekt an einer echten Tür vorzuführen. Deshalb haben wir einen kleinen Demonstrator aus Holz gebaut, bei dem man sich die Abläufe anschauen kann. Dieser verwendet ein Schloss (Preis: 14€), welches durch ein elektronisches Signal geöffnet werden kann, sowie ein Relais (Preis: 6€). Das eingesetzte Schloss wird den üblichen Sicherheitsstandards eines Haustürschlosses natürlich nicht gerecht. Da ein Türschloss in der Praxis aber typischerweise sowieso schon vorhanden ist, kann dieser zusätzliche Kostenpunkt vernachlässigt werden.

Weitere Hardware
Für den Server wird ein Raspberry Pi verwendet, dessen Kosten sich auf ca. 45€ belaufen.

ISO-14443-4 Protokoll
Zur Kommunikation zwischen dem ESP32-Mikrocontroller und dem MFRC522-Kartenleser verwenden wir die MFRC522-Library. Über diese Library lässt sich die Kommunikation mit Mifare-Classic- und Ultralight-C-Karten problemlos umsetzen. Desfire-Karten und Android-Geräte verwenden für die Kommunikation jedoch eine zusätzliche Transportprotokoll (ISO/IEC 14443-4), vergleichbar mit TCP im IP-Stack. Dieses Protokoll ist in Teilen und in experimenteller Form in der MFRC522-Library implmentiert, jedoch sind manche Funktionen fehlerhaft oder unvollständig umgesetzt. Die Library wartet beispielsweise bei jeder Nachricht von Kartenleser zu Gerät immer dieselbe, willkürlich gewählte, Zeit (36ms) auf eine Antwort und bricht die Kommunikation ab, wenn sie in dieser Zeit keine Antwort erhält. ISO/IEC 14443-4 definiert dagegen verschiedene Mechanismen, mit denen die Zeit bestimmt werden kann, wie lange der Kartenleser auf eine Antwort des Geräts warten soll. Dadurch kommt es sowohl bei Android-Geräten als auch bei manchen, aufwändigeren Operationen auf den Desfire-Karten zu Timeouts.

Das Protokoll sieht standardmäßig eine Wartedauer von 4,8ms vor. Beim ersten Handshake können Leser und Gerät jedoch eine andere Wartezeit zwischen ca. 0,3ms und 5s aushandeln (Punkt 7.3 im Protokoll). Falls das Gerät eine Anfrage trotzdem nicht schnell genug beantworten kann, kann es um eine Verlängerung bitten. Hierzu wird eine sogenannte "Frame waiting time extension (WTX)"-Nachricht gesendet (Punkt 7.4 im Protokoll). Diese Nachricht legt einen Wartezeit-Faktor fest und wird vom Kartenleser mit einer Antwort-Nachricht bestätigt. Bis zur nächsten Nachricht ist die Wartezeit entsprechend dieses Faktors größer. Um eine stabile Kommunikation mit Desfire-Karten und Android-Geräten zu ermöglichen, waren daher eine Analyse des bestehenden Library-Codes sowie mehrere Modifikation an der Nachrichten-Logik der Library nötig.

Desfire EV1 Protokoll
Während das oben erwähnte Transportprotokoll von Desfire-Karten für Studenten kostenlos einsehbar ist, sind sämtliche Informationen zur Anwendungsebene (Anlegen von Schlüsseln, Authentifzierung etc.) nur nach Unterschreiben einer NDA erhältlich. Dies ist allerdings nur für Unternehmen möglich. Dadurch war das Verstehen und Implementierung der Anwendungskommunikation für Desfire-Karten eine der Hauptschwierigkeiten dieses Projekts. Bei der Implementierung konnten wir uns die Tatsache zunutze machen, dass manche grundlegende Funktionen ähnlich zu den entsprechenden Funktionen von Ultralight-C-Karten umgesetzt sind. Allerdings haben einige Funktionen von Desfire-Karten keine Entsprechung: So sind etwa einige Desfire-Funktionen mit einer zusätlichen kryptographischen Schicht gesichert. Eine wertvolle Quelle waren daher eine Reihe von Kommunikationsmitschnitten, die der Ersteller dieses Blogs angefertigt hat.

Wifi-Manager
Server und Reader/Writer müssen sich im gleichen Wifi-Netzwerk befinden. Die Zugangsdaten des Netzwerk können über die Konfigurationsdatei angegeben werden. Wenn dies nicht der Fall ist, stellt der ESP32 ein eigenes WLAN-Netz zur Verfügung. Meldet man sich mit einem anderen Gerät bei diesem an, wird man automatisch auf ein Captive Portal umgeleitet, in dem man über eine grafische Oberfläche das gewünschte Netzwerk aus einer Liste von erreichbaren Netzwerken auswählen und die nötigen Zugangsdaten eingeben kann. Dieser Vorgang muss nur einmal erfolgen. Der ESP32 speichert alle Zugangsdaten.

mDNS
Damit der Reader/Writer mit der Server kommunizieren kann, muss dieser dessen IP-Adresse kennen. Die IP-Adresse kann in der Konfigurationsdatei spezifiziert werden. Alternativ findet der Reader/Writer den Server automatisch mittels Multicast-DNS (mDNS). Hierbei werden DNS-Anfragen nicht an einen dedizierten DNS-Server geschickt, sondern über eine Broadcast-Nachricht an alle Geräte im Netzwerk. In einer ersten Version haben wir statt mDNS das Service-Discovery-Protokoll SSDP verwendet. Allerdings stellte sich mDNS als zuverlässiger heraus. In der aktuellen Version sind sowohl Server als auch Writer via mDNS findbar. So können wir die Website des Writers im Browser direkt über  aufrufen.

Webserver
Geräte können am Writer über eine Webseite verwaltet werden. Diese Webseite wird direkt vom Writer aus zur Verfügung gestellt. Dafür verwenden die ESPAsyncWebServer-Library. Dadurch dass dieser Webserver asynchron arbeitet, können Dateien ausgeliefert werden, ohne dass der übrige Code blockiert. Während dies bei "normalen" Webservern Standard ist, ist das bei Mikrocontrollern nicht selbstverständlich.

Krytographische Primitive
Der Writer verwendet zum Registrieren von Geräten AES sowie HMAC-SHA256 (siehe ). Dafür verwenden wir die Bibliothek Mbed-TLS. Diese implementiert die benötigten krypographischen Primitive effizient und auf sichere Art und Weise, indem sie die Hardware-Kryptofunktionalitäten des ESP32 nutzt.

Konfigurationsdatei
Wir verwenden zur Konfiguration eine Datei im JSON-Format. Dieselbe Datei muss bei der Einrichtung des Systems auf Writer und Server kopiert werden. Hier eine Beispiel-Datei: Zum Parsen der Datei verwenden wir die ArduinoJson-Bibliothek.

Server
Der Server ist als Python-Anwendung implementiert. Für die verwendeten kryptographischen Verfahren (AES, (3-)DES, SHA-256, HMAC, ECDSA, CSPRNG) benutzen die Library. Die Schlüssel werden in einer SQLite-Datenbank verwaltet. Diese Datenbank ist zwar nicht verschlüsselt, aber betriebssystemseitig über das Linux-Dateisystem gesichert. Wir gehen davon aus, dass ein Angreifer keinen Root-Zugriff auf dem Raspi hat (in diesem Fall könnte er ohnehin das Schloss öffnen). Dadurch ist diese Sicherung der Schlüsseldatenbank unserer Meinung nach ausreichend.

Wie oben erwähnt, wird die IP des Servers mittels mDNS bekanntgegeben. Beim erfolgreicher Authentfizierung am Reader öffnet der Server das Türschloss mittels GPIO.

Ändern von Desfire-Schlüsseln
Um die Schlüssel von Desfire-Karten zu ändern, wird der neue Schlüssel nicht im Klartext an die Karte übermittelt, sondern mithilfe eines sogenannten Session-Keys verschlüsselt. Dieser Session-Key ergibt sich aus den / -Bytestrings der letzten Authentifzierung. Die Sessions-Keys werden serverseitig in einem  verwaltet, welches automatisch Schlüssel nach 5 Minuten entfernt. Wenn der Schlüssel einer Karte geändert wird, sendet der Server die entsprechende Nachricht, welche den neuen Schlüssel verschlüsselt mit dem Session-Key enthält, an den Writer. Wie oben beschrieben, ist diese Nachricht ein weiteres Mal mit dem Shared-Key verschlüsselt. Der Writer empfängt die Nachricht, entfernt die erste Verschlüsselungsebene und leitet die resultierende Nachricht an die Karte weiter. Die Verschlüsselung mit dem Session-Key ist notwenig, da das Kartenprotokoll dies so vorsieht. Dadurch, dass wir sie serverseitig durchführen, müssen wir das Session-Key-Management nicht auf dem Writer implementieren. Die Verschlüsselung mit dem Shared-Key ist nötig, da ein Angreifer, der die Kommunikation mitlesen kann und der den bisherigen Key kennt, sonst den neuen Key ermittlen. Dies gilt insbesondere für neue Karten, bei denen der initiale Schlüssel bekannt ist.

API
Die Kommunikation zwischen Reader/Writer und Server erfolgt über TCP. Jeder Befehl wird über einen 2-Byte-langen Code identifiziert. Der Server unterstützt die folgende Befehle. Dort wo WithHMAC steht, wird das in beschriebene Protokoll verwendet.


 * Authenticate
 * → AA KeyType(1) UID(7) AppId(3)
 * → EkRndB(8 | 16)
 * ← EkRndARndBPrime(16 | 32)
 * → EkRndAPrime(8 | 16)
 * wenn erfolgreich:
 * ← 00
 * sonst:
 * ← AE
 * Open Door
 * → 0D KeyType(1) UID(7) AppId(3)
 * → EkRndB(8 | 16)
 * ← EkRndARndBPrime(16 | 32)
 * → EkRndAPrime(8 | 16)
 * wenn erfolgreich:
 * ← 00
 * sonst:
 * ← AE
 * Get App Id
 * → 6A UID(7)
 * wenn App exisitert:
 * ← AppId(3)
 * sonst:
 * ← Zero(3)
 * Verify Android
 * → 4A UID(16)
 * ← DataToSign(16)
 * → Length(1) SignedData(Length)
 * wenn erfolgreich:
 * ← 00
 * sonst:
 * ← AE
 * Save Public Key
 * → 56 WithHMAC(UID(16) NameLength(1) Name(NameLength) KeyLength(1), PublicKey(KeyLength))
 * wenn erfolgreich:
 * ← 00
 * sonst:
 * ← 5E
 * Get All Devices
 * → 6D
 * ← Length(4) Json({"desfire": [[uid, name], ...], "android": [[uid, name], ...]}")
 * Delete Key
 * → DD WithHMAC(UID(16))
 * wenn erfolgreich:
 * ← 00
 * sonst:
 * ← 5E
 * Is Key Known
 * → 66 UID(7)
 * wenn Schlüssel bekannt:
 * ← 00
 * sonst:
 * ← 01
 * Is Android Device Known
 * → A6 UID(16)
 * wenn Schlüssel bekannt:
 * ← 00
 * sonst:
 * ← 01
 * ChangeKey
 * → C4 WithHMAC(KeyType(1) UID(7) AppId(3) NameLength(1) Name(NameLength))
 * ← ChangeCmdLength(1) MsgLength(1) Msg(MsgLength) [Msg = Cmd(1) || KeyNr(1) || Enc(ChangeCmd)]
 * → WithHMAC(StatusCode(1))
 * wenn erfolgreich:
 * ← 00
 * sonst:
 * ← 5E
 * ChangeKey
 * → C4 WithHMAC(KeyType(1) UID(7) AppId(3) NameLength(1) Name(NameLength))
 * ← ChangeCmdLength(1) MsgLength(1) Msg(MsgLength) [Msg = Cmd(1) || KeyNr(1) || Enc(ChangeCmd)]
 * → WithHMAC(StatusCode(1))
 * wenn erfolgreich:
 * ← 00
 * sonst:
 * ← 5E
 * ← 5E

Web-Client
Über den Web-Client lassen sich neue Karten und Geräte registrieren und bisher hinterlegte Karten oder Geräte verwalten. Auf der Startseite kann man sich entweder links zur Registrierung neuer Schlüssel weiterleiten lassen oder rechts zur Übersicht und Verwaltung der existierenden Geräte.

Bei der Neuregistrierung wird der Nutzer zunächst aufgefordert, einen Namen für das Gerät anzugeben und anschließend das Gerät an den NFC-Reader zu halten. Im Erfolgsfall hat man die Möglichkeit, entweder sofort ein weiteres Gerät zu registrieren oder zur Startseite zurückzukehren. Im Fehlerfall erhält man eine Fehlernachricht, die soweit möglich die Fehlerursache enthält, und kann die Registrierung entweder wiederholen oder abbrechen.

In der Geräteübersicht werden alle bisher registrierten Geräte aufgelistet und können bei Bedarf gelöscht werden.

Android-App
Zum Authentifizieren mittels Android-Geräten haben wir eine Android-App mittels Kotlin entwickelt. Beim Öffnen der App wird dem User sein öffentlicher Schlüssel angezeigt. Abgesehen davon beschränken sich die Funktionen der App auf einen Service, der im Hintergrund (selbst bei geschlossener) App ausgeführt wird und auf NFC-Verbindungen reagiert. Aus Sicherheitsgründen muss das Handy jedoch entsperrt sein. Der Reader/Writer erkennt anhand der UID des Geräts, dass es sich um ein Android-Gerät handelt, und wählt eine dedizierte Application mit einer von uns spezifizierten ID aus. Beim Auswählen der Application ruft das Android-Betriebssystem automatisch den entsprechenden Service auf, der anhand der passenden Application-ID identifiziert wird. Aus Privatsphäre-Gründen sieht das Android-Betriebssystem nicht vor, dass Geräte eindeutig via NFC identfiziert werden können. Daher generiert unsere App eine zufällige, 16-Byte-lange Geräte-Id, die beim Auswählen des Services an den Reader/Writer geschickt wird.

Der Service stellt zwei Funktionen zur Verfügung:
 * GetKey: Schickt den öffentlichen Schlüssel des ECDSA-Key-Pairs an den Reader/Writer
 * Verify: Erhält einen zufälligen Bitstring vom Reader/Writer, signiert diesen und antwortet mit der Signatur

Bei dem Erstellen des Schlüsselpaaren und beim Signieren verwenden wir Androids KeyStore-API. Wenn das Android-Gerät über ein Trusted-Platform-Modul verfügt, werden die Schlüssel dadurch in diesem abgespeichert. So ist es hardwareseitig nicht möglich, auf den privaten Schlüssel des Schlüsselpaares zuzugreifen.