85.1.8. pepsi-stage-dkim-sign¶
DKIM-sign a message in the Pepsi pipeline
- Handbuchabschnitt:
1
85.1.8.1.1. Name¶
pepsi-stage-dkim-sign - DKIM-Signier-Stage der Pepsi-Pipeline.
85.1.8.1.2. Übersicht¶
pepsi-stage-dkim-sign [GLOBAL-OPTIONS] worker
85.1.8.1.3. Beschreibung¶
pepsi-stage-dkim-sign ist ein Stage-Programm, das von pepsi-dispatch(1) als persistenter Worker ausgeführt wird, der Nachrichten-IDs von der Standardeingabe liest. Es stellt den Rohbytes der Nachricht einen DKIM-Signature-Header je Algorithmus in [pepsi] DKIM_ALGORITHMS voran (standardmäßig zwei: einen RSA, einen Ed25519) und rückt den Datensatz zu seiner NEXT_STAGE (einer Zustell-Stage) vor. Signieren stellt nur Header-Zeilen voran, sodass bestehende Signaturen weiter unten in der Nachricht ungestört bleiben. Es fasst den state des Datensatzes nicht an, sodass alle RFC-3461-Parameter in state.dsn für spätere Stages zur Beachtung erhalten bleiben.
Die Signierdomain (d=) ist SIGNING_DOMAIN, falls konfiguriert, sonst die Domain des From:-Headers der Nachricht. Die Schlüssel sind die domainweisen DKIM-Schlüssel unter dem gemeinsamen [pepsi] KEY_DIR (von pepsi-setup(1) eingerichtet), unter Verwendung der Selektoren aus dem [pepsi]-Abschnitt.
Diese Stage existiert, damit der Bau der Nachricht und das DKIM-Signieren getrennte Schritte sind. Insbesondere baut pepsi-stage-bounce(1) eine unsignierte DSN und richtet ihr NEXT_STAGE hierher, sodass ein erzeugter Bounce vor der Zustellung als die Domain seines Postmasters signiert wird.
Das Signieren ist fail-closed: Eine Nachricht wird nie unsigniert weitergeschaltet. Was stattdessen geschieht, hängt davon ab, wessen Schuld es ist:
Es lässt sich keine Signierdomain bestimmen (kein
SIGNING_DOMAINund kein verwendbarerFrom:-Header), oder diese Installation hat für die Domain kein Schlüsselverzeichnis unter[pepsi] KEY_DIR: Die Nachricht hatdkim-signfür eine Domain erreicht, für die hier niemand signiert. Mit einer BOUNCE_STAGE wird sie dorthin umgeleitet, mit einem DSN-Grund instate.bounce(Status5.7.1für eine Domain ohne Schlüssel,5.6.0für eine Nachricht ohne verwendbarenFrom:), zuvor in einen Datensatz je Empfänger aufgeteilt, sodass dasNOTIFYjedes Empfängers beachtet wird; die Diagnose nennt die Domain, nie das Schlüsselverzeichnis. Ohne eine solche wird sie in den terminalen Zustandfailedverschoben, mit dem Grund instate.last_error. Eine Null-Absender-Nachricht (selbst eine DSN oder eine automatische Antwort) wird immer fehlschlagen gelassen statt umgeleitet: Ein Bounce wird nie gebounct, und als fehlgeschlagene bleibt sie für den Betreiber sichtbar.Das Schlüsselverzeichnis existiert, aber ein Schlüssel kann nicht gelesen oder verwendet werden — eine halb abgeschlossene Rotation, eine geänderte Berechtigung, eine defekte Schlüsseldatei: Schuld ist der Host, nicht die Nachricht. Der Fehler wird erneut versucht (siehe pepsi-dispatch(1)): Die Nachricht bleibt an dieser Stage
paused, mit dem Grund instate.last_error, und wird erneut versucht, nach einer Minute und dann in sich verdoppelnden Abständen bis zu einer Stunde, bis sie länger in der Warteschlange steht als das MAX_LIFETIME der Stage (Standardwert120 h); erst dann wird sie aufgegeben. Sie sofort fehlschlagen zu lassen, würde jede ausgehende Nachricht bouncen, und die Bounces, die ebenfalls hier signiert werden, würden dann verworfen, ohne dass jemand davon erfährt.
Der eigene Abschnitt der Stage und [pepsi] werden beim Start des Workers geprüft: Eine Konfiguration, die sich nicht parsen lässt, lässt den Worker den Start verweigern (Exit 78), und der Dispatcher hält die Warteschlange zurück, bis sie korrigiert ist.
85.1.8.1.3.1. Abdeckung des Nachrichtentexts¶
Die Signatur deckt immer den gesamten Nachrichtentext strikt ab: jede Änderung des Nachrichtentexts während der Übertragung macht sie ungültig. Pepsi gibt das Tag für die Nachrichtentextlänge (l=) aus RFC 6376 nie aus, und es gibt keine Option, das zu tun.
Das Tag existiert, damit eine Signatur eine Mailingliste überlebt, die eine Fußzeile anhängt, doch kein Operator kann diesen Tausch sinnvoll eingehen. Der strikte Parser von mail-auth — den Pepsis eigener Verifizierer verwendet und den jede mail-auth-/Stalwart-Installation verwendet — weist jede Signatur mit l > 0 rundheraus zurück, statt bloß eine geringere Abdeckung zu dulden; das Tag macht damit aus einer Signatur, die bestanden hätte, eine, die fehlschlägt. Der Fall, für den es existiert, ist zugleich der Fall, in dem das Umschreiben durch die Liste SPF bereits gebrochen hat, sodass unter einer DMARC-Richtlinie p=reject dieser DKIM-Fehler den Unterschied zwischen zugestellt und abgewiesen ausmacht. RFC 6376 §8.2 warnt gesondert, dass angehängter Inhalt das Original in den Augen des Lesers durch MIME-Umstrukturierung oder laxes HTML-Parsen ersetzen kann und dass „Signierer bei der Verwendung dieses Tags äußerst vorsichtig sein sollten“.
85.1.8.1.3.2. Abdeckung der Header¶
Die von der Signatur abgedeckten Header (das h=-Tag) sind die Liste SIGNED_HEADERS (Standardwert: die üblichen Originator-/MIME-Header). From und Subject sind doppelt aufgeführt, sodass sie über-signiert werden (RFC 6376 §8.15): ein Header, der einmal öfter genannt wird, als er tatsächlich vorkommt, wird ein zusätzliches (leeres) Mal signiert, sodass ein Angreifer, der ein zweites From: oder Subject: voranstellt — die Header, die den scheinbaren Ursprung oder die Bedeutung einer Nachricht ändern — ein Vorkommen erzeugt, das die Signatur nicht abdeckt, was die Verifikation bricht. Wird in SIGNED_HEADERS ein Header aufgeführt, der in der Nachricht fehlt, wird er ebenso über-signiert (er kann später nicht hinzugefügt werden).
85.1.8.1.4. Konfiguration¶
Die Stage wird über ihren eigenen [stage-<name>]-Abschnitt eingebunden und konfiguriert (PROGRAM = pepsi-stage-dkim-sign, eine erforderliche NEXT_STAGE). Ihre Optionen — HEADER_CANONICALIZATION/BODY_CANONICALIZATION (unabhängig voneinander gewählt), SIGNATURE_EXPIRATION_DAYS, SIGNED_HEADERS und SIGNING_DOMAIN — sind zusammen mit dem gemeinsamen [pepsi]-Schlüsselmaterial (KEY_DIR, DKIM_SELECTOR, DKIM_ALGORITHMS) in pepsi.conf(5) dokumentiert. Der Hash ist immer SHA-256; standardmäßig werden sowohl eine RSA- als auch eine Ed25519-Signatur ausgegeben.
85.1.8.1.5. State¶
Eingaben: keine aus state — die Signierdomain stammt aus SIGNING_DOMAIN oder der Spalte from_header und der Hash des Nachrichtentexts aus der geladenen Nachricht.
Ausgaben: keine zu state hinzugefügt. Die Signaturen werden der headers-Spalte vorangestellt; das gesamte state — einschließlich state.dsn — bleibt unverändert erhalten. Das State-Layout wird in pepsi.state(7) beschrieben.
Übergänge: schaltet bei Erfolg zu NEXT_STAGE (der Zustell-Stage) weiter, nachdem es den oder die DKIM-Signature-Header vorangestellt hat. Wenn die Signierdomain nicht bestimmt werden kann oder hier keine Schlüssel hat, wird die Nachricht zu BOUNCE_STAGE umgeleitet (ein Datensatz je Empfänger, state.bounce gesetzt) oder, ohne eine solche oder bei einer Null-Absender-Nachricht, in den terminalen Zustand failed verschoben; wenn ihre Schlüssel nicht verwendet werden können, wird sie bis MAX_LIFETIME zur Wiederholung pausiert (fail-closed; siehe oben). Die Stage schließt nie eine Nachricht ab.
85.1.8.1.6. Befehle¶
Als Worker ausgeführt, signiert das Programm jede Nachricht und schaltet sie weiter.
85.1.8.1.7. Globale Optionen¶
- -c FILE, –config FILE
Liest die Konfiguration aus FILE statt aus dem Standard-Suchpfad.
- -L LOGLEVEL, –log LOGLEVEL
Setzt die Log-Ausführlichkeit (
error,warn,info,debug,trace; Standardwertinfo).- -h, –help
Gibt eine Verwendungsübersicht aus und beendet sich.
- -V, –version
Gibt die Version aus und beendet sich.
85.1.8.1.8. Exit-Status¶
Das Ergebnis jeder Nachricht wird pepsi-dispatch(1) auf der Statuszeile des Workers gemeldet, nicht als Exit-Status.
- 0
Der Worker lief, bis seine Standardeingabe geschlossen wurde.
- 1
Ein fataler Fehler ist aufgetreten (unlesbare Konfiguration, die Datenbank konnte nicht geöffnet werden, oder Standardein-/-ausgabe schlug fehl). Der Grund wird in das Journal geschrieben.
- 78
Der Worker hat den Start verweigert: Sein Abschnitt oder
[pepsi]lässt sich nicht parsen. Der Dispatcher reiht erneut ein, was er übergeben hatte, und versucht die Stage später wieder.
85.1.8.1.9. Siehe auch¶
pepsi-config(1), pepsi-stage-bounce(1), pepsi-stage-relay-to-internet(1), pepsi-stage-relay-to-smarthost(1), pepsi-dispatch(1), pepsi-ingress(1), pepsi.conf(5), pepsi.state(7), pepsi-setup(1)
85.1.8.1.10. Fehler¶
Melden Sie Fehler an den Pepsi-Issue-Tracker.