34. pepsi-stage-decrypt

Déchiffrer le courrier adressé à nos utilisateurs, et dire ce que vaut sa signature.

34.1. Rôle

pepsi-stage-decrypt est la moitié entrante de la cryptographie de bout en bout de Pepsi. Pour un message adressé à un destinataire que cet hôte sert, elle ouvre tout texte chiffré que le message porte, vérifie toute signature qu’il porte contre une véritable chaîne de confiance, note le verdict, et retire tout indicateur de sécurité que l’expéditeur aurait falsifié. Elle utilise les certificats que porte le message mais n’en stocke aucun. Elle ne fait aucune E/S réseau : lorsque la clé du signataire est inconnue, le message est mis en pause et un service pepsi-keydisc le libère. Ce qu’elle ouvre est classé en clair, à moins que pepsi-stage-reencrypt ne le scelle à nouveau avec la propre clé du client de messagerie du destinataire juste avant la remise. Référence : pepsi-stage-decrypt(1).

Les deux protocoles qu’elle lit sont des normes :

  • OpenPGP — la RFC 9580 (la refonte cryptographique, remplaçant la RFC 4880) pour la grammaire des paquets et les algorithmes, la RFC 3156 pour les conteneurs PGP/MIME (multipart/encrypted, multipart/signed et le paramètre micalg). L’OpenPGP en ligne (non MIME) est lu également, mais jamais écrit.

  • S/MIME — la RFC 5652 pour la Cryptographic Message Syntax, la RFC 5083 avec la RFC 5084 pour l”AuthEnvelopedData (AES-GCM), la RFC 3565 pour l”EnvelopedData AES-CBC, la RFC 5753 pour l’accord de clés ECDH, et le RFC 8551 pour le profil de message avec la RFC 8550 pour le traitement des certificats. Les certificats eux-mêmes — construction du chemin, usage étendu de clé emailProtection et liaison rfc822Name qui rattache un certificat à une adresse — relèvent de la RFC 5280.

Deux limites sont énoncées ici pour qu’on ne les prenne pas pour des défauts. La révocation de certificat n’est jamais vérifiée : ni les CRL (RFC 5280 §5) ni OCSP (RFC 6960) ne sont récupérés, de sorte qu’un certificat révoqué est tout de même validé et que le statut de révocation rapporté est toujours « non vérifié ». Et un conteneur non authentifié n’est pas rapporté comme bon : l”EnvelopedData AES-CBC ne porte aucun MAC, il s’ouvre donc avec un verdict untrusted plutôt que good, tandis que l”AuthEnvelopedData AES-GCM — ce que Pepsi émet — est authentifié. Les deux sont listés dans Index des RFC.

Avertissement

Placez cette étape après l’étape ARC et avant les étapes de contenu. La chaîne entrante recommandée est init = arc → decrypt → aliases → (anti-spam / language / …) → local delivery. Un ensemble ARC scelle le message tel qu’il est arrivé ; déchiffrer d’abord ferait décrire à l”AMS scellé des octets que personne d’autre n’a jamais vus. pepsi-setup avertit lorsqu’il trouve une étape ARC atteignable depuis une étape de déchiffrement, et de nouveau lorsqu’aucune étape de remise locale n’est atteignable depuis l’une d’elles.

34.2. Fonctionnalités

  • Elle ne s’exécute que pour les destinataires que cet hôte sert, et n’essaie même pas pour les autres. Le déchiffrement réécrit le corps, ce qui invalide la signature DKIM de l’expéditeur — sans conséquence pour un message sur le point d’être classé dans une boîte aux lettres ici, mais non pour un message relayé plus loin. Un message comportant les deux sortes de destinataires est scindé, de sorte que la copie redirigée soit celle qui est arrivée.

  • Un verdict qui distingue ce qui compte. valid signifie que la signature est saine et que la clé était ancrée ; valid-untrusted signifie saine avec une clé qui n’a pu être rattachée à rien (une clé vue pour la première fois, une CA sans ancre ici) — ce qui est la majeure partie du courrier signé du monde, et qui n’est jamais rapporté comme valid. invalid, unverifiable et none complètent l’ensemble, et un message portant plusieurs signatures prend le pire de celles qui couvrent le texte en clair ; une signature enveloppant du texte chiffré ne peut jamais rendre un message valid.

  • Les deux chemins d’échec remettent par défaut. ON_DECRYPT_FAILURE = passthrough — l’utilisateur détient peut-être la clé dans son propre client, et faire rebondir du courrier que nous n’avons simplement pas pu ouvrir n’aide personne. ON_BAD_SIGNATURE = record — les listes de diffusion qui réécrivent les corps, les redirecteurs et les clients à la canonicalisation cassée produisent tous de mauvaises signatures sur du courrier légitime. quarantine et bounce sont disponibles pour les deux.

  • Les indicateurs falsifiés sont retirés, sans condition. Chaque en-tête X-Pepsi-* disparaît de chaque message entrant, et les propres marques de sujet de cette étape disparaissent du début du Subject, avant que les nôtres ne soient ajoutées. X-Pepsi-Crypto est un en-tête privé et donc falsifiable par définition ; le retrait est toute la base sur laquelle un client peut y croire. (Pepsi-Origin n’est délibérément pas dans cet espace de noms et survit.)

  • Un signal que l’utilisateur voit réellement. [decrypted][verified] ajouté en tête du Subject dans l’ordre d’imbrication — pour la plupart des utilisateurs le seul signal qu’ils verront jamais, puisque personne lisant son courrier dans un client webmail n’inspecte les en-têtes. Il peut être désactivé, et la formulation est configurable.

  • Les certificats que le message transporte. Les certificats de signataire S/MIME et les parties application/pgp-keys sont lus dans le message et proposés au vérificateur — avant la décision d’attendre une clé, parce que la plupart du courrier signé porte le certificat qui l’a signé. Ils sont utilisés puis abandonnés : stocker ce qu’un message entrant enseigne relève de pepsi-stage-autocrypt-learn, qui s’exécute plus tard, après les étapes qui décident si le message est du spam. Un tel certificat ne peut qu’abaisser la valeur d’un verdict, jamais l’élever — une signature vérifiée contre lui est valid-untrusted et ne positionne jamais state.signature_verified.

  • Deux faits notés pour l’étape d’apprentissage. Le déchiffrement écrit le texte clair par-dessus les octets arrivés : state.crypto.in porte donc encrypted (il y avait du texte chiffré) et outer_gossip (le bloc d’en-têtes à l’arrivée portait déjà un champ Autocrypt-Gossip:, que n’importe quoi sur le chemin a pu écrire). Les deux sont des préconditions pour croire le gossip d’un message, et ni l’un ni l’autre ne peut plus être établi une fois le texte clair dans la ligne.

  • Le courrier destiné à la propre clé de l’utilisateur est laissé à son client. Un message chiffré vers la clé MUA enregistrée d’un destinataire (custody = client, une clé dont la moitié privée ne réside que dans le client de messagerie) est reconnu d’après les identifiants de destinataires du texte chiffré sans être ouvert, et transmis sans être ouvert – même lorsque l’une de nos propres clés pourrait l’ouvrir aussi. Il est noté decryption = for-client, n’est pas un échec de déchiffrement, et ne déclenche donc jamais ON_DECRYPT_FAILURE.

  • Chaque clé non révoquée est essayée, y compris les clés expirées, sélectionnée par capacité (purpose encrypt ou both) plutôt que par forme. L’ancien courrier doit rester lisible : c’est pourquoi une clé de déchiffrement survit à sa propre expiration.

34.3. Où elle se situe

ingress → arc → decrypt → aliases → check-whitelist → anti-spam →
detect-language → local delivery

Tout ce qui lit le corps veut du texte clair, et l’obtient parce que le déchiffrement vient tôt. Tout ce qui authentifie le message tel qu’il est arrivé — ARC — doit venir avant.

Le placement est aussi une question de performance. L’étape déclare Load::Full, de sorte que chaque message qui la traverse charge tout son corps depuis la base de données, même un message qui s’avère être du courrier ordinaire : le fait qu’un message soit protégé ne se voit pas dans les colonnes d’enveloppe, et Load est fixé par étape. C’est abordable sur un chemin entrant se terminant par une remise locale, et ce n’est pas une étape à placer devant du trafic de relais en masse.

34.4. Attendre une clé

Lorsque la clé du signataire n’est ni dans le message ni dans le cache peer_key, le message est garé : une demande de découverte est mise en file et un service pepsi-keydisc libère le message lorsqu’elle se conclut, clé trouvée ou non. Ainsi le premier message d’un nouveau correspondant obtient un verdict réel plutôt qu”unverifiable. Une entrée fraîche de cache négatif court-circuite entièrement l’attente, de sorte que seul ce premier message attend jamais.

Le fait qu’un message garé soit stocké déchiffré dépend de la forme du message, et la différence n’est pas cosmétique. Une signature S/MIME imbriquée dans une enveloppe survit au déchiffrement comme une couche MIME ordinaire : le message est donc garé avec le texte clair écrit et déchiffré exactement une fois. Une signature OpenPGP ne vit qu’à l’intérieur du flux de paquets et disparaît dès que le texte clair en est sorti : écrire le texte clair détruirait précisément la preuve pour la vérification de laquelle la clé est récupérée ; un tel message est garé inchangé et déchiffré à nouveau à la reprise.

34.5. Configuration

[stage-<name>] : PROGRAM = pepsi-stage-decrypt, NEXT_STAGE (requis) et BOUNCE_STAGE, plus ENABLED, LOCAL_DOMAINS / TARGETS / RECIPIENT_DELIMITER / REQUIRE_ACCOUNT, ON_DECRYPT_FAILURE, ON_BAD_SIGNATURE, QUARANTINE_STAGE, SUBJECT_TAGS, TAG_DECRYPTED, TAG_VERIFIED, TAG_BAD_SIGNATURE, STRIP_SUBJECT_TAGS, STRIP_SUBJECT_KEYWORDS, ADD_RESULT_HEADER, MAX_LAYERS, TRUST_ANCHOR_TTL, DISCOVERY et DISCOVERY_TIMEOUT. Toutes sont documentées dans pepsi.conf(5).

Ce sont des options de comportement, redéfinissables par adresse via la table pepsi.settings (indexée, pour le courrier entrant, sur un destinataire d’enveloppe). La politique cryptographique réside dans les options CRYPTO_* de [pepsi] et dans [pepsi-crypto], relève du seul administrateur système, et est délibérément inatteignable depuis pepsi.settings.

L’une d’elles ne s’applique pas dans ce sens : CRYPTO_ALLOW_DOWNGRADE régit ce qui peut être émis. SEIPDv1+MDC et l”EnvelopedData AES-CBC sont toujours lisibles, car les refuser à l’entrée rendrait cet hôte incapable de recevoir du courrier de la majeure partie du monde sans protéger personne.

34.6. Privilège

Installée setuid pepsi-crypto, mode 4750 pepsi-crypto:pepsi, et donc autonome plutôt que pliée dans le binaire pepsi unifié — le même arrangement que pepsi-stage-encrypt, et pour les deux mêmes raisons : le rôle de base de données pepsi-crypto est le seul à qui crypto_identity.private_wrapped est accordé et l’authentification par pair de PostgreSQL se fonde sur l”uid effectif, et la clé de chiffrement de clés est un fragment 0640 appartenant au même compte. Voir Gestion des clés pour le modèle de garde.

34.7. État

Écrit state.crypto.in : si le message est arrivé chiffré, ce qu’est devenu le texte chiffré (not-encrypted, decrypted, failed ou for-client), quelle identité l’a ouvert, le verdict de signature avec son signataire, son empreinte, sa source de clé, son rang de confiance et covers_plaintext, et la liste ordonnée des couches. Positionne state.encrypted = true pour un message arrivé chiffré et state.signature_verified = true pour le seul verdict valid — les deux clés que lisent pepsi-stage-check-whitelist et pepsi-stage-auto-whitelist. Sur un rebond de politique, elle écrit state.bounce avec un code d’état étendu 5.7.0.

34.8. Voir aussi

pepsi-stage-encrypt (la moitié sortante), pepsi-stage-arc, pepsi-keys, pepsi-keydisc (le service qui règle les mises en garage que crée cette étape), pepsi-stage-check-whitelist (qui conditionne une ligne de liste blanche au state.signature_verified que positionne cette étape), Gestion des clés, Interopérabilité des clients, Index des RFC, pepsi.conf(5), pepsi.state(7)