Zum Inhalt springen

AXON · Technik im Detail

Ein Ja, genau einmal.

Wie der gebaute Prototyp ein Ja mit HMAC-SHA256 an eine Handlung bindet, es beim Einlösen verbraucht und jeden Eintrag an den Prüfwert des vorigen kettet.

Was über die Grenze einer Rolle geht, entscheidet eine berechtigte Person. Sein Ja ist eine signierte Zeichenfolge, die nur für eine Aktion gilt, die berechtigte Person nennt, abläuft und beim Einlösen verbraucht wird. Die Anfrage muss dieselbe Person nennen; ob sie es wirklich ist, prüft der Prototyp nicht; für den Betrieb ist die Bindung an die angemeldete Sitzung vorgesehen. Dieser Teil ist als Prototyp gebaut. Die Seite beschreibt ihn so, wie er im Quelltext steht, und nennt, was für den Betrieb dazukommt.

Diese Seite beschreibt die Bauweise des Systems. Gebaut sind der Prototyp von Freigabe und Protokoll und der Demonstrator von KALKÜL; den ganzen Stand finden Sie unter Was heute steht.

Auf dieser Seite
  1. 01Aufbau der Zeichenfolge
  2. 02Prüfung in fester Reihenfolge
  3. 03Verbraucht beim Einlösen
  4. 04Der Verlauf als Hash-Kette
  5. 05Grenze der Kette und Verankerung
  6. 06Was für den Betrieb dazukommt
  7. 07Eckdaten
  8. 08Weiter

Aufbau der Zeichenfolge

Das Ja hat drei Teile, durch Punkte getrennt: das Kennzeichen v1, die Mitte und die Signatur. Die Mitte ist ein JSON-Objekt, kodiert in base64url ohne Füllzeichen. Die Signatur ist HMAC-SHA256 über v1. und die Mitte, ebenfalls in base64url.

Die Mitte trägt drei Felder. p bindet das Ja an die Sache: die Kennung der Freigabe (id), die Nummer der Aktion darin (nr) und die Person, die entscheiden darf (wer). exp ist der Ablauf als Sekundenzahl der Unix-Zeit, im Prototyp standardmäßig zehn Minuten nach dem Ausstellen. jti ist ein Einmalwert, eine UUID der Version 4 aus crypto.getRandomValues. Der Schlüssel hat mindestens 32 Byte.

Die Rechnung von SHA-256, HMAC-SHA256 und base64url ist reines TypeScript ohne crypto.subtle, damit sie in jedem Browser läuft, auch ohne verschlüsselte Verbindung. Die Tests rechnen die Ergebnisse mit node:crypto gegen.

v1 . base64url( JSON { p: { id, nr, wer }, exp, jti } ) . base64url( HMAC-SHA256( k, "v1." + Mitte ) )
Das Ja als Zeichenfolge, wie es freigabe.ts prägt.

Das Ja im Schnitt

Unten die Zeichenfolge, darüber ihre Mitte. Beim Einlösen wird alles geprüft und der Einmalwert verbraucht; erst dann entsteht eine Zeile im Protokoll. Eine Kopie endet beim Einlösen.

Freigabe in AXON im Schnitt: Zeichenfolge, Mitte, Einlösen, ProtokollUnten die Zeichenfolge, darüber ihre Mitte. Beim Einlösen wird alles geprüft und der Einmalwert verbraucht; erst dann entsteht eine Zeile im Protokoll. Eine Kopie endet beim Einlösen.ProtokollPREV_HASH · HASHANGELEGTENTSCHIEDENFREIGABEENTSCHIEDENEinlösenIN FESTER FOLGESIGNATURABLAUFBINDUNGEINMALWERTMitteP = ID · NR · WERPEXPJTIZeichenfolgeBASE64URLV1MITTEHMAC-SHA256EINLÖSENKOPIE
  • Protokoll (prev_hash · hash): .
  • Einlösen (in fester Folge): Signatur, Ablauf, Bindung, Einmalwert.
  • Mitte (p = id · nr · wer): p, exp, jti.
  • Zeichenfolge (base64url): v1, Mitte, HMAC-SHA256.
  • einlösen: fließt von zeichen nach protokoll.
  • Kopie: gesperrt von zeichen nach protokoll.
SCHNITT VON VORN

Prüfung in fester Reihenfolge

Beim Einlösen prüft der Prototyp zuerst die Zeichenfolge selbst: ob überhaupt eine vorliegt, ob sie drei Teile mit v1 vorn hat, ob die Signatur stimmt, ob die Mitte lesbares JSON ist, ob exp und jti vorhanden sind und zuletzt, ob der Ablauf überschritten ist. Die Signatur wird Zeichen für Zeichen ohne frühen Abbruch verglichen, damit die Dauer des Vergleichs nicht von der Stelle der ersten Abweichung abhängt.

Dann die Entscheidung, in dieser Reihenfolge: genau eine Aktion je Entscheidung, keine Sammelfreigabe; eine gültige Wahl aus freigeben, ändern und ablehnen; ein Idempotenzschlüssel gegen den doppelten Klick; eine bekannte Freigabe, deren Frist noch läuft, sonst bleibt nur ablehnen; ein gültiges Ja; die Bindung, bei der Kennung, Aktionsnummer und Person im Ja genau der Anfrage entsprechen müssen; dann der Einmalwert.

Ein abgelaufenes Ja taugt nur noch zum Ablehnen. Jede Abweisung kommt mit Zeit, Kennung, Aktion, Person und Grund in eine Liste der Versuche; die Abweisung selbst schreibt keine Zeile in das Protokoll.

Verbraucht beim Einlösen

Der Einmalwert jti kommt in die Menge der verbrauchten Werte, bevor die Entscheidung wirkt. Wer dieselbe Zeichenfolge mit neuem Idempotenzschlüssel ein zweites Mal vorlegt, etwa als Kopie aus dem Verlauf des Browsers oder aus einer Nachricht, prallt mit dem Grund „schon benutzt“ ab. Mit demselben Schlüssel und demselben Inhalt (Kennung, Aktion, Wahl, Änderung) liefert der Prototyp vor jeder weiteren Prüfung das erste Ergebnis zurück. Eine zweite Wirkung entsteht in keinem Fall. Der Wert bleibt auch dann verbraucht, wenn die Entscheidung danach an einer fehlenden oder bereits entschiedenen Aktion scheitert.

Davon getrennt schützt der Idempotenzschlüssel vor dem doppelten Klick. Kommt dieselbe Anfrage mit demselben Schlüssel und demselben Inhalt noch einmal, liefert der Prototyp das erste Ergebnis zurück, ohne neu zu entscheiden. Derselbe Schlüssel mit anderem Inhalt ist ein Konflikt. Den Inhalt vergleicht er über SHA-256 seines stabilen JSON.

Der Verlauf als Hash-Kette

Jede Zeile des Protokolls hat die Felder seq, zeit, wer, ereignis, id, daten und prev_hash, den Prüfwert der Zeile davor; die erste Zeile beginnt bei 64 Nullen. Ihr eigener Prüfwert hash ist SHA-256 als Hex über prev_hash und das JSON der Zeile ohne hash, mit rekursiv alphabetisch sortierten Schlüsseln.

Die Prüfung rechnet die Kette von vorn nach. Stimmen der Bezug auf den Vorgänger und der neu gerechnete Prüfwert, geht sie weiter. An der ersten Zeile, die nicht passt, hält sie an und meldet deren Nummer. Wer eine Zeile ändert, ohne ihren Prüfwert neu zu rechnen, fällt an dieser Zeile auf; rechnet er ihn neu, an der nächsten. Löschen, Einfügen und Vertauschen in der Mitte fallen ebenso auf.

h₀ = 64 Nullen
hₙ = SHA-256( hₙ₋₁ ‖ JSON_sortiert( Zeileₙ ohne hash ) )

Prüfung: für n = 1, 2, …
  Zeileₙ.prev_hash = hₙ₋₁  und  Zeileₙ.hash = hₙ,  sonst Bruch bei n
Rechnung und Prüfung der Kette, wie protokoll.ts sie ausführt.

Grenze der Kette und Verankerung

Eine Kette, die vollständig neu gerechnet wird, ist in sich wieder stimmig. Der Prototyp zeigt das ausdrücklich: Er kann ab einer Zeile alle Prüfwerte neu rechnen, und die Prüfung meldet danach keinen Bruch. Zwei Dinge erkennt die Kette allein nicht: das vollständige Neuschreiben ab einer Zeile und das Abschneiden der letzten Zeilen. Gegen beides hilft nur ein Vergleichswert außerhalb: der verankerte letzte Prüfwert mit seiner Zeilennummer.

Dafür ist vorgesehen, den letzten Prüfwert in festen Abständen außerhalb zu verankern: bei einem Zeitstempeldienst nach RFC 3161, in einem schreibgeschützten Speicher unter fremder Verwaltung oder bei einer dritten Stelle. Wer prüft, vergleicht dann den Stand der Kette mit dem verankerten Wert, und ein neu geschriebener oder gekürzter Verlauf fällt auf. Was nach dem letzten Anker geschrieben wurde, deckt erst der nächste.

Was für den Betrieb dazukommt

Der Prototyp hält Freigaben, verbrauchte Einmalwerte und Protokoll im Speicher eines Browserfensters, und der Schlüssel entsteht für den Besuch. Für den Betrieb vorgesehen sind ein Schlüssel in einem Hardware-Sicherheitsmodul oder Schlüsseldienst mit Rotation, Prüfung und Verbrauch im Server mit einem eindeutigen Index auf dem Einmalwert, damit von zwei gleichzeitigen Einlösungen nur eine gelingt, ein Protokoll in einem Speicher, der nur Anhängen erlaubt, und die Bindung des Ja an die angemeldete Sitzung der Person.

Vorgesehen ist auch, das Ja zusätzlich an einen Hash der Parameter zu binden. Dann gilt es nicht nur für Aktion und Person, sondern für genau die Werte, die die Person gesehen hat. Im Prototyp binden Kennung, Aktionsnummer und Person; die Aktion selbst ist mit der Freigabe angelegt und liegt fest.

Eckdaten

Format
v1.Mitte.Signatur, base64url ohne Füllzeichen
Mitte
p mit id, nr, wer; exp in Unix-Sekunden; jti als UUID v4
Signatur
HMAC-SHA256 über v1. und Mitte, Schlüssel ab 32 Byte
Ablauf
im Prototyp standardmäßig zehn Minuten; abgelaufen nur noch ablehnen
Vergleich
Zeichen für Zeichen ohne frühen Abbruch
Einmalig
jti verbraucht vor der Wirkung; eine Kopie prallt ab
Doppelklick
Idempotenzschlüssel, Inhalt über SHA-256 des stabilen JSON verglichen
Protokoll
hash = SHA-256(prev_hash ‖ JSON sortiert), Start bei 64 Nullen, Prüfung bis zur ersten Bruchstelle
Für den Betrieb
vorgesehen: Verankerung nach RFC 3161, Schlüssel im HSM, Verbrauch im Server, Bindung an einen Hash der Parameter

Weiter

Welche Entscheidung trifft bei Ihnen nur eine berechtigte Person? An ihr zeigen wir die Freigabe im Prototyp.