Rust im Rampenlicht: Unsere Wahl für sichere Anwendungen

von am

Ein technischer deep-dive über unsere Erfahrungen mit Rust und sicherer Programmierung.

Im letzten Beitrag sind wir auf die Unterschiede von Verschlüsselung eingegangen und warum es wichtig ist für die Integrität von einem kritischen System, Ende-zu-Ende Verschlüsselung zu verwenden. Und dass man oft genauer Hinschauen muss wenn es um Verschlüsselung geht.

Die Herausforderungen

Auch, wenn Ende-zu-Ende Verschlüsselung viele Vorteile bietet, bringt es auch Herausforderungen mit sich. Eine dieser Herausforderungen war der Datenexport. Nicht nur für privatwirtschaftliche Unternehmen, sondern besonders auch für öffentliche Träger ist ein vollständiger Datenexport sehr wichtig. Er bietet Flexibilität und eine gewisse Unabhängigkeit von einzelnen Dienstleistern.

Deswegen war uns diese Funktion besonders wichtig. Kam aber mit Herausforderungen. In einem Hinweisgebersystem können sich über Monate oder Jahre, große Datenmengen sammeln. Angefangen über rein textliche Meldungen, über Sprachaufzeichnungen bis zu größeren Dateianhängen, die bei compentum alle Verschlüsselt werden.

Da wir eine clientseitige Ende-zu-Ende Verschlüsselung haben, haben wir nie Zugriff auf die Meldungen im Klartext und nur die Ombudsperson kann diese Daten im Browser entschlüsseln.

Für den einzelnen Fallexport als PDF ist das kein Problem, da diese Daten direkt im Browser entschlüsselt werden und eine PDF daraus generiert. Was aber wenn man alle Daten exportieren will? Das könnte eine sehr lange Zeit in Anspruch nehmen und wäre für einen Benutzer unzumutbar. Abgesehen davon, könnte es zu vielen Fehlerquellen kommen, wenn man das Browserfenster schließt oder die Internetverbindung nicht stabil ist und kurzzeitig abbricht.

Lokale Entschlüsselung

Deswegen sind wir schnell zu dem Schluss gekommen, dass eine lokale Entschlüsselung der Daten, die beste Lösung darstellen würde.
Wenn ein kompletter Datenexport angefordert wird, können wir relativ schnell alle verschlüsselten Daten in ein Archiv packen und dem Benutzer zur Verfügung stellen. In dem Archiv enthalten ist eine kleine Anwendung mit dem die Daten, mit dem entsprechendem Schlüssel entschlüsselt werden.

Anforderungen

Die Anwendung zum Entschlüsseln ist relativ simpel gehalten. Sie muss die json-Dateien mit den Schlüsseln und den Hinweisen einlesen und dann die Hinweise sowie die angehangen Dateien entschlüsseln und speichern.

Die Anforderungen waren auch sehr schnell klar.

  • Geringe Dateigröße, da die Anwendung mit in das Export Archiv soll.
  • Performant, es können mehrere hundert oder gar tausend Hinweise im Export sein, diese sollten schnell entschlüsselt werden. Dazu kommen die Dateianhänge die unter Umständen groß werden können.
  • Wenig Ressourcen verbrauchen, um auch auf schwächeren System laufen zu können.
  • Sicher sein, etwas abstrakt aber Sicherheit ist nun mal wichtig.
  • Cross Plattform, da alle gängigen System (Linux, Windows, MacOS) unterstützt werden sollen.

Proof of Concepts

⚛️ Electron

Das erstmal am nahe liegenste wäre Electron. Da die ganze Verschlüsselungslogik mittels der Web Crypto API gebaut ist, könnte man den Verschlüsselungs-Service direkt benutzen und müsste nur das kleine Frontend bauen.

Nach einem kleinem Proof of Concept und Abgleich der Anforderungen, wurde aber schnell klar, dass Electron möglicherweise nicht das richtige war.

Anforderung

Electron

Notiz

Geringe Dateigröße

🔴

~ 190 mb

Performant

🟠

Wenig Ressourcen verbrauchen

🔴

Hoher RAM Verbrauch

Sicher sein

🟠

Cross Plattform

🟢

Besonders die hohe Dateigröße und der Ressourcenverbrauch waren nicht vertretbar. Bei einer so simplen und „kleinen“ Anwendung, sollte diese nicht fast 200 mb groß sein.

🦀 Rust for the rescue

Eine andere Möglichkeit war Rust. In den letzten Jahren immer beliebter geworden war es einen Versuch wert und der Projekt Scope war gering genug dafür, da es keine komplexe Anwendung ist.

Nach kurzer Zeit stand ein kleiner Proof of Concept der für Test- und Vergleichszwecke ausreichend war. Da es in Rust keine Web Crypto API gibt, mussten wir auf bestehende crates zurückgreifen. Dafür wurden die Rust Crypto crates verwendet. Da diese alle wichtigen Implementierungen hatten die wir benötigten. (AES-GCM, RSA-OAEP, PBKDF2). Dazu kommt, dass die Speicherverwaltung von Rust sehr gut und sicher ist.

Anforderung

Rust

Notiz

Geringe Dateigröße

🟢

Mit GUI ~ 3mb

Performant

🟢

Wenig Ressourcen verbrauchen

🟢

Verbraucht kaum RAM

Sicher sein

🟢

Cross Plattform

🟢

Die Entwicklung war sehr angenehm, auch wenn es einige kleinere Punkte gab die etwas mehr Aufmerksamkeit brauchten.

Zum einen gibt es bis jetzt keine crate die JSON Web Keys (JWK) vollumfänglich unterstützt, und JWE implementiert hat. Für JWT und Signierung gibt es zwar welche, aber diese unterstützen keine RSA-OAEP Schlüssel.

Zum anderen, da die JSON des Exports relativ groß werden kann, war es uns wichtig diese zu streamen und nicht komplett in den Arbeitsspeicher zu laden. Die bekannteste crate um mit json zu arbeiten ist serde und serde_json. Die aber keine out-of-the-box Möglichkeit für streaming bietet und man da selbst tätig werden muss. Was jedoch auch nicht sonderlich komplex war:

pub fn iter_json_array<T: DeserializeOwned, R: Read>(
    mut reader: R,
) -> impl Iterator<Item = Result<T, io::Error>> {
    let mut at_start = false;
    std::iter::from_fn(move || yield_next_obj(&mut reader, &mut at_start).transpose())
}

pub fn process_reports<P: AsRef<Path>>(path: P, wallet: Wallet) -> Result<(), Box<dyn Error>> {
    let reader = BufReader::new(File::open(path)?);
   
    for report in iter_json_array(reader) {
        let report: Report = report.unwrap();
        let decrypted_report = report.decrypt(&wallet).expect("Error decrypting report");
    }
}

GUI

Ein anderer wichtiger Punkt ist die grafische Oberfläche. Da Kunden ungern mit Konsolen-Anwendungen arbeiten. Im Rust-Universum gibt es mittlerweile viele Frameworks die dies bieten. Da die Anwendung jedoch simpel und klein sein sollte, haben wir uns für Slint entschieden, da es zum einen eine sehr angenehme Möglichkeit bietet Oberflächen zu gestalten und zum anderen auch für embedded systems optimiert ist und somit gute Performance bietet.

Die Oberfläche wird in separaten .slint Dateien gestaltet und in der Rust Applikation dann initialisiert werden. Man kann somit auch sehr gut komplexere Anwendungen gestalten da man das UI in Komponenten / Module einteilen kann.

component HeaderLogo inherits Rectangle {
    width: 415px;
    height: 80px;

    Image {
        source: @image-url("../assets/logo.png");
        height: parent.height;
    }
}

Abschluss

Abschließend sind wir sehr zufrieden mit der Entscheidung auf Rust zu setzten und sehen die Vorteile einer sicheren und schnellen Systemsprache.

Mit wenigen Klicks zum eigenen Meldekanal

Schnell anmelden und unverbindlich testen.

Jetzt kostenlos starten