Ich habe mich kürzlich mit einem Thunderbird- Entwickler über API-Design unterhalten . Während dieses Gesprächs teilte ich meine Gedanken zu RNP mit , einer neuen Implementierung von OpenPGP , die Thunderbird kürzlich anstelle von GnuPG verwendet .
Der Gesprächspartner war skeptisch gegenüber meiner These, dass die RNP-API verbessert werden muss, und fragte: "Ist es nicht subjektiv - welche API ist besser und welche schlechter?" Ich bin damit einverstanden, dass wir keine guten Metriken für die Bewertung von APIs haben. Ich bin jedoch nicht der Meinung, dass wir die API im Prinzip nicht beurteilen können.
Tatsächlich vermute ich, dass die meisten erfahrenen Programmierer eine schlechte API erkennen, wenn sie sie sehen. Ich denke weiter in diesem Artikel wird sich herausstellen, dass sich eine gute Heuristik entwickelt, die ich versuchen werde, auf meiner eigenen Erfahrung mit (und über) GnuPG, Sequoia , aufzubauen und RNP. Dann gehe ich die RNP-API durch. Leider kann diese API nicht nur leicht missbraucht werden, sondern täuscht auch, sodass sie in sicherheitskritischen Kontexten noch nicht verwendet werden sollte. Die Zielgruppe von Thunderbird sind jedoch Personen, von denen bekannt ist, dass sie gefährdet sind, wie Journalisten, Aktivisten, Anwälte und ihre Kommunikationspartner. Alle diese Menschen brauchen Schutz. Meiner Meinung nach bedeutet dies, dass Thunderbird noch einmal darüber nachdenken sollte, ob RNP verwendet werden soll.
Hinweis: Ich empfehle außerdem, diese E-Mail zu lesen: Verwenden wir GPL-Bibliotheken in Thunderbird! die ich an den Thunderbird Development Planning Post gesendet habe .
Was sind die Merkmale einer schlechten API?
Bevor wir Sequoia mit Justus und Kai starteten, arbeiteten wir drei an GnuPG . Wir haben uns nicht nur selbst mit gpg befasst, sondern auch mit vielen nachfolgenden gpg-Benutzern gesprochen und zusammengearbeitet. Die Leute konnten viele gute Dinge über GnuPG sagen .
In Bezug auf die Kritik an gpg waren zwei Arten von Kritik an der API am bedeutendsten. Das erste läuft darauf hinaus: Die gpg-API ist zu dogmatisch. Zum Beispiel verwendet gpg einen Schlüsselring-Ansatz. Daher können Sie ein OpenPGP-Zertifikat nur anzeigen oder verwenden, wenn es in die persönliche Schlüsselbasis importiert wurde. Einige Entwickler möchten sich jedoch zuerst das Zertifikat ansehen und es erst dann importieren. Wenn Sie beispielsweise anhand des Fingerabdrucks auf einem Schlüsselserver nach einem Zertifikat suchen, können Sie überprüfen, ob das zurückgegebene Zertifikat wirklich das ist, das Sie benötigen.weil seine URL sich selbst authentifiziert. Dies kann mit gpg erfolgen, jedoch nur auf Umgehung, wobei die Prinzipien des darin eingebetteten Programmiermodells umgangen werden. Die Grundidee lautet: Erstellen Sie ein temporäres Verzeichnis, fügen Sie eine Konfigurationsdatei hinzu, weisen Sie gpg an, ein alternatives Verzeichnis zu verwenden, importieren Sie das Zertifikat dort, überprüfen Sie das Zertifikat und löschen Sie das temporäre Verzeichnis. Dies ist eine offizielle Empfehlung von Justus, die auf unseren Gesprächen mit nachfolgenden GPG-Benutzern basiert. Ja, diese Methode funktioniert. Es erfordert jedoch das Schreiben von Code, der für das Betriebssystem spezifisch ist. Dieser Code ist langsam und es treten häufig Fehler auf.
Eine andere Klasse von Bemerkungen, auf die wir schon oft gestoßen sind, ist, dass man für die Arbeit mit gpg viele nicht offensichtliche Dinge wissen muss, um diesen Mechanismus nicht zu missbrauchen. Oder anders ausgedrückt: Sie müssen bei der Verwendung der gpg-API sehr vorsichtig sein, um zu vermeiden, dass versehentlich eine Sicherheitsanfälligkeit in Ihren Code eingefügt wird.
Berücksichtigen Sie die EFAIL- Schwachstellen, um das zweite Problem besser zu verstehen ... Das Hauptproblem mit der gpg-Entschlüsselungs-API: Beim Entschlüsseln einer Nachricht gibt gpg einfachen Text aus, selbst wenn die Eingabe beschädigt wurde. gpg gibt in diesem Fall zwar einen Fehler zurück, aber einige Programme geben immer noch einfachen Text in einer beschädigten Form aus. Also warum nicht? Es ist definitiv besser, zumindest einen Teil der Nachricht zu zeigen, als nichts zu zeigen, oder? Nun, die EFAIL-Schwachstellen zeigen, wie ein Angreifer dies nutzen kann, um einen Webfehler in eine verschlüsselte Nachricht einzufügen . Wenn ein Benutzer diesen Beitrag anzeigt, tritt ein Webfehler aus dem Beitrag aus. Puh.
Also, wessen Schuld ist dieser Fehler? Die GnuPG- Entwickler bestanden darauf Das Problem liegt auf Anwendungsebene, da sie gpg falsch verwenden:
Es wird empfohlen, dass E-Mail-Benutzeragenten den Statuscode DECRYPTION_FAILED einhalten und keine Daten anzeigen oder zumindest einen geeigneten Weg wählen, um potenziell beschädigte E-Mails anzuzeigen, ohne ein Orakel zu erstellen Informieren des Benutzers, dass die E-Mail kein Vertrauen schafft.
gpg signalisierte einen Fehler; Anwendungen halten den API-Vertrag nicht ein. Ich muss den GnuPG-Entwicklern zustimmen und hinzufügen: Die GPG-Oberfläche war (und ist) eine tickende Zeitbombe, weil sie dem Benutzer nicht sagt, wie er vorgehen soll. Im Gegenteil, eine einfache und scheinbar vorteilhafte Handlung ist falsch. Und eine API dieser Artsind leider häufig in GnuPG.
Was macht eine gute API aus?
Die Erkenntnis, dass die GPG-API zu dogmatisch und schwer zu verwenden ist, hat meine Pläne geprägt. Als wir das Sequoia-Projekt starteten, waren wir uns einig, dass wir solche Fehler vermeiden wollten. Basierend auf unseren Beobachtungen setzen wir zwei Tests in die Praxis um, die wir weiterhin als Referenzpunkte für die Entwicklung der Sequoia-API verwenden. Erstens muss es zusätzlich zu einer High-Level-API eine Low-Level-API geben, die nicht dogmatisch ist - in dem Sinne, dass sie den Benutzer nicht daran hindert, etwas zu tun, was nicht verboten ist. Gleichzeitig sollte die API den Benutzer zu den richtigen (fest codierten) Dingen führen, damit die richtigen Aktionen einfach ausgeführt werden können und bei der Auswahl einer Aktion am offensichtlichsten sind .
Um diese beiden leicht widersprüchlichen Ziele zu erreichen, alles möglich zu machen, aber Fehler zu vermeiden, haben wir uns besonders stark auf zwei Werkzeuge verlassen: Typen und Beispiele. Typen erschweren die unbeabsichtigte Verwendung eines Objekts, da der API-Vertrag zur Kompilierungszeit formalisiert wird und sogar bestimmte Konvertierungen erzwingt . Beispiele - Codefragmente - werden kopiert . Gute Beispiele zeigen den Benutzern daher nicht nur, wie sie die Funktion richtig verwenden, sondern beeinflussen auch stark, wie sie sie verwenden.
Typen
Ich zeige Ihnen anhand eines Beispiels, wie wir Typen in Sequoia verwenden und wie sie uns helfen, eine gute API zu erstellen. Um das Beispiel klarer zu machen, ist es hilfreich, sich an einen Kontext in Bezug auf OpenPGP zu erinnern.
OpenPGP
In OpenPGP gibt es mehrere grundlegende Datentypen, nämlich Zertifikate, Komponenten (wie Schlüssel und Benutzer-IDs) und Bindungssignaturen. Das Stammverzeichnis des Zertifikats ist der Primärschlüssel, der den Fingerabdruck des Zertifikats vollständig identifiziert (Fingerabdruck = Hash (Primärschlüssel)). Ein Zertifikat enthält normalerweise Komponenten wie Unterschlüssel und Benutzer-IDs. OpenPGP bindet eine Komponente mit einer sogenannten Bindungssignatur an ein Zertifikat. Wenn wir einen regulären Primärschlüssel-Hash als Fingerabdruck verwenden und Signaturen verwenden, um Komponenten an den Primärschlüssel zu binden, werden Bedingungen erstellt, damit später zusätzliche Komponenten hinzugefügt werden können. Die Bindungssignaturen enthalten auch Eigenschaften. Daher ist es möglich, die Komponente zu ändern, um beispielsweise die Gültigkeitsdauer des Unterschlüssels zu verlängern.Infolgedessen können einer bestimmten Komponente mehrere gültige Signaturen zugeordnet werden. Ankersignaturen sind nicht nur grundlegend, sondern auch ein wesentlicher Bestandteil des OpenPGP-Sicherheitsmechanismus.
Da es viele gültige Bindungssignaturen geben kann, muss es eine Möglichkeit geben, die gewünschte auszuwählen. Nehmen wir als erste Annäherung an, dass die gewünschte Signatur die neueste, nicht abgelaufene, nicht widerrufene gültige Signatur ist, die nicht für die Zukunft verschoben wurde. Aber was ist eine gültige Unterschrift? Bei Sequoia muss die Signatur nicht nur eine mathematische Prüfung bestehen, sondern auch mit der Richtlinie übereinstimmen. Zum Beispiel erlauben wir SHA-1 aufgrund unserer Fähigkeit , kompromittierten Kollisionen zu widerstehen , nur in einer sehr kleinen Anzahl von Situationen . ( Paul Schaub , der an PGPainless arbeitet , hat kürzlich ausführlich über diese Komplexität geschrieben..) Indem wir den API-Benutzer dazu zwingen, all diese Überlegungen zu berücksichtigen, schaffen wir einen Nährboden für Schwachstellen. In Sequoia ist der einfache Weg, die Ablaufzeit zu ermitteln, der sichere Weg. Betrachten Sie den folgenden Code, der wie erwartet funktioniert:
let p = &StandardPolicy::new();
let cert = Cert::from_str(CERT)?;
for k in cert.with_policy(p, None)?.keys().subkeys() {
println!("Key {}: expiry: {}",
k.fingerprint(),
if let Some(t) = k.key_expiration_time() {
DateTime::<Utc>::from(t).to_rfc3339()
} else {
"never".into()
});
}
cert
Ist ein Zertifikat. Wir beginnen damit, die Richtlinie darauf anzuwenden. (Die Richtlinien sind benutzerdefiniert, aber in der Regel ist StandardPolicy nicht nur ausreichend, sondern auch am besten geeignet.) In der Tat wird hier eine Ansicht des Zertifikats erstellt, in der nur die Komponenten mit einer gültigen Bindungssignatur sichtbar sind. Wichtig ist auch, dass eine Reihe neuer Methoden geändert und eingeführt werden. Die Schlüsselmethode wurde beispielsweise geändert, um ValidKeyAmalgamation anstelle von KeyAmalgamation zurückzugeben . (Dies ist eine Zusammenführung, da das Ergebnis nicht nur den Schlüssel, sondern alle damit verbundenen Signaturen enthält. Einige glauben, dass dieser Prozess besser als Katamari bezeichnet wird... ¯ \ _ (ツ) _ / ¯) ValidKeyAmalgamation hat eine gültige Ankersignatur gemäß den oben genannten Kriterien. Es bietet auch Methoden wie key_expiration_time, was nur mit einem gültigen Schlüssel sinnvoll ist! Beachten Sie auch, dass der mit key_expiration_time verwendete Rückgabetyp ergonomisch ist. Anstatt einen Rohwert zurückzugeben, gibt key_expiration_time SystemTime zurück , das sicher und einfach zu verwenden ist. Gemäß
unserem ersten Prinzip "Alle zulassen" behält der Entwickler weiterhin den Zugriff auf einzelne Signaturen und untersucht Unterpaketeum anhand einer anderen Signaturbindung herauszufinden, wann der Schlüssel abläuft. Verglichen mit der Art und Weise, wie die Sequoia-API den Ablauf eines Schlüssels korrekt kennen soll, würde jeder andere Ansatz der API widersprechen. Dies ist unserer Meinung nach eine gute API.
Beispiele von
Die Version 1.0 der Sequoia-Bibliothek fand im Dezember 2020 statt. Neun Monate zuvor hatten wir eine vollständige Situation und waren zur Veröffentlichung bereit. Aber sie warteten . Wir haben die nächsten neun Monate gebraucht, um der öffentlichen API Dokumentation und Beispiele hinzuzufügen. Schauen Sie sich die Dokumentation zur Cert- Datenstruktur an, um zu sehen, was wir erhalten. Wie in unserem Beitrag erwähnt, konnten wir nicht für jedes Feature Beispiele bis auf eines liefern, aber wir haben einiges getan. Als Bonus für das Schreiben der Beispiele konnten wir auch einige Ecken und Kanten finden, die wir dabei polierten.
Nach der Veröffentlichung konnten wir mit vielen Entwicklern sprechen, die Sequoia in ihren Code aufgenommen haben. Ein roter Faden durch ihr Feedback war die Erkenntnis, wie nützlich sowohl die Dokumentation als auch die Beispiele waren. Wir können bestätigen, dass wir, obwohl dies unser Code ist, fast täglich in die Dokumentation schauen und unsere eigenen Beispiele kopieren. Es ist einfacher. Da die Beispiele Ihnen zeigen, wie Sie eine bestimmte Funktion richtig verwenden, warum sollten Sie sie von Grund auf neu ausführen?
RNP API
RNP ist eine neue Implementierung von OpenPGP, die hauptsächlich von Ribose entwickelt wurde . Vor ungefähr zwei Jahren beschloss Thunderbird, Enigmail in Thunderbird zu integrieren und gleichzeitig GnuPG durch RNP zu ersetzen . Die Tatsache, dass Thunderbird RNP gewählt hat, ist nicht nur für RNP schmeichelhaft. Dies bedeutet auch, dass RNP wohl die am häufigsten nachgefragte Implementierung von OpenPGP zum Verschlüsseln von E-Mails geworden ist.
Kritik ist leicht als negativ wahrzunehmen. Ich möchte ganz klar sagen: Ich denke, die Arbeit von Ribose ist gut und wichtig. Ich bin ihnen dankbar, dass sie Zeit und Mühe in eine neue Implementierung von OpenPGP investiert haben. Das OpenPGP-Ökosystem muss dringend Abwechslung schaffen. Dies ist jedoch keine Entschuldigung für die Freigabe eines unreifen Produkts zur Verwendung in einem sicherheitskritischen Kontext.
Sicherheitskritische Infrastruktur
Leider hat RNP noch keinen Zustand erreicht, in dem es meiner Meinung nach sicher eingesetzt werden kann. Enigmail wurde nicht nur von Personen verwendet, die sich um den Schutz ihrer Daten sorgen, sondern auch von Journalisten, Aktivisten und Anwälten, die sich um ihre eigene Sicherheit und die Sicherheit ihrer Gesprächspartner kümmern. In einem Interview von 2017 sagte Benjamin Ismail, Leiter des Kapitels Reporter ohne Grenzen im asiatisch-pazifischen Raum :
Wir verwenden GPG hauptsächlich, um frei mit unseren Quellen zu kommunizieren. Die Informationen, die sie uns über Menschenrechte und Verstöße gegen diese Rechte geben, sind für sie nicht sicher, daher ist es notwendig, die Integrität unserer Gespräche zu schützen.
Interview mit Benjamin Ismail von der Organisation Reporter ohne Grenzen
Es ist wichtig, dass Thunderbird diesen Benutzern auch während dieser Übergangszeit weiterhin die sicherste Erfahrung bietet.
RNP- und Unterschlüssel-Bindungssignaturen
Als ich darüber sprach, wie wir Typen in Sequoia verwenden, um den Missbrauch der API zu erschweren, habe ich Ihnen gezeigt, wie Sie das Ablaufdatum eines Schlüssels in nur wenigen Codezeilen ermitteln können. Ich wollte mit einem Beispiel beginnen, das einer in OpenPGP oder RNP unerfahrenen Person zeigt, wie dieselbe Funktionalität mit RNP implementiert werden kann. Der folgende Code durchläuft die Zertifikat-Unterschlüssel (Schlüssel) und zeigt das Ablaufdatum für jeden Unterschlüssel an. Zur Erinnerung: Die Ablaufzeit wird in der Unterschlüssel-Bindungssignatur gespeichert, und der Wert 0 gibt an, dass der Schlüssel niemals abläuft.
int i;
for (i = 0; i < sk_count; i ++) {
rnp_key_handle_t sk;
err = rnp_key_get_subkey_at(key, i, &sk);
if (err) {
printf("rnp_key_get_subkey_at(%d): %x\n", i, err);
return 1;
}
uint32_t expiration_time;
err = rnp_key_get_expiration(sk, &expiration_time);
if (err) {
printf("#%d (%s). rnp_key_get_expiration: %x\n",
i + 1, desc[i], err);
} else {
printf("#%d (%s) expires %"PRIu32" seconds after key's creation time.\n",
i + 1, desc[i],
expiration_time);
}
}
Ich habe diesen Code auf einem Zertifikat mit fünf Unterschlüsseln getestet. Der erste Unterschlüssel hat eine gültige verbindliche Signatur und läuft nicht ab. Die zweite hat eine gültige verbindliche Unterschrift und läuft in Zukunft ab. der dritte hat eine gültige verbindliche Unterschrift, ist aber bereits abgelaufen; Der vierte hat eine ungültige verbindliche Signatur, nach der der Unterschlüssel in Zukunft abläuft. Die fünfte Signatur hat überhaupt keinen Anker. Hier ist die Ausgabe:
#1 (doesn't expire) expires 0 seconds after key's creation time.
#2 (expires) expires 94670781 seconds after key's creation time.
#3 (expired) expires 86400 seconds after key's creation time.
#4 (invalid sig) expires 0 seconds after key's creation time.
#5 (no sig) expires 0 seconds after key's creation time.
Beachten Sie zunächst, dass der Aufruf von rnp_key_get_expiration erfolgreich ist, unabhängig davon, ob der Unterschlüssel eine gültige Bindungssignatur, eine ungültige Bindungssignatur oder überhaupt keine Bindungssignatur hat. Wenn Sie die Dokumentation lesen , scheint dieses Verhalten etwas überraschend. Es sagt:
.
: 0 , .
Da die Schlüsselablaufzeit in der Bindungssignatur gespeichert ist, verstehe ich das als OpenPGP-Experte folgendermaßen: Ein Aufruf von rnp_key_get_expiration ist nur erfolgreich, wenn der Unterschlüssel eine gültige Bindungssignatur hat. Tatsächlich stellt sich heraus, dass die Funktion standardmäßig auf 0 gesetzt wird, wenn keine gültige Bindungssignatur vorhanden ist. Angesichts der obigen Bemerkung würde der API-Benutzer erwarten, dass dies wie folgt interpretiert wird: Dieser Schlüssel ist unbegrenzt gültig.
Um diesen Code zu verbessern, müssen Sie zunächst überprüfen, ob der Schlüssel eine gültige Bindungssignatur hat. Vor kurzem wurden RNP mehrere Funktionen hinzugefügt, um CVE-2021-23991 zu adressieren . Insbesondere RNP-Entwickler Die Funktion rnp_key_is_valid wurde hinzugefügt , um Informationen darüber zurückzugeben, ob ein Schlüssel gültig ist. Dieses Add-On verbessert die Situation, erfordert jedoch, dass der Entwickler explizit auswählt, ob diese sicherheitskritischen Überprüfungen durchgeführt werden sollen (anstatt die bereits festgelegten Überprüfungen explizit aufzugeben - wie dies bei Sequoia der Fall wäre). Da es bei Sicherheitsüberprüfungen nicht darum geht, nützliche Arbeit zu leisten, können Sie diese leicht vergessen: Der Code funktioniert auch dann, wenn keine Sicherheitsüberprüfungen durchgeführt wurden. Und da Expertenwissen erforderlich ist, um die richtige Wahl zu treffen, werden Schecks vergessen.
Der folgende Code bietet Sicherheitsüberprüfungen und überspringt alle Schlüssel, die rnp_key_is_valid für ungültig hält:
int i;
for (i = 0; i < sk_count; i ++) {
rnp_key_handle_t sk;
err = rnp_key_get_subkey_at(key, i, &sk);
if (err) {
printf("rnp_key_get_subkey_at(%d): %x\n", i, err);
return 1;
}
bool is_valid = false;
err = rnp_key_is_valid(sk, &is_valid);
if (err) {
printf("rnp_key_is_valid: %x\n", err);
return 1;
}
if (! is_valid) {
printf("#%d (%s) is invalid, skipping.\n",
i + 1, desc[i]);
continue;
}
uint32_t expiration_time;
err = rnp_key_get_expiration(sk, &expiration_time);
if (err) {
printf("#%d (%s). rnp_key_get_expiration: %x\n",
i + 1, desc[i], err);
} else {
printf("#%d (%s) expires %"PRIu32" seconds after key's creation time.\n",
i + 1, desc[i],
expiration_time);
}
}
Ausgabe:
#1 (doesn't expire) expires 0 seconds after key's creation time.
#2 (expires) expires 94670781 seconds after key's creation time.
#3 (expired) is invalid, skipping.
#4 (invalid sig) is invalid, skipping.
#5 (no sig) is invalid, skipping.
Dieser Code überspringt zwei Schlüssel, die keine gültige Bindungssignatur haben, aber er überspringt auch einen abgelaufenen Schlüssel - was wahrscheinlich nicht das ist, was wir wollten, obwohl die Dokumentation uns warnt, dass diese Funktion "... Ablaufdaten überprüft".
Obwohl es auch vorkommt, dass wir keinen abgelaufenen Schlüssel oder kein abgelaufenes Zertifikat verwenden möchten, greifen wir manchmal auf diese zurück. Wenn ein Benutzer beispielsweise vergisst, den Schlüssel zu erneuern, sollte er sehen können, dass der Schlüssel abgelaufen ist, und dann das Zertifikat überprüfen und in diesem Fall auch den Schlüssel erneuern. Obwohl
gpg --list-keys
keine abgelaufenen Schlüssel angezeigt werden, sind beim Bearbeiten eines Zertifikats abgelaufene Unterschlüssel weiterhin sichtbar, sodass der Benutzer ihre Gültigkeit erneuern kann:
$ gpg --edit-key 93D3A2B8DF67CE4B674999B807A5D8589F2492F9
Secret key is available.
sec ed25519/07A5D8589F2492F9
created: 2021-04-26 expires: 2024-04-26 usage: C
trust: unknown validity: unknown
ssb ed25519/1E2F512A0FE99515
created: 2021-04-27 expires: never usage: S
ssb cv25519/8CDDC2BC5EEB61A3
created: 2021-04-26 expires: 2024-04-26 usage: E
ssb ed25519/142D550E6E6DF02E
created: 2021-04-26 expired: 2021-04-27 usage: S
[ unknown] (1). Alice <alice@example.org>
Es gibt andere Situationen, in denen ein abgelaufener Schlüssel nicht ungültig gemacht werden sollte. Angenommen, Alice sendet Bob eine signierte Nachricht: "Ich zahle Ihnen 100 Euro für ein Jahr", und der Signaturschlüssel läuft in sechs Monaten ab. Wenn das Jahr vorbei ist, schuldet Alice Bob aufgrund dieser Unterschrift etwas? Ja, ich denke schon. Die Unterschrift war gültig, als sie angebracht wurde. Die Tatsache, dass der Schlüssel bereits abgelaufen ist, spielt keine Rolle. Wenn der Schlüssel abgelaufen ist, sollten die von ihm nach Ablauf des Zeitraums versiegelten Signaturen natürlich als ungültig angesehen werden. Ebenso sollte eine Nachricht nicht mit einem abgelaufenen Schlüssel verschlüsselt werden.
Kurz gesagt, ob ein Schlüssel als gültig angesehen werden sollte, ist sehr kontextsensitiv. rnp_key_is_valid ist besser als nichts, aber trotz des Namens ist diese Funktion sehr differenziert, um festzustellen, ob ein Schlüssel gültig ist.
Im Rahmen dieses Commits wurde die zweite Funktion hinzugefügt
rnp_key_valid_till
. Diese Funktion gibt "einen Zeitstempel zurück, vor dem der Schlüssel als gültig angesehen werden kann ... Wenn der Schlüssel nie gültig war, wird Null als Wert zurückgegeben." Mit dieser Funktion können Sie feststellen, ob der Schlüssel jemals gültig war. Dazu müssen Sie überprüfen, ob diese Funktion einen Wert ungleich Null zurückgibt:
int i;
for (i = 0; i < sk_count; i ++) {
rnp_key_handle_t sk;
err = rnp_key_get_subkey_at(key, i, &sk);
if (err) {
printf("rnp_key_get_subkey_at(%d): %x\n", i, err);
return 1;
}
uint32_t valid_till;
err = rnp_key_valid_till(sk, &valid_till);
if (err) {
printf("rnp_key_valid_till: %x\n", err);
return 1;
}
printf("#%d (%s) valid till %"PRIu32" seconds after epoch; ",
i + 1, desc[i], valid_till);
if (valid_till == 0) {
printf("invalid, skipping.\n");
continue;
}
uint32_t expiration_time;
err = rnp_key_get_expiration(sk, &expiration_time);
if (err) {
printf("rnp_key_get_expiration: %x\n", err);
} else {
printf("expires %"PRIu32" seconds after key's creation time.\n",
expiration_time);
}
}
Ergebnisse:
#1 (doesn't expire) valid till 1714111110 seconds after epoch; expires 0 seconds after key's creation time.
#2 (expires) valid till 1714111110 seconds after epoch; expires 94670781 seconds after key's creation time.
#3 (expired) valid till 1619527593 seconds after epoch; expires 86400 seconds after key's creation time.
#4 (invalid sig) valid till 0 seconds after epoch; invalid, skipping.
#5 (no sig) valid till 0 seconds after epoch; invalid, skipping.
Jetzt haben wir die gewünschten Ergebnisse erzielt! Wir zeigen die korrekten Ablaufzeiten für die ersten drei Unterschlüssel an und zeigen auch an, dass die letzten beiden Unterschlüssel ungültig sind.
Aber schauen wir uns das genauer an
rnp_key_valid_till
. Zunächst wird in OpenPGP die Schlüsselablaufzeit als vorzeichenloser 32-Bit-Einzug ab dem Zeitpunkt der Schlüsselerstellung gespeichert, ebenfalls im vorzeichenlosen 32-Bit-Format. Daher müsste die Funktion einen breiteren Typ verwenden oder zumindest den Code auf Überläufe überprüfen. (Ich habe dieses Problem gemeldet und es wurde bereits behoben.)
Aber selbst wenn wir diesen Pfosten ignorieren, ist die Funktion immer noch seltsam. In OpenPGP kann ein Schlüssel mehrere Zeiträume gültig sein. Angenommen, der Schlüssel läuft am 1. Juli ab und der Benutzer erneuert ihn erst ab dem 10. Juli. Im Zeitraum vom 1. bis 10. Juli war der Schlüssel ungültig, und während dieser Zeit generierte Signaturen sollten ebenfalls als ungültig betrachtet werden. Was sollte die betrachtete Funktion für einen solchen Schlüssel zurückgeben? Noch wichtiger ist, wie sollte ein Benutzer einer solchen API das Ergebnis interpretieren? Ist es überhaupt angebracht, eine solche API zu verwenden? ( Ja, fragte ich .)
Bei Sequoia gingen wir den anderen Weg. Anstatt die Information zurückzugeben, dass der Schlüssel gültig ist, drehen wir die Situation um. API-Benutzer könnten fragen: ist diese Taste zum Zeitpunkt t gültig . Nach unserer Erfahrung war dies alles, was in allen uns bekannten Fällen tatsächlich erforderlich war.
Denken Sie nicht, dass ich mich speziell mit diesem speziellen Problem mit der RNP-API befasse. Dies ist nur eine Komplikation , über die ich kürzlich nachgedacht habe. Als wir die RNP-API erneut implementierten, um ein alternatives OpenPGP-Backend für Thunderbird zu erstellen , hatten wir viele ähnliche Probleme .
Fazit
Die Fehler der RNP-Entwickler sind verständlich und entschuldbar. OpenPGP ist wie viele andere Protokolle komplex. Es kann jedoch erheblich vereinfacht werden, wenn wir uns bemühen, die PKI flexibel und zuverlässig zu halten und nicht nur über ein Tool zur Dateiverschlüsselung zu verfügen.
Die RNP-API ist jedoch gefährlich. Thunderbird verwendet in sicherheitskritischen Kontexten. In einem Interview von 2017 machte Michal 'Rysiek' Wozniak vom Zentrum für organisierte Kriminalität und Korruptionsforschung (OCCRP) deutlich, dass jemandes Leben auf dem Spiel steht:
Ich bin der festen Überzeugung, dass viele unserer Informanten und Journalisten in Gefahr wären oder hinter Gittern, wenn wir GnuPG nicht die ganze Zeit benutzt hätten ...
Interview mit Michal 'Rysiek' Wozniak vom Zentrum für Korruptions- und Organisationsforschung Kriminalität
Wie wird sich das auf Thunderbird auswirken? Ich sehe drei Möglichkeiten. Erstens könnte Thunderbird wieder zu Enigmail wechseln. Sie könnten denken, dass das Portieren von Enigmail auf Thunderbird 78 schwierig wäre, aber ich habe von vielen Thunderbird-Entwicklern gehört, dass dies mit einem Lift technisch machbar ist. Einer der Gründe, warum Thunderbird sich von Enigmail entfernt hat, ist die enorme Zeit, die die Enigmail-Entwickler aufwenden mussten, um Benutzern bei der korrekten Installation und Konfiguration von GnuPG zu helfen. Daher ist dieser Weg nicht ideal.
Zweitens könnte Thunderbird zu einer anderen OpenPGP-Implementierung wechseln. Es gibt heutzutage eine ganze Reihe von ihnen. zur Auswahl. Persönlich denke ich, dass Thunderbird zu Sequoia hätte wechseln sollen. Natürlich bin ich ein Sequoia-Entwickler, also bin ich voreingenommen. Aber es geht nicht ums Geld: Der Fonds zahlt mich, und auf dem freien Markt würde mir vielleicht doppelt so viel angeboten, wie ich jetzt verdiene. Ich arbeite, um Benutzer zu schützen. Aber abgesehen von der Sequoia-API und den Implementierungsvorteilen gewinnt Thunderbird in diesem Fall noch in einer weiteren Hinsicht: Wir haben diese Implementierung bereits zum Laufen gebracht. Vor einigen Wochen haben wir Octopus veröffentlicht , ein alternatives OpenPGP-Backend für Thunderbird. Es weist nicht nur eine funktionale Parität mit RNP auf, sondern weist auch eine Reihe zuvor fehlender Funktionen auf, z. B. die Integration in gpg, sowie einige Sicherheitslücken, die behoben wurden, und erfüllte mehrere nicht funktionale Anforderungen.
Drittens hätte Thunderbird OpenPGP überhaupt nicht mehr verwenden können. Diese Entscheidung passt nicht zu mir. Bei mehreren Gelegenheiten war ich jedoch besorgt über die Sicherheit der am stärksten gefährdeten Benutzer von Thunderbird, und ich glaube, dass es vielleicht sogar sicherer ist, überhaupt keinen OpenPGP-Support bereitzustellen als den Status Quo.
Macleod VPS sind ideal für die API-Entwicklung.
Registrieren Sie sich über den obigen Link oder indem Sie auf das Banner klicken und erhalten Sie 10% Rabatt für den ersten Monat der Anmietung eines Servers einer beliebigen Konfiguration!