DE:Konzept geheime Wahlen mit OS4 - OpenSlides/OpenSlides GitHub Wiki

Einleitung

Der Vote-Service besteht aus zwei Komponenten. Dem vote-service und dem decrypter.

Der Vote-Service nimmt die Steuerungsbefehle des OpenSlides-Backends entgegen, sowie die einzelnen Stimmen der Nutzer.

Der decrypter hat Zugriff auf die privaten kryptografischen Schlüssel. Nur er ist in der Lage, die verschlüsselten Stimmen der Clients zu öffnen.

Der Vote-Service und der decrypter müssen von zwei unterschiedlichen Teams betrieben werden. Es darf keinen Admin geben, der Zugriff auf beide Komponenten hat.

Dem decrypter wird beim Start ein asynchrones Schlüsselpaar übergeben: der Hauptschlüssel. Nur der decrypter darf den geheimen Hauptschlüssel kennen. Alternativ kann der decrypter sich den Schlüssel auch selbst als zufällige 32-Bit-Folge generieren.

Der decrypter hat keinen Zugriff auf den Datastore oder andere Services innerhalb von OpenSlides.

Ablauf einer Abstimmung

Vorbereitung der Clients

Vor den Abstimmungen erhalten die Clients den öffentlichen Hauptschlüssel von OpenSlides über ein calculated field im Autoupdate-Service in base64-Kodierung. Sie können durch den Fingerprint des Schlüssels sicherstellen, dass der Schlüssel nicht verfälscht wurde. Den Fingerprint des Hauptschlüssels müssen sie über eine sichere, vom OpenSlides-Betreiber unabhängige Quelle erhalten. Der Fingerprint könnte beispielsweise über die direkte base64-Kodierung angezeigt und verifiziert werden oder alternativ als 16x16-Pixelbild.

Die Übertragung des Schlüssels über den Server an den Client dient nur der Einfachheit: Das Ziel ist, dass Client und decrypter unabhängig voneinander Kenntnis vom Hauptschlüssel haben müssen.

Starten einer Abstimmung

Beim Starten einer Abstimmung erhält der vote-service ein Kontrollsignal über das OpenSlides-Backend. Dieser leitet das Signal an den decrypter weiter.

Der decrypter generiert daraufhin ein neues geheimes Schlüsselpaar. Dieser wird Poll-Key genannt. Der Poll-Key wird mit dem Hauptschlüssel signiert und gespeichert. Anschließend wird der öffentliche Poll-Key über den vote-service an die stimmberechtigten Nutzer weitergegeben. Der öffentliche Poll-Key wird inklusive Signatur vom Backend auch im Datastore im Poll-Model persistiert.

Abstimmung im Client

Der Client überprüft mit dem öffentlichen Hauptschlüssel die Signatur des öffentlichen Poll-Key. Anschließend wird mit dem öffentlichen Poll-Key die Stimme verschlüsselt.

Das Request-Body ist fast wie bisher.

https://github.com/OpenSlides/OpenSlides/wiki/poll.vote

Der Key value wird verschlüsselt. Die anderen Keys sind im Klartext.

In das verschlüsselten value-object wird ein vom Client zufällig gewähltes Token geschrieben. Dieses muss der Client zur späteren Verifizierung auf irgendeine Art vorhalten - der einfachste Weg ist die simple Speicherung im Arbeitsspeicher, andere Möglichkeiten wären der Download als Datei oder die Verschlüsselung mittels eines extra Passworts.

Der Request-Body ist beispielsweise:

{
    id: Id; // poll_id
    user_id: id; // vote-user. Für Vote-Delegation.
    
    value: base64(enc({
        // pollmethod in (Y, N)
        votes: {<option_id>: <amount>} | 'Y' | 'N' | 'A';

        // pollmethod not in (Y, N)
        votes: {<option_id>: 'Y' | 'N' | 'A'} | 'Y' | 'N' | 'A';
        
        token: string; // Zufällige Zeichenfolge, die nur der Client kennt.
    }))
}

Der Client schickt den Request an den vote-service.

Validierung und Speichern durch Collector

Der vote-service zieht aus der auth-session die User-Id, schaut, dass der Nutzer anwesend ist, überprüft, dass der Request-User für den Vote-User abstimmen darf (Stimm-Delegierung) und der Vote-User in einer Gruppe ist, die abstimmen darf.

Anschließend speichert er im Vote-Backend (Redis oder Postgres) sowohl die verschlüsselte Stimme als auch die Information, dass der Nutzer abgestimmt hat. In der selben Datenbank-Transaktion wird sichergestellt, dass der Nutzer vorher noch nicht abgestimmt hat und die Abstimmung noch läuft.

Ende der Abstimmung

Das OpenSlides-Backend sendet am Ende der Abstimmung ein Signal an den vote-service.

Der vote-service lädt alle verschlüsselten Stimmen aus dem Backend (postgres/redis) und sendet diese an den decrypter.

Entschlüsseln der Ergebnisse

Der decrypter entschlüsselt die Stimmen.

Kann eine Stimme nicht entschlüsselt werden, so wird als Platzhalter eine Fake-Stimme eingefügt, die die Fehlermeldung enthält, dass die Stimme nicht entschlüsselt werden konnte.

Über alle eingegangenen Stimmen wird ein Hash gebildet. Beim ersten Request zu einer poll_id wird dieser Hash gespeichert. Bei allen weiteren Requests wird der Hash mit dem bereits gespeicherten Hash verglichen. Stimmt er nicht überein, dann wurden andere Daten gesendet und das Request wird abgewiesen.

Anschließend wird die Reihenfolge der Stimmen zufällig getauscht. Die Liste der entschlüsselten Stimmen wird an den vote-service zurückgegeben. Zum Beispiel:

[{
    "votes": "Y",
    "token": "abc",
},{
    "votes": "N",
    "token": "123",
}]

Die Liste der Werte wird mit dem Hauptschlüssel signiert und die Signatur mit zurückgegeben. Zusätzlich wird ein Mapping an invaliden Stimmen vom Index der Stimme in der Vote-Liste zu der entsprechenden Fehlermeldung mitgegeben.

Auswerten der Abstimmung

Der Vote-Service leitet das Ergebnis an das Backend weiter.

Das Backend wertet das Ergebnis aus. Es wird zusätzlich im Datastore der signierte Datenblob vom Vote-Decrypt-Service nebst der Signatur gespeichert. Dieser Blob wird mit der Poll veröffentlicht.

Löschen der Abstimmung im Vote-Service

Das OpenSlides-Backend sendet nach dem erfolgreichen Speichern der Daten im Datastore einen Clear-Request an den Vote-Service.

Der vote-service löscht hieraufhin alle Informationen zur Abstimmung (die verschlüsselten Stimmen) aus dem Vote-Backend.

Der decrypter löscht hieraufhin den Poll-Key und den oben angelegten Hash. Anschließend ist der Decrypter nicht mehr in der Lage, Stimmen für diese Abstimmung zu entschlüsseln.

Es ist unschädlich, wenn der Poll-Key sich in einem Backup befindet und daher nicht endgültig gelöscht werden kann. Wichtig ist nur, dass der Decrypter nach einem Clear-Request keinen Zugriff mehr auf den poll-key hat. Der richtige Begriff ist daher nicht "löschen" sondern "sperren".

Überprüfung durch die Clients

Die Clients erhalten nun die signierte Liste der Abstimmungsergebnisse. Sie überprüfen mit dem öffentlichen Hauptschlüssel, ob die Daten verändert wurden. Da der Client sein Token kennt, überprüft er, ob er seine Stimme in der Liste finden kann und ob diese richtig ist. Außerdem überprüft er, ob die Anzahl der Stimmen in der Liste richtig ist. Hierfür muss der Client während der Abstimmung wissen, wie viele User stimmberechtigt waren.

Optional kann der Client die signierte Abstimmungsliste durch eine externe Software überprüfen. Hierdurch wird sichergestellt, das der OpenSlides-Client nicht manipuliert wurde.

Kommunikation zwischen Collector und Decrypter

Die Kommunikation zwischen vote-service und dem decrypter passiert über gRPC.

Hierbei kann optional sichergestellt werden, dass kein Man-in-the-middle-Angriff passiert und nur berechtigte Services miteinander kommunizieren. Dies ist jedoch außerhalb des vorliegenden Konzepts und wird über entsprechende Firewall-Regeln bzw. TLS-Zertifikate sichergestellt.

Setup des decrypters

Das Setup eines decrypters ist möglichst einfach. Es wird keine Datenbank benötigt. Die wenigen Daten, die gespeichert werden müssen, werden direkt auf der Festplatte abgelegt. Dies sind pro Poll der Poll-Key zusammen mit dem Entschlüsselungs-Hash.

Alternativ zum Speichern der Daten auf der Festplatte wird es auch möglich sein die Daten in eine Postgres-Datenbank zu speichern.

Der Vote-Decrypt-Service kann so betrieben werden, dass er nur Requests vom Vote-Service/OpenSlides-Server annimmt. Über entsprechende Firewall-Einstellungen können alle anderen IPs gesperrt werden. Auf diese Weise besteht ein hoher Schutz gegen DDoS- und andere Angriffe.

Angriffszenarien

Die Sicherheit basiert auf der Annahme, dass der Admin des Vote-Service und der Admin des decrypters nicht zusammenarbeiten.

Der Admin des decrypters kennt die Reihenfole, in der die Stimmen abgegeben wurden. Er weiß aber nicht, welche Nutzer wann abgestimmt hat. Der Admin des Vote-Service kennt die Reihenfolge, in der die Nutzer abgestimmt haben, kann aber die unverschlüsselten Stimmen nicht den Nutzern zuordnen. Insbesondere kann er die entschlüsselte Stimme nicht neu verschlüsseln um sie mit den gespeicherten verschlüsselten Stimmen zu vergleichen. Wenn er die entschlüsselten Stimmen neu verschlüsselt entstehen anderer Datenblobs.

Keiner der Admins kann eine Stimme entfernen, verändern oder hinzufügen. Dies würde bei der Auswertung durch die Clients auffallen.

Der Vote-Service-Admin hat nicht die Möglichkeit jedem User unterschiedliche Daten anzuzeigen (jeder Nutzer wird seine korrekte Stimme angezeigt, aber alle anderen Stimmen sind manipuliert), weil dann die Signatur fehlschlagen würde. Der decrypter gibt nur einen Datensatz zurück, weshalb dieser ebenfalls Nutzern nicht unterschiedliche Daten geben kann.

Der Vote-Service-Admin kann keinen angepassten JS-Code an die Clients ausliefern, der die Signatur falsch prüft, da die Clients den Datenblob auch manuell prüfen können. Entweder im Terminal oder über einen externen Service.

Mögliche Angriffe sind folgende:

Vote-Service-Admin betreibt eigenen Vote-Decrypt-Service

Der Vote-Service-Admin betreibt illegal seinen eigenen decrypter und verteilt seinen eigenen öffentlichen Hauptschlüssel des falschen decrypters.

Dieser Angriff fällt auf, wenn die Clients die Signatur des Hauptschlüssels über eine sichere Quelle überprüfen.

Manipulierte Client-Software

Der Vote-Service-Admin könnte einen gefälschten Client ausliefern. Dieser verhält sich zwar korrekt, sendet die unverschlüsselte Stimme (oder mit einem Schlüssel des Vote-Admins) aber zusätzlich noch an eine andere URL.

Mit dem Angriff wird die Wahl nicht manipuliert. Der Admin erfährt jedoch, wie welcher Nutzer abgestimmt hat.

Da OpenSlides sehr viele Requests absendet, wäre es selbst für einen aufmerksmane Nutzer sehr schwer, möglich die Daten zu entdecken.

Eine Lösung wäre, dass wir für das Voting eine abgeschlossene App/Browser-Plugin/etc. anbieten, welches code-reviewed wurde und vom Admin zur Laufzeit nicht angepasst werden kann.

Falsche Anzahl der Wahlberechtigten

Bei größeren Veranstaltungen könnte der Vote-Service-Admin gegenüber den Nutzern behaupten, dass mehr Leute stimmberechtigt sind. Zum Beispiel behauptet er, dass 100 Personen stimmberechtigt sind, obwohl es tatsächlich nur 80 sind. Die 20 Differenzstimmen kann nun der Admin selbst abgeben.

Nach der oben beschriebenen Überprüfung durch die Clients fällt dies nicht auf. Der Angriff fällt nur auf, wenn ein Nutzer weiß, dass es (nach der Satzung) nur 80 Delegierte hätte geben dürfen, aber 100 Stimmen abgegeben wurden.

Abstimmen für Nichtwähler

Der Vote-Service-Admin könnte beim Beenden einer Wahl für alle Nicht-Wähler eine Stimme abgeben. Der Angriff funktioniert nur für Nicht-Wähler, die auf Anwesend gesetzt sind.

Dieser Angriff fällt nur auf, wenn Nutzer später beweisen könnten, dass sie nicht abgestimmt haben. Dies dürfte sehr schwierig sein.

Abfangen und einzelnes Entschlüsseln der Stimmen

Der Admin kann bei einer Person herausfinden, wie diese abgestimmt hat, wenn er dafür in Kauf nimmt, dass die Abstimmung ungültig wird. Er sendet dafür die eine Stimme an zur Entschlüsselung an den Decrypt-Service. Dieser entschlüsselt die Stimme. Da es nur eine Stimme gibt, kann die Reihenfolge nicht geändert werden und der Admin erfährt, wie diese eine Stimme unverschlüsselt aussieht.

Allerdings kann der Admin anschließend die anderen Stimme nicht mehr entschlüsseln, da dies zu einem anderen Entschlüsselungs-Hash führen würde. Insbesondere kann er die anderen Stimmen nicht unter einer anderen poll_id an den decrypter senden, da dann die Stimmen mit einem anderen Poll_key verschlüsselt sind.

Der Angriff kann nicht dadurch abgewehrt werden, dass der Vote-Decrypt-Service die Stimmen nur dann entschlüsselt, wenn mindestens X Stimmen übergeben werden. Denn der OpenSlides-Admin kann ohne Probleme X Fake-Stimmen erstellen und mit dem öffentlichen poll_key verschlüsseln. Wenn er die Fake-Stimmen und die eine User-Stimme übersendet und entschlüsseln lässt, dann kann er in der Rückgabeliste ohne Probleme seine eigenen Stimmen aussortieren und erhält nur die User-Stimme.

Eine Verteidigung gegen diesen Angriff existiert noch nicht.

Performance

Die Performance währned einer Abstimmung ist identisch wie der aktuelle Vote-Service.

Erst am Ende einer Abstimmung müssen die Einzelstimmen entschlüsselt werden.

Interface der Services

Der decrypter hat drei URLs. Alle urls werden nur vom Vote-Service aufgerufen, es ist aber unkritisch, wenn jemand anderes diese aufruft. Alle urls sind idempotent.

vote-start

Erzeugt einen asymmetrischen Poll-Key und gibt den öffentlichen Key zurück.

vote-results

Nimmt eine Liste von verschlüsselten Stimmen entgegen und gibt diese entschlüsselt in zufälliger Reihenfolge zurück. Speichert den Hash und stellt sicher, dass dieser bei weiteren Requests nicht unterschiedlic hist.

vote-clear

Löscht alle Daten zu einer Poll

Kryptograhie

Folgende kryptografischen Operationen sind erforderlich:

Hauptschlüssel

Der asynchrone Hauptschlüssel wird nur zum Signieren verwendet. Er muss vom Decrypter nur gelesen werden. Für ein einfaches Setup sollte jedoch ein Tool angeboten werden, um den Schlüssel zu erstellen. Der Hauptschlüssel sollte sicher genug sein, dass er für einen längeren Zeitraum nicht ausgewechselt werden muss.

Theoretisch kann - wenn gerade keine Abstimmung läuft - der Hauptschlüssel ausgetauscht werden. Da der Fingerprint des öffentlichen Schlüssels an die Clients verteilt werden muss, sollte dies nur überlegt passieren.

Poll-Keys

Die Poll-Keys werden pro Abstimmung erstellt. Dies passiert beim Starten einer Abstimmung. Zu diesem Zeitpunkt gibt es (solange nicht parallel andere Veranstaltungen laufen) keine hohe CPU last. Das Erstellen sollte jedoch nur wenige 100 Millisekunden dauern, damit der Poll-Manager auf den Response nicht lange warten muss.

Die Poll-Keys werden nur zum Verschlüsseln verwendet.

Verschlüsseln der Votes

Die Votes haben eine unbestimmte Größe. In den meisten Fällen sollte es unter 100 Byte sein. Das System sollte jedoch mit 1kb großen Stimmen keine Performanceprobleme bekommen.

Das Verschlüsselungsverfahren muss sicherstellen, dass die verschlüsselten Daten nicht verändert wurden, da die Daten über den (potenziell bösen) OpenSlides-Admin an den Decrypter gesandt werden und nicht weiter signiert sind. Ein HMAC ist daher erforderlich.

Außerdem muss das Verfahren sicherstellen, dass bei der Verschlüsselung der gleichen Daten unterschiedliche verschlüsselte Daten entstehen. Der OpenSlides-Admin kennt am Ende einer Abstimmung sowohl die verschlüsselten Einzellstimmen als auch die entschlüsselten Einzelstimmen. Es darf ihm nicht möglich sein durch erneutes verschlüsseln der Einzelstimmen den Klartext einer Stimme der verschlüsselten Stimme zuzuordnen. Es muss daher eine probabilistic encryption verwendet werden.

Die Verschlüsselung passiert verteilt in jedem Client. Die Geschwindigkeit beim Verschlüsseln sollte daher keine Rolle spielen.

Die Entschlüsselung passiert in einem Request. Grundsätzlich sind es gleichviele Einzelstimmen. Eine Parallelisierung sollte (auf die Anzahl der CPU-Kerne) möglich sein. Ein solcher Request ist zwar nicht zeitkritisch, sollte jedoch nicht mehr als ein paar Sekunden dauern. Während dem Request dürfte es keine weiteren Anfragen an den Decrypter geben.

Die Besonderheit ist, dass nicht ein großer Datenblob entschlüsselt wird, sondern viele kleine. Bei einer hybriden Verschlüsselung müssen daher sehr viele AES-Schlüssel mit dem asymmetrischen Verfahren entschlüsselt werden. Das asymmetrische Verfahren sollte daher schnell sein.

Signieren

Durch den Hauptschlüssel werden die öffentlichen Poll-Keys sowie die Liste aller Stimmen signiert. Dies passiert immer in nicht kritischen Momenten. Die Signatur des poll-keys muss nur für den Zeitraum der Abstimmung halten. Die Signatur für die Gesamtliste sollte jedoch für einen längeren Zeitraum die Echtheit beweisen können.

⚠️ **GitHub.com Fallback** ⚠️