13. Interopérabilité des clients

Le chiffrement de bout en bout ne vaut la peine que si l’autre bout peut le lire. Ce chapitre consigne ce qui a réellement été mesuré, contre quelles versions, et quelles cases n’ont jamais été exécutées.

Important

Une case de ces tableaux signifie l’une de trois choses, et elles ne sont pas interchangeables :

mesuré

Un test automatisé a exécuté l’outil tiers et vérifié l’issue. Le nom du test est donné, de sorte que l’affirmation puisse être rejouée.

observé

Un humain l’a exécuté une fois et a noté ce qui s’est passé. Aucun test ne le garde ; ce peut être déjà faux.

non exécuté

Personne n’a essayé. Ce n’est pas une affirmation d’échec, et ce n’est absolument pas une affirmation de succès.

Les lignes ne sont jamais remplies par inférence. Si la sortie S/MIME de Pepsi est lisible par OpenSSL, il n’en découle pas qu’Outlook la lise, et ce chapitre ne prétend pas le contraire.

13.1. Résumé

  • OpenPGP contre gpg 2.4.7 — mesuré, dans les deux sens, PGP/MIME et en ligne. Les six cases passent.

  • S/MIME contre OpenSSL 3.5.6 — mesuré, dans les deux sens, y compris AuthEnvelopedData (AES-GCM), EnvelopedData (AES-CBC), RSA-OAEP, RSA PKCS#1 v1.5, ECDH-KARI, ainsi que SignedData détachée et opaque.

  • S/MIME contre Thunderbird 140.12.0esr — mesuré, déchiffrement seulement, et le résultat est un négatif : Thunderbird lit notre EnvelopedData (AES-256-CBC) et ne peut pas du tout lire notre valeur par défaut compilée AuthEnvelopedData (AES-256-GCM) — silencieusement, en renvoyant un texte clair vide plutôt qu’une erreur. C’est pourquoi une installation empaquetée livre CRYPTO_ALLOW_DOWNGRADE = yes. La vérification de signature et la moitié OpenPGP restent non exécutées. Voir Thunderbird : la barrière, et comment tester un client graphique sans interface graphique.

  • Outlook, Apple Mail — non exécuté. Voir Outlook et Apple Mail : observations consignées, non des barrières.

La plus grande question ouverte — quels clients peuvent lire les AuthEnvelopedData S/MIME — a sa réponse la plus importante, et c’est non. Aussi CRYPTO_ALLOW_DOWNGRADE = yes est-il livré comme valeur par défaut empaquetée dans ${DATADIR}/config.d/thunderbird.conf alors que la valeur par défaut compilée est negotiate : une installation est interopérable dès la sortie de boîte, et un déploiement qui veut l’AEAD supprime un fichier. Outlook et Apple Mail restent non mesurés ; vu ce résultat, supposez qu’ils en ont besoin aussi jusqu’à ce que quelqu’un vérifie.

13.2. OpenPGP : gpg

Testé contre gpg (GnuPG) 2.4.7, libgcrypt 1.11.0, tel qu’empaqueté sur Debian 13. Chaque case est exécutée par cargo test -p pepsi-crypto ; les tests sont sautés proprement (ils n’échouent pas) sur une machine sans gpg.

Capacité

Statut

Test

Notes

gpg vérifie notre signature PGP/MIME

mesuré

pgp.rs::gpg_verifies_our_detached_signature

multipart/signed de la RFC 3156.

gpg déchiffre notre texte chiffré PGP/MIME

mesuré

interop.rs::gpg_decrypts_what_we_emit

Seulement après la négociation SEIPD ci-dessous.

Nous vérifions une signature PGP/MIME de gpg

mesuré

interop.rs::we_verify_a_gpg_signature

L’armure propre à gpg et son micalg.

Nous déchiffrons un texte chiffré PGP/MIME de gpg

mesuré

interop.rs::we_decrypt_what_gpg_emits

Nous vérifions un corps signé en clair par gpg

mesuré

interop.rs::we_verify_a_gpg_clearsigned_message

En ligne, non MIME.

Nous déchiffrons un texte chiffré en ligne de gpg

mesuré

interop.rs::we_decrypt_a_gpg_inline_message

En ligne, non MIME.

gpg lit notre sortie en ligne

sans objet

—

Pepsi lit l’OpenPGP en ligne mais ne l”écrit jamais ; tout ce qui est émis est du PGP/MIME. Il n’y a rien à donner à gpg.

13.2.1. La négociation SEIPDv2, confirmée contre le vrai gpg

CRYPTO_ALLOW_DOWNGRADE vaut negotiate par défaut pour une raison concrète :

INTEROP OBSERVATION: gpg (GnuPG) 2.4.7 CANNOT read our SEIPDv2/AEAD ciphertext

Cette ligne est imprimée par interop.rs::gpg_cannot_read_seipd_v2 à chaque exécution. gpg 2.4.x — c’est-à-dire l’essentiel du parc déployé — ne peut pas du tout ouvrir un message SEIPDv2 de la RFC 9580. Chiffrer inconditionnellement en AES-GCM produirait donc du courrier que la plupart des utilisateurs d’OpenPGP ne peuvent pas lire.

negotiate tranche cela à partir du certificat même du destinataire : un certificat OpenPGP déclare quelles versions de SEIPD son propriétaire sait lire, de sorte que Pepsi émet du SEIPDv2 vers un correspondant dont la clé dit qu’il peut le traiter, et du SEIPDv1+MDC vers un dont la clé dit qu’il ne le peut pas — et note lequel des deux a eu lieu sur la couche (LayerDetail::downgraded) au lieu de rétrograder silencieusement. C’est ce que gpg_decrypts_what_we_emit prouve de bout en bout : chiffrer vers un certificat SEIPDv1 seulement produit quelque chose que le gpg installé ouvre.

Notez l’asymétrie délibérément construite dans les deux tests. L’affirmation positive (« gpg ouvre ce que nous émettons après négociation ») est vérifiée, car c’est une affirmation sur notre comportement. L’affirmation négative (« gpg ne peut pas lire le SEIPDv2 ») est seulement imprimée, car c’est une affirmation sur la feuille de route de quelqu’un d’autre : un futur gpg qui gagnerait la prise en charge du SEIPDv2 ne doit pas faire virer cette suite au rouge.

13.3. S/MIME : OpenSSL

Testé contre OpenSSL 3.5.6 (7 avril 2026). Exécuté par cargo test -p pepsi-crypto ; sauté proprement lorsqu”openssl est absent.

Capacité

Statut

Test

Notes

openssl cms déchiffre notre texte chiffré

mesuré

smime.rs::openssl_decrypts_what_we_emit

AuthEnvelopedData AES-GCM avec RSA-OAEP et avec PKCS#1 v1.5 ; EnvelopedData AES-CBC ; ECDH-KARI.

openssl cms vérifie notre signature

mesuré

smime.rs::openssl_verifies_what_we_sign

Détachée et opaque.

openssl lit notre message ne contenant que des certificats

mesuré

smime.rs::openssl_reads_our_certs_only_message

Nous ouvrons ce qu”openssl émet maintenant

mesuré

smime.rs::we_open_what_openssl_emits_right_now

Exécute l’OpenSSL installé, et suit donc les évolutions en amont.

Nous ouvrons une sortie OpenSSL figée

mesuré

smime.rs::every_openssl_produced_message_opens

Six réponses connues smime-kat-openssl-*.der versionnées (RSA et EC, GCM et CBC, RSA-OAEP, et les deux KDF ECDH), de sorte que le lecteur soit testé sur des machines sans OpenSSL du tout. Douze spécimens de ce genre sont versionnés dans l’ensemble de la suite S/MIME.

Nous vérifions des signatures OpenSSL figées

mesuré

smime.rs::every_openssl_produced_signature_verifies

Détachée, opaque et EC.

…y compris P-384

mesuré

smime.rs::p384_signatures_verify

Un test distinct, car plusieurs PKI S/MIME nationales délivrent des certificats P-384.

13.3.1. La question des AuthEnvelopedData

Un client y a répondu — Thunderbird, ci-dessous — et la réponse était non, ce pour quoi une installation empaquetée livre CRYPTO_ALLOW_DOWNGRADE = yes. Le reste du tableau demeure ouvert.

La valeur par défaut compilée de Pepsi est l”AuthEnvelopedData S/MIME (AES-256-GCM, RFC 5083/5084). Contrairement à OpenPGP, il n’y a rien à négocier : un certificat X.509 n’annonce aucune capacité de chiffrement de contenu, il n’y a donc aucun signal à lire dans la clé du destinataire. CRYPTO_ALLOW_DOWNGRADE = negotiate se comporte donc comme no pour S/MIME, et un déploiement dont les correspondants ne peuvent pas lire l’AEAD doit régler CRYPTO_ALLOW_DOWNGRADE = yes globalement, ce qui rabaisse chaque message S/MIME à de l”EnvelopedData AES-256-CBC.

Note

Un Pepsi installé règle déjà cet interrupteur. Le résultat Thunderbird ci-dessous est assez grave — illisible, et silencieusement — pour que yes soit livré comme valeur par défaut empaquetée dans ${DATADIR}/config.d/thunderbird.conf, qui est analysé avant pepsi.conf. La valeur par défaut du code demeure negotiate : supprimez ce seul fichier et le déploiement revient immédiatement à l’AEAD, ce qui est le bon geste pour un déploiement qui connaît ses correspondants. C’est le seul endroit où le choix est fait — pepsi-setup n’écrit délibérément pas l’option, car tout ce qu’il mettrait dans pepsi.conf redéfinirait le fichier empaqueté sans le dire.

Qu’un déploiement donné ait besoin de cet interrupteur dépend entièrement des clients qu’emploient ses correspondants — et c’est à cela que sert ce tableau :

Client

Lit-il les AuthEnvelopedData ?

Fondement

OpenSSL 3.5.6 cms

oui

mesuré (smime.rs::openssl_decrypts_what_we_emit, cas gcm-oaep et gcm-kari)

Thunderbird 140.12.0esr (NSS)

non

mesuré (thunderbird.rs::thunderbird_reads_our_smime) ; voir ci-dessous

Outlook (bureau)

inconnu

non exécuté

Outlook Web

inconnu

non exécuté

Apple Mail / Mail iOS

inconnu

non exécuté

13.3.2. Thunderbird ne peut pas le lire

Mesuré contre Thunderbird 140.12.0esr, pour le transport de clé RSA comme pour l’accord de clé ECDH, sur du texte chiffré produit par cette crate :

  • l”EnvelopedData avec AES-256-CBC — la sortie de CRYPTO_ALLOW_DOWNGRADE = yes — est lue correctement ;

  • l”AuthEnvelopedData avec AES-256-GCM — la sortie par défaut — n’est pas lue du tout.

Le S/MIME de Thunderbird, c’est NSS : il s’agit donc d’une propriété de NSS plutôt que de l’interface utilisateur, et il est très improbable qu’elle change au sein d’une série ESR.

Avertissement

NSS échoue silencieusement. Il ne lève pas d’erreur sur un message AuthEnvelopedData ; il renvoie un texte clair vide. Un appelant qui ne vérifie qu’une exception conclut que le message s’est déchiffré en rien. Notre propre test a dû être écrit de manière à traiter « ouvert, zéro octet » comme un échec — voir la note sur plaintext() dans thunderbird.rs — et un opérateur qui débogue le rapport « le message est vide » d’un utilisateur devrait soupçonner ceci avant de soupçonner le message.

La conséquence pour les déploiements est concrète : si vos correspondants lisent leur courrier dans Thunderbird, il vous faut CRYPTO_ALLOW_DOWNGRADE = yes. Contrairement à OpenPGP il n’y a rien à négocier — un certificat X.509 n’annonce aucune capacité de chiffrement de contenu — de sorte que Pepsi ne peut pas le détecter par destinataire et choisir à votre place.

Parce que cela ne peut pas être détecté, et parce que l’échec est silencieux, c’est cette valeur que livre une installation empaquetée : ${DATADIR}/config.d/thunderbird.conf la règle, et config.d est analysé avant pepsi.conf. Le compromis est délibéré et réversible en une étape :

  • le fichier est une valeur par défaut, non une décision — faites-en un rm (ou écrivez CRYPTO_ALLOW_DOWNGRADE = negotiate dans pepsi.conf, qui est analysé ensuite et l’emporte) et chaque message S/MIME redevient du chiffrement authentifié ;

  • cela ne coûte rien du côté OpenPGP. L’étape de chiffrement demande le conteneur auto, et negotiate et yes le résolvent à l’identique — SEIPDv2 pour un certificat qui l’annonce, SEIPDv1+MDC seulement pour un qui ne l’annonce pas. La négociation par destinataire qu’OpenPGP peut faire n’est pas abandonnée pour corriger un problème que S/MIME ne peut pas éviter ;

  • la seule autre chose que yes change, c’est qu’un message S/MIME 3DES entrant devient déchiffrable au lieu d’être refusé. Le 3DES n’est jamais émis sous aucun réglage, et RC2, le DES simple, le SED OpenPGP non protégé et MD2 restent refusés sous tous.

Lorsque NSS gagnera la prise en charge de l’AEAD, la barrière le dira à la première exécution qui réussit — elle imprime la case AES-256-GCM comme READ CORRECTLY au lieu de virer au rouge — et le fichier empaqueté pourra être abandonné.

Avertissement

Outlook et Apple Mail restent non mesurés. Un opérateur dont les utilisateurs échangent du S/MIME avec eux devrait tenir CRYPTO_ALLOW_DOWNGRADE = yes pour le réglage sûr et vérifier à la main avec un vrai message. Pepsi note le conteneur émis dans le state.crypto de chaque message, de sorte que ce qui est réellement parti soit auditable après coup plutôt que deviné.

13.4. Thunderbird : la barrière, et comment tester un client graphique sans interface graphique

Thunderbird est une barrière de publication aux côtés de gpg et d’OpenSSL : c’est le client le plus largement déployé parlant les deux protocoles, et celui qui a le plus de chances d’attraper un problème de MIME, de Content-Transfer-Encoding ou de micalg que gpg et OpenSSL acceptent volontiers.

La barrière est pepsi-crypto/tests/thunderbird.rs, exécutée par make integrationtests (ou directement avec PEPSI_INTEROP_THUNDERBIRD=1 cargo test -p pepsi-crypto --test thunderbird -- --nocapture). Version testée : Thunderbird 140.12.0esr.

La barrière n’a besoin ni d’un vrai profil ni d’un affichage. Le S/MIME de Thunderbird, c’est NSS, et le décodeur CMS de NSS est atteignable depuis du JavaScript à privilèges chrome sous le nom @mozilla.org/nsCMSDecoderJS;1 : le test n’automatise donc aucune interface utilisateur :

  1. pepsi-crypto émet le message — notre sortie, non une fixture ;

  2. openssl pkcs12 empaquette la clé de fixture correspondante ;

  3. Thunderbird démarre en --headless --marionette -remote-allow-system-access contre un profil jetable ;

  4. un petit client Marionette (JSON préfixé par sa longueur sur TCP, implémenté à l’intérieur du test — il est trop petit pour mériter une dépendance) exécute un script chrome qui importe la clé dans la base NSS de ce profil et appelle le décodeur ;

  5. ce qui revient est comparé au texte clair qui avait été chiffré.

Il vit dans make integrationtests plutôt que dans make check uniquement parce que démarrer Thunderbird coûte une quinzaine de secondes. Sans PEPSI_INTEROP_THUNDERBIRD=1, ou sans thunderbird et openssl sur le PATH, il imprime pourquoi il est sauté et rend la main.

Deux pièges que rencontrera quiconque étend ce test :

  • nsICMSDecoderJS accumule d’un appel à l’autre. Réutiliser une même instance de décodeur pour plusieurs messages fait que le second rapporte le texte clair du premier concaténé au sien — ce qui se lit comme un succès. Une instance par message.

  • NSS rapporte un conteneur illisible comme un résultat vide, non comme une erreur. Un test qui ne vérifie qu’une exception levée passe sur un message qui n’a jamais été déchiffré.

Ce qui n’est toujours pas mesuré : si Thunderbird vérifie ce que nous signons. nsICMSDecoderJS n’expose que decrypt, et atteindre la vérification de signature de NSS depuis un script chrome suppose de passer par le pipeline d’affichage des messages, qui exige bel et bien un profil avec un compte configuré. La moitié OpenPGP est de même non mesurée contre le RNP de Thunderbird, quoique gpg — une implémentation entièrement indépendante — couvre les deux sens pour ce protocole.

La moitié « corpus figé » du mécanisme est construite et fonctionne : pepsi-crypto/tests/data/interop/ contient des spécimens ainsi que le verdict que chacun doit produire, et interop.rs::the_frozen_third_party_corpus_still_opens les parcourt. Il contient actuellement trois spécimens de gpg 2.4.7 et aucun spécimen de Thunderbird — en ajouter un n’exige aucun changement de code ; voir le README de ce répertoire.

13.4.1. Pourquoi le corpus figé importe

Lorsque la barrière échoue, elle doit dire de quel côté le changement s’est produit, et un test en direct ne le peut pas : un résultat rouge signifie soit que notre émetteur a régressé, soit que le client a bougé, et l’exécution ne peut pas les distinguer. Les deux moitiés ensemble le peuvent —

  • le corpus figé est ouvert par notre lecteur, et les octets sont dans git : un échec là signifie donc que nous avons régressé ; et

  • les cases en direct exécutent l’outil courant : un échec là signifie donc qu”ils ont bougé.

La version testée doit être consignée dans ce chapitre et épinglée dans le harnais, et un spécimen de sa sortie figé dans le corpus. Relever cet épinglage est alors un acte délibéré avec son propre résultat à consigner, ce qui empêche la barrière de pourrir.

13.5. Outlook et Apple Mail : observations consignées, non des barrières

Ils sont volontairement hors de l’automatisation : ils ne peuvent pas tourner sur du matériel qu’on peut garder en intégration continue, et une barrière qui n’est pas réellement vérifiée est pire qu’une observation honnête. Ils sont censés être exercés à la main au moment d’une publication, avec les versions et les échecs consignés ici.

Client

Protocole

Statut

Notes

Outlook (bureau)

S/MIME

non exécuté

Aucune version testée. Voir La question des AuthEnvelopedData.

Outlook Web

S/MIME

non exécuté

Aucune version testée.

Apple Mail / Mail iOS

S/MIME

non exécuté

Aucune version testée.

Un webmail sans prise en charge de la cryptographie

—

non exécuté

C’est le cas pour lequel Le portail de repli par lien sécurisé existe : le destinataire n’a ni clé ni prise en charge côté client, et reçoit un courrier de notification avec un lien plutôt qu’un texte chiffré qu’il ne peut pas ouvrir.

13.6. Règles d’encodage imposées par l’interopérabilité

Plusieurs règles de l’émetteur n’existent que parce qu’une seconde implémentation a rejeté la chose évidente. Elles sont consignées ici parce qu’elles paraissent arbitraires dans le code et sont coûteuses à redécouvrir :

  • CRLF canonique avant la signature. Une signature couvre l’entité MIME exactement telle qu’elle apparaît sur le fil. L’écrivain d’armure de Sequoia émet des LF nus : les octets signés passent donc par un canonicaliseur CRLF avant d’être hachés ; sans lui, gpg calcule un condensat différent et rapporte une mauvaise signature sur des données correctes.

  • L’entité signée est préservée octet pour octet. Les octets remis au vérificateur sont les octets entre les frontières MIME, intacts — aucun réenveloppement, aucun réordonnancement d’en-têtes, aucun changement d’encodage de transfert. Tout « rangement » d’une partie multipart/signed l’invalide.

  • ``micalg`` est indicatif. Il est écrit correctement en sortie et ignoré en entrée : le vrai condensat est à l’intérieur de la signature. Les clients se trompent, et rejeter sur un micalg discordant rejette du courrier valide.

  • ``-recip`` plutôt qu’un argument positionnel final, dans le harnais de test OpenSSL : l’analyse d’options d’OpenSSL s’arrête au premier argument positionnel, ce qui abandonne silencieusement -binary et produit des échecs déroutants.

13.7. Reproduire les lignes mesurées

Tout ce qui est marqué mesuré s’exécute depuis une copie de travail sans réseau et sans déploiement :

cargo test -p pepsi-crypto                      # every measured cell
cargo test -p pepsi-crypto --test interop -- --nocapture   # OpenPGP, verbose

Les deux sont sautés plutôt qu’en échec lorsque gpg ou OpenSSL manque, de sorte qu’une machine sans eux exécute tout de même le reste de la suite. Pour voir quelles cases se sont réellement exécutées, passez --nocapture : une case sautée imprime sa raison.

Pour rafraîchir les spécimens gpg figés contre un gpg plus récent :

PEPSI_CRYPTO_WRITE_INTEROP_CORPUS=1 cargo test -p pepsi-crypto --test interop

Cela réécrit les spécimens et la version de producteur consignée ; c’est un acte délibéré dont un relecteur voit le diff, jamais une partie d’une exécution ordinaire.

13.8. Voir aussi

Gestion des clés — la couche d’identités, de garde et de découverte derrière les messages mesurés ici. pepsi-stage-encrypt et pepsi-stage-decrypt — les deux étapes dont ces tableaux décrivent la sortie et l’entrée. Index des RFC — de quelle spécification vient chaque conteneur et chaque paramètre. Suite de tests — comment le reste de la suite est organisé.