{
"$type": "site.standard.document",
"bskyPostRef": {
"cid": "bafyreiacafd576f7frvhsabdns5t4panj4gz64g3zzseztgcyw46qcpjoq",
"uri": "at://did:plc:nkxz2ojdvmieg2nhphvputvp/app.bsky.feed.post/3mmo2uqzjybc2"
},
"coverImage": {
"$type": "blob",
"ref": {
"$link": "bafkreid3xrwosiuwjvy5fo3pu25k2c6pt7qumgobhfebypg7zoplgcutam"
},
"mimeType": "image/png",
"size": 757135
},
"description": "Kleines Update zur Mini-Saga rund um Post-Quantum-TLS auf Postfix. Zur Erinnerung: Im Original-Beitrag vom Februar stand tls_eecdh_auto_curves noch global in der main.cf, mit X25519MLKEM768 an erster Stelle. Im Nachtrag vom 1. April im selben Beitrag kam dann die Trennung in master.cf, weil die globale Variante einen ClientHello jenseits 1400 Bytes erzeugt und damit gegen manche Zielserver auf die Nase faellt. Dank smtp_tls_security_level = may faellt Postfix in dem Fall stillschweigend auf […]",
"path": "/2026/05/01/postfix-3-11-1-openssl-3-5-post-quantum-tls-built-in-default/",
"publishedAt": "2026-05-25T08:31:26.000Z",
"site": "https://www.kernel-error.de",
"tags": [
"Original-Beitrag vom Februar",
"Post-Quantum TLS fuer E-Mail (Original-Beitrag mit April-Nachtrag)",
"Post-Quantum TLS fuer Nginx",
"Post-Quantum TLS auf Nginx: 15 Tage $ssl_curve ausgewertet",
"Mailinglisten-Thread auf postfix-users",
"fragen"
],
"textContent": "Kleines Update zur Mini-Saga rund um Post-Quantum-TLS auf Postfix. Zur Erinnerung: Im Original-Beitrag vom Februar stand `tls_eecdh_auto_curves` noch global in der main.cf, mit X25519MLKEM768 an erster Stelle. Im Nachtrag vom 1. April im selben Beitrag kam dann die Trennung in master.cf, weil die globale Variante einen ClientHello jenseits 1400 Bytes erzeugt und damit gegen manche Zielserver auf die Nase faellt. Dank `smtp_tls_security_level = may` faellt Postfix in dem Fall stillschweigend auf Plaintext zurueck, eure Mail geht raus, aber unverschluesselt. Klingt akademisch, ist es nicht.\n\nMit dem letzten regulaeren `pkg upgrade` auf FreeBSD 15.0-RELEASE-p7 ist Postfix 3.11.1 eingezogen. Damit erledigen sich beide Workarounds. Der Built-in-Default ist jetzt korrekt, und der ganze master.cf-Override-Block aus dem April darf ersatzlos raus. Wer beide Vorgaengerbeitraege nachgebaut hat, darf jetzt rueckwaerts wieder aufraeumen — mit zwei `postfix reload` ist man durch.\n\n### Was Postfix 3.11.1 mitbringt\n\nPostfix 3.11.1 setzt den Built-in-Default fuer `tls_eecdh_auto_curves` auf:\n\n\n tls_eecdh_auto_curves = ?X25519MLKEM768:DEFAULT\n\nDas Fragezeichen vor dem Gruppennamen ist OpenSSL-3.5+ Syntax fuer _Delayed Key-Share_ , Postfix reicht das 1:1 durch. Bedeutung: X25519MLKEM768 wird in der TLS-Extension `supported_groups` annonciert, der eigentliche KeyShare aber NICHT vorab im ClientHello generiert. Der KeyShare wird erst materialisiert, wenn der Server per HelloRetryRequest gezielt danach fragt. ClientHello bleibt damit klein, MLKEM wird trotzdem ausgehandelt sobald die Gegenstelle es unterstuetzt — und faellt sauber auf eine klassische Kurve zurueck wenn nicht.\n\nDamit erledigt sich die Begruendung fuer die Client/Server-Trennung aus dem April-Update. Inbound und Outbound koennen denselben Default nutzen — kein Bloat in eine Richtung, kein Verzicht auf PQC in die andere.\n\n### master.cf aufraeumen\n\nAus master.cf werden alle `-o tls_eecdh_auto_curves=...` Overrides ersatzlos entfernt. Im konkreten Setup hier waren das fuenf Stellen: `smtp/inet`, `submission/inet`, der eigene `2525/inet`-Listener, `smtps/inet` und `smtp/unix` fuer Outbound. Diese Zeilen alle raus:\n\n\n smtp inet n - n - - smtpd\n -o tls_eecdh_auto_curves=X25519MLKEM768,X25519,prime256v1,secp384r1 # raus\n\n submission inet n - n - - smtpd\n -o tls_eecdh_auto_curves=X25519MLKEM768,X25519,prime256v1,secp384r1 # raus\n\n smtps inet n - n - - smtpd\n -o tls_eecdh_auto_curves=X25519MLKEM768,X25519,prime256v1,secp384r1 # raus\n\n smtp unix - - n - - smtp\n -o tls_eecdh_auto_curves=X25519,X25519MLKEM768,prime256v1,secp384r1 # raus\n\nIn der main.cf wird ebenfalls nichts mehr explizit gesetzt — der Built-in-Default greift. Verifikation:\n\n\n # postconf tls_eecdh_auto_curves\n tls_eecdh_auto_curves = ?X25519MLKEM768:DEFAULT\n\n # postconf -P | grep tls_eecdh_auto_curves\n (leer -- kein Override aktiv)\n\nAnschliessend `postfix reload`. Kein Restart, kein Service-Ausfall, keine offenen Verbindungen verloren. Dovecot bleibt unveraendert, dort sieht es weiter so aus:\n\n\n ssl_curve_list = X25519MLKEM768:X25519:prime256v1:secp384r1\n\nDovecot ist nur Server, kein Outbound-Client. Da gibt es kein ClientHello-Bloat-Problem.\n\n### Was DEFAULT konkret enthaelt\n\nHinter dem schlichten Wort `DEFAULT` versteckt OpenSSL 3.5.4 eine konkrete Liste. Ich habe sie aus dem `extension_type=supported_groups`-Block eines Test-Handshakes herausgezogen:\n\n\n X25519MLKEM768 (4588) # mit ?-Prefix vom davorgestellten Eintrag, dedupliziert\n X25519 (29)\n secp256r1 (23) # prime256v1\n X448 (30)\n secp384r1 (24)\n secp521r1 (25)\n ffdhe2048 (256)\n ffdhe3072 (257)\n\nDamit ist klar warum man den Default in der Regel ungeaendert laesst — er enthaelt MLKEM768 hybrid an erster Stelle, alle gaengigen klassischen Kurven dahinter und ein paar FFDHE-Gruppen als Backup. Wer es minimaler will und auf FFDHE verzichten kann, setzt explizit `?X25519MLKEM768:X25519:prime256v1:secp384r1`. Notwendig ist das nicht.\n\n### Verifikation #1: ClientHello-Groessen mit openssl s_client\n\nUm das Delayed-Key-Share-Verhalten ohne Glaubensfrage zu pruefen, vier verschiedene Group-Listen gegen einen MLKEM-faehigen MX gefahren — Gmail tut sich da als Test-Target ganz gut. Die Schleife direkt zum Nachstellen:\n\n\n for groups in\n '?X25519MLKEM768:DEFAULT'\n 'X25519MLKEM768:X25519:prime256v1:secp384r1'\n 'X25519:X25519MLKEM768:prime256v1:secp384r1'\n 'X25519'; do\n echo \"=== groups=$groups ===\"\n openssl s_client -connect gmail-smtp-in.l.google.com:25 -starttls smtp\n -groups \"$groups\" -msg </dev/null 2>&1\n | grep 'Handshake.*ClientHello'\n done\n\nIn der `-msg`-Ausgabe steht pro Handshake-Record eine Zeile wie\n\n\n >>> TLS 1.3, Handshake [length 014e], ClientHello\n\nDie hex-Length ist die Groesse des TLS-Records. Tabellarisch:\n\nGroup-Liste| Initial ClientHello| Nach HRR\n---|---|---\n`?X25519MLKEM768:DEFAULT` (Postfix 3.11 Default)| 334 Bytes (0x014e)| 1518 Bytes (0x05ee)\n`X25519MLKEM768:X25519:prime256v1:secp384r1` (alt-Inbound)| 1510 Bytes (0x05e6)| —\n`X25519:X25519MLKEM768:prime256v1:secp384r1` (alt-Outbound)| 326 Bytes (0x0146)| 1510 Bytes (0x05e6)\n`X25519` (klassisch ohne PQC)| 320 Bytes (0x0140)| —\n\nInterpretation: Der neue Default in Zeile 1 verhaelt sich praktisch identisch zum alten Outbound-Workaround in Zeile 3 — kleines Initial-ClientHello mit nur klassischen KeyShares, MLKEM wird erst ueber HelloRetryRequest aktiviert. Der alte Inbound-Style in Zeile 2 hingegen pusht den rund 1184 Byte grossen ML-KEM-768-PublicKey schon im ersten ClientHello mit und sprengt damit die 1400-Byte-Schwelle, an der einige Mail-Frontends regelmaessig stoppen. Genau das Verhalten, das im Original-Beitrag und im April-Update als Problem identifiziert wurde — jetzt nativ vermieden.\n\n### Verifikation #2: tcpdump auf dem Wire\n\nWer dem OpenSSL-Output nicht glaubt, kann sich das ClientHello auch direkt vom Draht holen. Damit der Capture funktioniert, sind zwei Stolpersteine zu beachten:\n\n * **tcpdump muss auf dem Host laufen, nicht in der Jail.** In FreeBSD-Jails ist der direkte BPF-Zugriff aufs Interface defaultmaessig abgeschaltet (`Packet capture is not supported on that device`). tcpdump auf der Host-Seite sieht den Jail-Verkehr aber sauber ueber das geteilte Interface.\n * **openssl s_client greift per Default zu IPv6** , wenn der Zielhost AAAA-Records hat. Ein reiner IPv4-Filter bleibt dann leer (`0 packets captured / 689 packets received by filter` — ich gestehe, der Moment war kurz verwirrend). Loesung: openssl mit `-4` zwingen oder den Filter dual-stack auslegen.\n\n\n\nKonkret im Test gelaufen (smtp-Jail mit IPv4 148.251.30.205, IPv6 2a01:4f8:262:4716::25, Interface igb0):\n\n\n tcpdump -i igb0 -n -s 0 -c 8 -w /tmp/ch.pcap\n 'dst port 25 and (host 148.251.30.205 or host 2a01:4f8:262:4716::25)' &\n\n jexec smtp openssl s_client -4\n -connect gmail-smtp-in.l.google.com:25 -starttls smtp\n -groups '?X25519MLKEM768:DEFAULT' </dev/null\n\nAnschliessend pcap auslesen:\n\n\n tcpdump -nn -tttt -r /tmp/ch.pcap # Paketsequenz\n tcpdump -nn -r /tmp/ch.pcap -X # Hex-Dump fuer TLS-Record-Inspektion\n\nGekuerzte Sequenz:\n\n\n 18:35:59.443 IP 148.251.30.205.10420 > 142.251.127.27.25: Flags [S], length 0\n 18:35:59.448 IP Flags [.], length 0\n 18:35:59.525 IP Flags [P.], length 23: SMTP: EHLO ...\n 18:35:59.541 IP Flags [P.], length 10: SMTP: STARTTLS\n 18:35:59.554 IP Flags [P.], length 339: SMTP\n\nDas letzte Paket mit 339 Byte Payload enthaelt das ClientHello. Hex-Auszug aus `tcpdump -X`:\n\n\n 0x0030: ...1603 0101 4e 01 0001 4a 03 0354\n 0x0040: 045a 83dd 0481 ee15 c537 75f2 6b38 f360\n ...\n\nAufgeschluesselt:\n\n\n 0x16 -- TLS Record Type Handshake\n 0x0301 -- TLS Version (legacy in TLS 1.3 ClientHello)\n 0x014e -- TLS Record Length 334 Bytes\n 0x01 -- Handshake Type ClientHello\n 0x00014a -- ClientHello-Body 330 Bytes\n\n339 Byte TCP-Payload teilen sich auf in 5 Byte TLS-Record-Header und 334 Byte Body. Das passt locker in ein einziges TCP-Segment, weit unter der 1400-Byte-Schwelle. Damit ist das `?`-Praefix-Verhalten auch auf dem Wire bestaetigt.\n\n### Verifikation #3: eigene Inbound-Seite von extern\n\nZum Abschluss von einem zweiten FreeBSD-Host aus — also nicht aus der smtp-Jail selbst, sondern als komplett externer Client — gegen alle relevanten Ports getestet:\n\n\n for entry in 25:smtp 465: 587:smtp 993: 4190:sieve; do\n port=${entry%:*}; starttls=${entry#*:}\n host=smtp.kernel-error.de\n case $port in 993|4190) host=imap.kernel-error.de ;; esac\n if [ -z \"$starttls\" ]; then args=\"-connect $host:$port\"\n else args=\"-connect $host:$port -starttls $starttls\"; fi\n echo \"--- Port $port ---\"\n echo QUIT | openssl s_client $args -brief 2>&1 | grep -E 'Negotiated|Verification:'\n done\n\nErgebnis fuer jeden der fuenf Ports identisch:\n\n\n Verification: OK\n Negotiated TLS1.3 group: X25519MLKEM768\n\nPostfix-Inbound auf 25/465/587 und Dovecot auf 993/4190 verhandeln durchweg X25519MLKEM768. Der Default greift fuer `smtpd` genauso wie fuer `smtp` outbound, ohne dass irgendwo noch eine Trennung haendisch erzwungen werden muss.\n\n### Fazit\n\nMit Postfix 3.11 und OpenSSL 3.5 ist sowohl die main.cf-globale Variante aus dem Original-Beitrag als auch die master.cf-Trennung aus dem April-Update Geschichte. Der Built-in-Default `?X25519MLKEM768:DEFAULT` liefert genau das Verhalten, das ich vorher manuell aufgebaut hatte: kleiner ClientHello outbound, MLKEM via HelloRetryRequest, klassischer Fallback wo noetig.\n\nKonkret: wer den Original-Beitrag oder den April-Update nachgebaut hat, kann nach dem pkg-Upgrade auf Postfix 3.11.x die `tls_eecdh_auto_curves`-Eintraege ersatzlos rausnehmen — aus main.cf und/oder master.cf — und mit `postfix reload` aktivieren. `openssl s_client -msg` und ein kurzer tcpdump bestaetigen anschliessend, dass das initiale ClientHello tatsaechlich klein bleibt und MLKEM ueber HRR aushandelt.\n\nDrei Iterationen, ein Default, der am Ende einfach passt. So darf das gerne haeufiger laufen.\n\n**Siehe auch:** Post-Quantum TLS fuer E-Mail (Original-Beitrag mit April-Nachtrag), Post-Quantum TLS fuer Nginx, Post-Quantum TLS auf Nginx: 15 Tage $ssl_curve ausgewertet und der Mailinglisten-Thread auf postfix-users, der die ganze Saga ueberhaupt erst angestossen hat.\n\nWie immer: bei Fragen, fragen.",
"title": "Postfix 3.11.1 mit OpenSSL 3.5: Post-Quantum-TLS jetzt nativ — meine alten Workarounds duerfen raus"
}