12. Le portail de repli par lien sécurisé¶

Le chiffrement de bout en bout s’arrête au premier correspondant qui ne publie aucune clé, et la plupart n’en publient pas. Une politique ENCRYPT = required ne laisse alors à un opérateur que deux mauvaises réponses : faire rebondir le message, ou l’envoyer en clair quand même. Le portail de lien sécurisé est la troisième : retenir le message sur le serveur, chiffré, et laisser le destinataire le lire dans un navigateur après avoir saisi un PIN qui lui est parvenu par une autre voie.

C’est la troisième branche de la politique ON_NO_KEY de pepsi-stage-encrypt ; les deux autres, et le magasin de clés qui décide de la branche empruntée, sont dans Gestion des clés.

Sur cette page

  • Le déroulement

  • Ce que cela protège, et ce que cela ne protège pas

  • Répondre

  • Où placer le portail

  • Stockage et dimensionnement

  • L’exploiter

  • Habillage

  • Voir aussi

12.1. Le déroulement¶

  1. Un utilisateur local envoie un message confidentiel. pepsi-stage-encrypt ne trouve aucune clé utilisable pour le destinataire et, parce que ON_NO_KEY = secure-link, l’achemine vers pepsi-stage-secure-link au lieu de le relayer.

  2. Cette étape génère un PIN, dérive un jeton d’URL de 192 bits à partir de la ligne de la file (voir pepsi-stage-secure-link(1)) et une clé de contenu à partir du PIN, d’un sel par message et du poivre du serveur (le jeton n’est pas une entrée de la dérivation de clé — il constitue les données associées de l’AEAD), ne stocke que le texte chiffré et supprime la ligne de la file d’attente.

  3. Le destinataire reçoit un courrier de notification avec un lien. L”expéditeur reçoit le PIN (la valeur par défaut PIN_DELIVERY = sender) et le transmet par téléphone, par message texte ou de vive voix.

  4. Le destinataire ouvre le lien, saisit le PIN et lit le message. Les pièces jointes peuvent être téléchargées ; si REPLY_STAGE est configuré, une réponse peut être écrite et renvoyée à l’expéditeur.

  5. Après EXPIRY_DAYS, le message cesse d’être lisible, et pepsi-secure-link prune le supprime.

Le pipeline ressemble à ceci

[stage-encrypt]
PROGRAM = pepsi-stage-encrypt
ENCRYPT = required
SECURE_LINK_STAGE = secure-link
NEXT_STAGE = srs

[stage-secure-link]
PROGRAM = pepsi-stage-secure-link

[pepsi-secure-link]
BASE_URL = https://secure.example.org
NOTIFY_STAGE = srs
ORGANIZATION = Example Ltd
# PEPPER is written to secrets.d/pepsi-secure-link.secret by pepsi-setup.

NOTIFY_STAGE est sur le chemin sortant, car les courriers de notification et de PIN sont des messages que le déploiement émet et doit signer et relayer normalement.

12.2. Ce que cela protège, et ce que cela ne protège pas¶

12.2.1. Le message stocké¶

La clé de contenu est dérivée avec Argon2id (RFC 9106) à partir de trois entrées conservées en trois endroits différents. Argon2id plutôt qu’un simple hachage ou PBKDF2 parce que le secret étiré est un court PIN (par défaut dix caractères tirés d’un alphabet de 31 symboles qui écarte les caractères ressemblants) : ce qu’il faut rendre coûteux est une recherche hors ligne parallèle, et seule une fonction gourmande en mémoire élève le coût du matériel qu’un attaquant emploierait plutôt que le seul temps.

Les trois entrées :

  • le PIN, qui parvient au destinataire hors bande et n’est stocké nulle part ;

  • un sel par message, qui est une colonne de base de données (un sel n’est pas un secret — c’est ce qui empêche un seul précalcul de couvrir tous les messages) ;

  • un poivre côté serveur, qui ne réside que dans secrets.d/pepsi-secure-link.secret.

Seul le texte chiffré AES-256-GCM est stocké — un AEAD, de sorte que le message stocké est authentifié autant que confidentiel et qu’un texte chiffré altéré échoue à s’ouvrir plutôt que de se déchiffrer en quelque chose qu’un attaquant a choisi. Par conséquent :

  • Une base volée n’est pas une boîte aux lettres. Un vidage, une réplique ou une bande de sauvegarde livre du texte chiffré et un sel, et ni le PIN ni le poivre n’y figurent.

  • Un serveur compromis après coup n’est pas non plus une boîte aux lettres. Un attaquant qui s’empare aussi du poivre doit encore trouver le PIN, et un PIN de 10 caractères sous Argon2id n’est pas quelque chose que l’on parcourt, pour les messages stockés avant son arrivée. Celui qui garde le contrôle du pipeline voit chaque nouveau message en clair au moment où il est soumis, comme tout autre courrier en file d’attente.

  • Il n’existe aucun vérificateur de mot de passe. Vérifier un PIN et déchiffrer avec lui sont la même opération — l’étiquette AEAD est la vérification — de sorte que rien de ce qui est stocké ne peut être cassé hors ligne pour retrouver un PIN.

  • L’administrateur ne peut pas lire un message stocké. Ni avec la base, ni avec le poivre, ni avec les deux. Il n’existe pas de pepsi-secure-link read, pas de --decrypt et pas de clé de séquestre. C’est une propriété du message stocké : l’opérateur est digne de confiance pour le courrier en transit (Modèle de menace), et un opérateur qui modifie le serveur peut capturer un message avant qu’il ne soit scellé.

Le prix de cette dernière propriété : un PIN perdu est un message perdu. La voie de récupération est que l’expéditeur le renvoie, ce qui émet un nouveau PIN et un nouveau lien. Attendez-vous à ce qu’on vous demande une clé de séquestre ; en ajouter une redonnerait exactement la capacité que cette conception existe pour supprimer.

Perdre le poivre a la même forme à plus grande échelle : chaque message stocké devient illisible d’un coup. Sauvegardez-le avec le reste de secrets.d, et traitez sa rotation comme l’invalidation de chaque lien encore en cours.

12.2.2. Ce qui n’est pas protégé : les métadonnées¶

Délibérément en clair, sous forme de colonnes de base de données ordinaires :

  • l”expéditeur et le destinataire — la notification et le second facteur doivent être remis, et « est-ce arrivé ? » doit pouvoir recevoir une réponse ;

  • les horodatages — créé, expire, première lecture ;

  • les compteurs de cycle de vie — nombre de lectures, tentatives de PIN échouées, verrouillages, réponses ;

  • le journal d’accès — quand chaque tentative a eu lieu, depuis quelle adresse, et comment elle s’est terminée.

Le sujet n’est pas de cette liste. C’est en général le champ le plus révélateur — souvent il est le message (« Résultats de la biopsie », « Lettre de licenciement ») — et rien dans l’acheminement ni l’expiration n’en a besoin : il réside donc dans le texte chiffré avec le corps.

Une console d’opérateur liste donc qui et quand, jamais quoi, et l’accusé de lecture aussi. Le support en est d’autant plus difficile : « le message que vous avez envoyé à Bob à 09:14 a été lu à 11:02 » est tout ce que quiconque peut dire ici, et il n’y a aucun moyen de confirmer ce qu’il contenait.

Le journal d’accès consigne l’adresse du pair et rien d’autre — pas d’agent utilisateur, et pas de géolocalisation.

12.2.3. Le second facteur¶

Toute la construction repose sur le fait que le PIN voyage par un canal que la boîte aux lettres du destinataire ne contrôle pas. PIN_DELIVERY le choisit :

sender (par défaut)

Le PIN est envoyé par courrier à l”expéditeur, qui le transmet. Aucune dépendance externe, cela fonctionne partout, et le canal est un canal auquel les deux correspondants font déjà confiance. Cela dépend en revanche du fait que l’expéditeur emploie réellement un second canal — renvoyer le PIN à la même adresse anéantirait le dispositif, et le courrier du PIN le dit.

command

Le PIN est remis à un programme configuré (une passerelle SMS, un crochet de messagerie d’entreprise) sur l’entrée standard. L’option la plus forte lorsqu’un véritable second canal existe.

none

Nettement plus faible, et à choisir délibérément. Le lien lui-même porte le PIN, de sorte que quiconque peut lire la boîte aux lettres du destinataire peut lire le message — ce que la fonctionnalité existe précisément pour empêcher. Cela place aussi le PIN dans une URL, et donc potentiellement dans un historique de navigateur ou les journaux d’un intermédiaire. Ne l’utilisez que là où la boîte aux lettres est véritablement le facteur.

12.2.4. Force brute¶

Un PIN assez court pour être lu à voix haute est assez court pour être deviné : la devinette est donc bornée deux fois. Par jeton, MAX_ATTEMPTS PIN erronés (10 par défaut) le verrouillent pendant LOCKOUT (15 minutes), et la fenêtre double à chaque nouveau verrouillage, jusqu’à 64 fois la durée de base ; pendant un verrouillage, même le bon PIN est refusé. Le compteur d’échecs est remis à zéro au début d’un verrouillage, de sorte que la fenêtre suivante offre un budget neuf plutôt qu’un reverrouillage instantané. Par source, RATE_LIMIT plafonne les requêtes par minute sur toutes les routes du portail, ce qui empêche aussi la dérivation de clé délibérément coûteuse de devenir un moyen de dépenser la mémoire du serveur.

Seule une véritable tentative est comptée. Un formulaire soumis avec le champ PIN vide — ou ne portant rien que la normalisation conserve — est refusé sans toucher au compteur et sans exécuter la dérivation. Cela n’apprend à un attaquant rien qu’il n’ait déjà, et le compter permettrait à tout client qui resoumet le formulaire vide (une double soumission, un préchargement, un suiveur de redirection qui conserve la méthode) d’exclure un destinataire légitime de son propre message.

Une session en cours n’est pas soumise au verrouillage, de sorte qu’un tiers devinant sur le même jeton ne peut pas exclure un lecteur légitime en pleine lecture.

12.2.5. Le navigateur¶

Rendre dans un navigateur du courrier contrôlé par un attaquant est le plus grand risque que porte cette fonctionnalité, et on y répond en ne le faisant pas :

  • Le message est rendu en texte, jamais en HTML. Un message uniquement HTML est replié en texte ; le balisage ne survit pas. Il n’y a donc aucun assainisseur HTML à contourner, et aucun contenu distant — la politique dit img-src 'none' (img-src data: lorsqu’un LOGO_FILE est incorporé) et s’y tient, de sorte qu’un pixel de suivi ne puisse pas signaler que le message a été ouvert. La mise en forme et les images en ligne sont perdues ; un destinataire qui a besoin de l’original peut télécharger les parties.

  • Chaque valeur dynamique d’une page — sujet, expéditeur, date, nom de fichier, nom de l’organisation — est échappée en HTML à l’endroit où elle est écrite dans la page.

  • Chaque réponse porte Content-Security-Policy: default-src 'none' avec un nonce par réponse pour l’unique feuille de style en ligne et aucune source de script nommée du tout, plus Referrer-Policy: no-referrer, X-Content-Type-Options: nosniff, X-Frame-Options: DENY et Cache-Control: no-store.

  • Une pièce jointe est toujours servie en application/octet-stream comme attachment avec une politique de bac à sable, quel que soit le type que le message prétendait.

12.3. Répondre¶

Dans un portail en lecture seule, le destinataire peut lire confidentiellement mais doit répondre en clair, rendant l’essentiel de ce que la fonctionnalité avait gagné. Répondre est donc pris en charge. Parce que c’est un point de terminaison d’injection de messages sur le web public, les contraintes y sont intégrées plutôt que laissées à la procédure :

  • La réponse ne va qu’à l’expéditeur d’origine, et son expéditeur d’enveloppe est forcé au destinataire stocké. Ni l’un ni l’autre n’est une entrée : il n’y a donc rien à détourner — c’est ce qui empêche le portail de devenir un relais ouvert.

  • Le message injecté ne porte pas state.local_origin. Il entre à REPLY_STAGE pour ce qu’il est : du courrier entrant qui se trouve avoir pris naissance sur notre propre serveur web. Il ne peut donc pas emprunter le chemin de soumission — pas de relais en notre nom, pas d’ajout automatique à la liste blanche, pas de SRS comme s’il était d’origine locale.

  • Elle n’est pas signée en DKIM comme l’un de vos domaines. L’auteur est une partie extérieure ; pepsi-setup avertit si REPLY_STAGE se résout en une étape de signature. La réponse porte un en-tête X-Pepsi-Secure-Link-Reply: …; author-unverified afin que le destinataire puisse voir comment elle est arrivée.

  • Le corps est en text/plain, quoi qu’on ait tapé. Composer du HTML impliquerait d’assainir aussi le HTML sortant, et pas seulement l’entrant.

  • Les pièces jointes sont autorisées et plafonnées — taille totale, nombre de fichiers, et réponses par message — la limite de taille étant appliquée pendant que le téléversement s’écoule, de sorte qu’un client qui déclare un mauvais Content-Length ne gagne rien. Les téléversements ne sont jamais écrits sur disque : ils vont directement dans le message injecté, si bien qu’il n’y a pas de seconde durée de stockage et que rien ne subsiste si l’injection échoue.

  • Chaque réponse est consignée dans le journal d’accès du message.

Répondre est désactivé à moins que REPLY_STAGE ne soit défini.

12.4. Où placer le portail¶

Un nom d’hôte dédié est la recommandation — un enregistrement DNS et un SAN certbot procurent une véritable séparation d’origine — mais ce n’est pas exigé, et une origine partagée est sûre par construction plutôt que par discipline :

  • le cookie de session est limité à Path=/secure/, de sorte qu’il n’est jamais envoyé à /metrics, /resume ou au Web Key Directory, même sur un seul hôte ;

  • sa valeur est un AEAD scellé sous le poivre du serveur, de sorte qu’un cookie planté par un sous-domaine frère ne s’ouvre pas — ce qui aurait sinon appelé un préfixe __Host- (un préfixe que la RFC 6265bis rend incompatible avec la limitation de chemin, puisqu’il exige Path=/) ;

  • il est HttpOnly, Secure, SameSite=Strict et propre à l’hôte (pas de Domain) ;

  • les en-têtes de sécurité sont posés par route, non par hôte, de sorte que la politique stricte du portail s’applique où qu’il soit monté.

Le portail doit être joignable en HTTPS depuis le navigateur : le cookie de session porte Secure. Un reverse proxy terminant le TLS en façade convient.

12.5. Stockage et dimensionnement¶

Le texte chiffré réside dans la base en BYTEA. Cela garde tout transactionnel — créer un message sécurisé est une insertion, l’élagage un DELETE, et la sauvegarde et la restauration couvrent le contenu automatiquement, sans fichiers orphelins et sans une seconde chose dont il faudrait bien régler les permissions.

Le coût est une taille de base et un brassage de WAL proportionnels au trafic du portail. Un message est stocké une fois par destinataire sans clé (chacun avec son propre PIN et son propre texte chiffré, de sorte que le PIN d’un destinataire n’ouvre jamais la copie d’un autre), et [stage-encrypt] MAX_SIZE (25 Mo par défaut) borne chacun. Dimensionnez un déploiement en conséquence, et exécutez pepsi-secure-link prune chaque jour — le portail refuse déjà un jeton expiré, mais rien ne supprime les octets tant que la tâche ne s’exécute pas.

12.6. L’exploiter¶

pepsi-secure-link list montre ce qui est encore en cours ; show y ajoute le journal d’accès d’un message ; revoke détruit un message (cela ne remet pas le courrier dans la file d’attente — cela le retire) ; prune supprime ce qui a expiré. Voir pepsi-secure-link(1).

La frontière en base est plus étroite que dans le reste du schéma et est vérifiée à chaque pepsi-setup run : seul le rôle pepsi-httpd peut lire secure_message.ciphertext. Le rôle pepsi — sous lequel s’exécute chaque worker d’étape, y compris ceux qui analysent du courrier hostile — peut créer un message, lister les métadonnées et supprimer une ligne, et ne peut pas en relire un. pepsi-ingress et les autres ne peuvent pas toucher aux tables du tout.

12.7. Habillage¶

ORGANIZATION, BRAND_COLOR et LOGO_FILE fixent l’apparence des pages ; ORGANIZATION est aussi nommé dans les courriers de notification. La couleur doit être une couleur hexadécimale et le logo une image matricielle locale, pour la même raison : ils sont interpolés dans une page, et une valeur qui pourrait être du texte arbitraire ou un SVG élargirait la surface d’attaque de la page. Les trois courriers de notification eux-mêmes sont des modèles Mustache (secure-link, secure-pin, secure-receipt) sous [pepsi] TEMPLATE_DIR et peuvent être réécrits entièrement.

12.8. Voir aussi¶

Gestion des clés (le magasin et la couche de découverte qui décident si un destinataire a besoin de ce repli), pepsi-stage-encrypt (ON_NO_KEY = secure-link), pepsi-stage-secure-link, pepsi-secure-link (la CLI d’opérateur), pepsi-httpd (qui sert le portail), pepsi.conf(5).

Logo

Pepsi 0.0.0

Langues

  • English
  • Deutsch
  • français

Navigation

Sommaire

  • 1. Introduction
  • 2. Démarrage sur un VPS bon marché
  • 3. Installation
  • 4. Paquets Debian
  • 5. L’assistant
  • 6. Configuration
  • 7. Exploiter Pepsi
  • 8. Dépannage
  • 9. Fonctionnalités prises en charge
  • 10. Extensions du protocole SMTP
  • 11. Gestion des clés
  • 12. Le portail de repli par lien sécurisé
    • 12.1. Le déroulement
    • 12.2. Ce que cela protège, et ce que cela ne protège pas
    • 12.3. Répondre
    • 12.4. Où placer le portail
    • 12.5. Stockage et dimensionnement
    • 12.6. L’exploiter
    • 12.7. Habillage
    • 12.8. Voir aussi
  • 13. Interopérabilité des clients
  • 14. Modèle de menace
  • 15. Modèle de sécurité
  • 16. Microsoft Exchange comme passerelle
  • 17. Listes de diffusion
  • 18. Archives
  • 19. L’API REST de GNU Mailman 3
  • 20. L’API d’administration
  • 21. La console d’administration
  • 22. Architecture
  • 23. L’état du message
  • 24. Étendre le pipeline
  • 25. Suite de tests
  • 26. Suite de benchmarks
  • 27. Performance
  • 28. pepsi-ingress
  • 29. pepsi-dispatch
  • 30. pepsi-httpd
  • 31. pepsi-stage-arc
  • 32. pepsi-stage-srs
  • 33. pepsi-stage-encrypt
  • 34. pepsi-stage-decrypt
  • 35. pepsi-stage-dkim-sign
  • 36. pepsi-stage-bounce
  • 37. pepsi-stage-aliases
  • 38. pepsi-stage-relay-to-internet
  • 39. pepsi-stage-relay-to-smarthost
  • 40. pepsi-stage-relay-to-maildir
  • 41. pepsi-stage-dot-forward
  • 42. pepsi-stage-relay-to-lmtp
  • 43. pepsi-stage-discard
  • 44. pepsi-stage-anti-spam
  • 45. pepsi-stage-auto-pay
  • 46. pepsi-stage-check-whitelist
  • 47. pepsi-stage-auto-whitelist
  • 48. pepsi-stage-autocrypt-learn
  • 49. pepsi-stage-reencrypt
  • 50. pepsi-stage-detect-language
  • 51. pepsi-detect-language
  • 52. pepsi-stage-block-language
  • 53. pepsi-stage-vacation
  • 54. pepsi-stage-secretary
  • 55. pepsi-stage-edit-settings
  • 56. pepsi-stage-if
  • 57. pepsi-stage-milter
  • 58. pepsi-stage-route
  • 59. pepsi-stage-vks-confirm
  • 60. pepsi-stage-secure-link
  • 61. pepsi-setup
  • 62. pepsi-queue
  • 63. pepsi-status
  • 64. pepsi-sendmail
  • 65. pepsi-whitelist
  • 66. pepsi-keys
  • 67. pepsi-keydisc
  • 68. pepsi-settings
  • 69. pepsi-list
  • 70. pepsi-archive
  • 71. pepsi-stage-list
  • 72. pepsi-stage-list-post
  • 73. pepsi-stage-list-deliver
  • 74. pepsi-stage-list-command
  • 75. pepsi-stage-list-bounce
  • 76. pepsi-tlsrpt
  • 77. pepsi-secure-link
  • 78. pepsi-failure-bouncer
  • 79. pepsi-quota
  • 80. pepsi-helper-token-refresh
  • 81. pepsi-telemetry
  • 82. pepsi-telemetry-client
  • 83. pepsi-config
  • 84. Stabilité des fonctionnalités
  • 85. Pages de manuel
  • 86. Index des RFC

Related Topics

  • Documentation overview
    • Previous: 11. Gestion des clés
    • Next: 13. Interopérabilité des clients

Recherche rapide

©2026, GNUnet e.V.. | Powered by Sphinx 8.1.3 & Alabaster 0.7.16 | Page source