33. pepsi-stage-encrypt

Signer le courrier sortant au nom de son auteur, et le chiffrer pour chaque destinataire.

33.1. Rôle

pepsi-stage-encrypt est la moitié sortante de la cryptographie de bout en bout de Pepsi. Pour chaque message soumis localement, elle résout quelle protection a été demandée, l’applique — en signant avec la clé de l’auteur du From:, puis en chiffrant vers celle du destinataire — et achemine chaque destinataire plus loin. Elle ne fait aucune E/S réseau : un destinataire dont la clé n’est pas en cache met le message en pause, et un service pepsi-keydisc le libère. Référence : pepsi-stage-encrypt(1).

Elle écrit les deux mêmes normes que pepsi-stage-decrypt lit — la RFC 9580 avec les conteneurs PGP/MIME de la RFC 3156 pour OpenPGP, et le CMS de la RFC 5652 avec le profil de la RFC 8551 pour S/MIME — mais le côté écriture a deux choix que le côté lecture n’a pas, et tous deux sont visibles dans la configuration :

  • Vers quel conteneur chiffrer. Pour OpenPGP c’est négociable : un certificat déclare quelles versions de SEIPD son propriétaire sait lire, de sorte que CRYPTO_ALLOW_DOWNGRADE = negotiate émet du SEIPDv2 RFC 9580 (AEAD) vers un correspondant dont la clé dit qu’il sait le lire, et du SEIPDv1+MDC vers un correspondant dont la clé dit le contraire. Pour S/MIME il n’y a rien à négocier — un certificat X.509 n’annonce aucune capacité de chiffrement de contenu — aussi le code émet-il par défaut de l”AuthEnvelopedData RFC 5083/5084 (AES-256-GCM), et seul un réglage global le rabaisse à l’AES-CBC de la RFC 3565. Une installation par paquet le règle bel et bien, dans ${DATADIR}/config.d/thunderbird.conf, parce que Thunderbird ne sait pas lire l”AuthEnvelopedData ; supprimer ce fichier restaure l’AEAD. Interopérabilité des clients consigne quels clients sont connus pour lire quoi.

  • Quelle recipient info construire, ce qui n’est pas un choix : cela découle du type de clé dans le certificat du destinataire. RSA reçoit un KeyTransRecipientInfo de la RFC 5652 §6.2.1 (RSAES-OAEP de la RFC 4055, ou PKCS#1 v1.5 là où c’est exigé), un certificat EC un KeyAgreeRecipientInfo de la RFC 5753 avec la clé de contenu enveloppée selon la RFC 3394.

L’OpenPGP en ligne (non MIME) n’est jamais écrit ; tout ce qui est émis est du PGP/MIME.

Avertissement

Placez cette étape avant l’étape de signature DKIM. La chaîne sortante recommandée est submission → … → encrypt → srs → dkim-sign → relay. DKIM doit signer les octets réellement transmis ; dans l’autre sens on produit du courrier d’apparence valide dont la signature ne se vérifie pas, ce qui est pire que pas de signature du tout. pepsi-setup avertit lorsqu’il trouve les deux dans le mauvais ordre.

33.2. Fonctionnalités

  • Le signataire est l’auteur. La signature emploie la clé de l’adresse From:, jamais celle de l’expéditeur d’enveloppe : la signature porte sur l’auteur que voit le destinataire. Un message dont l’auteur n’est pas servi par ce déploiement, ou pour lequel il ne détient aucun matériel, part non signé au lieu de rebondir — c’est du courrier légitime malgré tout, et DKIM porte déjà la revendication au niveau du domaine.

  • Un texte chiffré par destinataire. Chaque destinataire chiffré obtient son propre texte chiffré sur sa propre ligne de file d’attente. Aucune divulgation de la liste des destinataires, aucune clé de contenu partagée, et une fuite de Bcc structurellement impossible. Lorsque les destinataires d’un message divergent, l’étape les scinde à raison d’un par ligne — state.dsn.rcpt découpé en parallèle — et chaque ligne réintègre seule cette étape. Les destinataires qui partagent tous une même issue ne sont jamais scindés.

  • Protocole par destinataire. OpenPGP (PGP/MIME, RFC 3156) ou S/MIME (CMS, RFC 8551), choisi par PREFER lorsqu’un correspondant dispose des deux. Un auteur qui signe avec un protocole et chiffre vers l’autre signe tout de même à l’intérieur du texte chiffré.

  • Négociation de conteneur par destinataire. Sous la valeur par défaut CRYPTO_ALLOW_DOWNGRADE = negotiate, OpenPGP ne retombe sur SEIPDv1+MDC que lorsque le certificat propre du destinataire prouve qu’il ne sait pas lire l’AEAD — un fait concernant le correspondant, journalisé et noté, jamais silencieux.

  • Une politique explicite pour « pas de clé ». ON_NO_KEY découle de ENCRYPT : opportunistic envoie du courrier ordinaire, required emploie le portail de lien sécurisé lorsqu’un tel portail est configuré, et fait rebondir le message sinon. required ne se dégrade jamais en clair.

  • Les en-têtes de demande n’atteignent jamais le fil. X-Pepsi-Sign et X-Pepsi-Encrypt (posés par le module complémentaire Outlook) sont retirés de chaque message sortant quoi qu’ils aient dit, et sont entièrement ignorés à moins que le message ne soit d’origine locale. Un mot-clé de sujet reconnu est retiré du Subject également.

  • Un plafond de taille visible. MAX_SIZE (25 Mo par défaut) borne ce qui est chiffré : ni le constructeur CMS ni les chemins OpenPGP ne travaillent en flux, de sorte que le message entier est résident une fois par destinataire. Au-delà du plafond, ON_OVERSIZE décide.

  • Le préréglage pEp. ENABLE_PEP (activé par défaut) est un préréglage de valeurs par défaut, et non un mode contraignant : il fait valoir SIGN par défaut encrypted-only (ne signer que ce qui est chiffré, à l’intérieur du texte chiffré) et crée une clé OpenPGP pour un expéditeur local d’un domaine servi à sa première soumission, lorsque l’adresse n’a aucune identité d’aucune sorte et que [pepsi-crypto] AUTO_CREATE_IDENTITY le permet. Avec ENABLE_PEP = no, une clé n’est créée que lorsqu’un auteur demande explicitement une protection (une signature ou un chiffrement seul) ou sous SIGN = always. Le login propriétaire de la clé est résolu au moyen des options de localité de [pepsi-crypto], comme pour pepsi-keys. Toute option écrite explicitement l’emporte toujours.

  • La propre clé de l’utilisateur. Un expéditeur local habilité à parler pour l’adresse From: peut enregistrer la clé que détient son client de messagerie (à partir de son en-tête Autocrypt:, ou d’une clé jointe pour la première clé de l’adresse) comme clé MUA (custody = client). Elle devient la face publique de l’adresse : le courrier local est chiffré vers elle, WKD la sert, et Pepsi n’ajoute alors ni en-tête Autocrypt: ni fichier de clé qui lui soit propre.

  • Le courrier que le client a déjà protégé est laissé tel quel. Une soumission dont la couche la plus externe est du texte chiffré n’est pas chiffrée, signée ni dotée d’une clé une seconde fois (state.crypto.out.client_protected) ; une soumission que le client a signée peut encore être chiffrée, mais ne reçoit aucune signature de Pepsi.

  • La clé sous forme de fichier. ATTACH_KEYS_AS_FILES (désactivé par défaut) joint aussi OpenPGP_0x<long key ID>.asc – à l’intérieur du texte chiffré lors du chiffrement – jusqu’à ce que chaque destinataire ait prouvé qu’il détient la clé.

  • Les clés par e-mail. Avec RESPONSE_STAGE défini, un message adressé à KEYS_CONTROL_LOCAL_PART@<served domain> (par défaut pepsi-keys@) est une commande de clé (status, generate, register, publish, retire) et reçoit une réponse au lieu d’être relayé.

  • Elle annonce la clé de l’auteur. AUTOCRYPT (par défaut activé) ajoute un en-tête Autocrypt: d’Autocrypt Level 1 portant la clé publique transférable minimale d’une identité OpenPGP dont ce déploiement détient encore la moitié privée — sur chaque message soumis localement qu’elle valide, y compris le passage en clair sans traitement, puisqu’un correspondant doit apprendre la clé avant qu’il n’y ait quoi que ce soit à chiffrer. Il n’est jamais ajouté par-dessus un en-tête posé par le MUA de l’auteur, ni lorsque la face publique est une clé MUA, et prefer-encrypt=mutual n’est revendiqué que lorsque le ENCRYPT configuré vaut required. Sur le chemin chiffrant, le champ va sur le bloc d’en-têtes extérieur.

  • Le gossip est sur consentement explicite. AUTOCRYPT_GOSSIP (par défaut désactivé) rend en outre un champ Autocrypt-Gossip: par autre destinataire à l’intérieur de la partie protégée, car cela redistribue le matériel de clé de tiers à des correspondants qui ne l’ont jamais demandé. Les adresses des sujets proviennent uniquement des To:/Cc: analysés, de sorte qu’un destinataire en Bcc ne peut jamais être gossipé ; OpenPGP seulement ; aucune découverte (une adresse non mise en cache est abandonnée, jamais mise en attente) ; et aucun gossip du tout au-delà de 50 destinataires visibles. GOSSIP_MIN_TRUST est son propre plancher (par défaut owner-confirmed), délibérément non hérité de MIN_TRUST : republier une clé n’est pas le même acte que d’en utiliser une.

33.3. Où elle se situe

submission → aliases → anti-spam → detect-language → encrypt → srs → dkim-sign → relay

Les deux voisines qui lisent le corps du message — la barrière de péage à l’envoi et la détection de langue — veulent du texte clair, et elles l’obtiennent parce que le chiffrement vient en dernier. ARC ne s’applique pas : il vise le courrier relayé pour le compte d’autrui, et cette étape ne touche jamais que du courrier émis par Pepsi.

Un message qui n’est pas d’origine locale est avancé sans être touché (ses en-têtes X-Pepsi-* retirés) : chiffrer un message tiers relayé invaliderait la signature DKIM et la chaîne ARC de l’auteur.

33.4. Configuration

[stage-<name>] : PROGRAM = pepsi-stage-encrypt, NEXT_STAGE (requis) et BOUNCE_STAGE, plus ENABLE_PEP, SIGN, ENCRYPT, ATTACH_KEYS_AS_FILES, KEYS_CONTROL_LOCAL_PART, RESPONSE_STAGE, PREFER, ON_NO_KEY, ON_OVERSIZE, MIN_TRUST, SUBJECT_KEYWORDS_SIGN / _ENCRYPT / _BOTH, STRIP_REQUEST_HEADERS, PROTECT_HEADERS, MAX_SIZE, AUTOCRYPT, AUTOCRYPT_GOSSIP, GOSSIP_MIN_TRUST, SECURE_LINK_STAGE, DISCOVERY et DISCOVERY_TIMEOUT. pepsi-setup exige un BOUNCE_STAGE dès que ON_NO_KEY/ON_OVERSIZE se résolvent en bounce, et vérifie que SECURE_LINK_STAGE et RESPONSE_STAGE nomment des étapes réelles. 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 sortant, sur l’expéditeur d’enveloppe). La politique cryptographique — quels algorithmes, si un conteneur rétrogradé peut être émis, les tailles de clé minimales — 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 ou depuis pepsi-stage-edit-settings.

33.5. 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é.

Les deux moitiés de la frontière de garde des clés reposent sur cette identité : le rôle de base de données pepsi-crypto est le seul à détenir le droit sur crypto_identity.private_wrapped, et l’authentification par pair de PostgreSQL se fonde sur l”uid effectif ; la clé de chiffrement de clés est un fragment 0640 appartenant au même compte. Un binaire setgid atteindrait le fragment et s’authentifierait tout de même auprès de la base en tant que pepsi — le rôle à qui la colonne est refusée. Voir Gestion des clés pour le modèle de garde.

33.6. État

Écrit state.crypto.out : si le message a été signé, par quelle identité, et une entrée par destinataire portant outcome, protocol, fingerprint, key_source, trust, content_algorithm et downgraded, plus key_attached et client_protected lorsqu’ils s’appliquent. Positionne state.encrypted = true lorsque quelque chose a été chiffré — la clé que pepsi-stage-auto-whitelist lit pour sa colonne signature_required. Sur un rebond de politique, elle écrit state.bounce avec un code d’état étendu 5.7.0.

Chaque issue en clair atteinte alors que le chiffrement était souhaité est journalisée en WARN : envoyer du courrier non protégé n’est jamais un événement silencieux.

33.7. Voir aussi

pepsi-stage-decrypt (la moitié entrante), pepsi-stage-secure-link (où mène la route secure-link en l’absence de clé), pepsi-stage-dkim-sign, pepsi-keys, pepsi-keydisc, Gestion des clés (identités, garde et échelle de confiance sur laquelle le MIN_TRUST de cette étape sélectionne), Le portail de repli par lien sécurisé, Interopérabilité des clients (quels clients ont été mesurés face à ce qu’émet cette étape), Index des RFC, pepsi.conf(5), pepsi.state(7)