Skip to content

Latest commit

 

History

63 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Signatures du manifeste OTSPI

Petite application PHP (sans dépendance) et base SQLite qui recueille les signatures du manifeste pour une identité numérique libre et ouverte, en remplacement du formulaire Framaforms. Licence EUPL 1.2.

Fonctionnement

  1. La personne remplit le formulaire (public/index.php) : prénom, nom, e-mail, fonction et organisation facultatives, consentement à la publication.
  2. Elle reçoit un e-mail qui récapitule les informations saisies, avec un lien valable 48 heures (double consentement). Le lien ouvre une page qui les récapitule à nouveau, avec un bouton : seul ce bouton (POST) confirme, pour que les antivirus de messagerie qui suivent les liens ne confirment pas à la place de la personne.
  3. Après confirmation, un second e-mail contient le lien de retrait, qui supprime la signature et les données, et le lien vers sa preuve horodatée (public/preuve.php) :
    • une attestation JSON de la signature (manifeste, nom, fonction, organisation, choix de publication, date de confirmation, sel aléatoire ; jamais l'adresse e-mail) est figée à la confirmation, et son empreinte SHA-256 est horodatée (RFC 3161) par l'autorité d'horodatage de staging d'OTSPI (api.staging.open-eidas.eu) — preuve non qualifiée, sans valeur juridique ;
    • la page de preuve affiche la date certifiée et les détails du jeton, que le serveur vérifie sans dépendance (src/horodatage.php : signature CMS de l'unité d'horodatage, empreinte, usage « Time Stamping », émission par la chaîne épinglée src/tsa-staging-ca.pem), propose l'attestation (.json) et le jeton (.tsr) au téléchargement, et renvoie au vérificateur de demo.open-eidas.eu ou à openssl ts -verify ;
    • la page de preuve propose aussi une attestation PDF (src/pdf.php, sans dépendance) : une page A4 avec les données de la signature et de son horodatage, l'attestation JSON et son jeton .tsr en pièces jointes (ISO 32000), et un horodatage PAdES du PDF lui-même (/SubFilter /ETSI.RFC3161, reconnu par Adobe Acrobat Reader, pyHanko et la démonstration d'OTSPI). Construit au premier téléchargement et conservé ; sans horodatage si l'autorité ne répond pas, puis reconstruit ;
    • si l'autorité ne répond pas (5 secondes au plus), la confirmation n'est pas bloquée : la tâche quotidienne réessaie. Elle horodate aussi les signatures antérieures et leur envoie leur preuve (100 e-mails au plus par passage). php bin/horodatage.php fait le même rattrapage à la demande.
  4. Si la personne a accepté la publication, la signature attend une modération : l'adresse contact reçoit une notification avec un lien vers l'administration (connexion par clé de sécurité ; les anciens liens moderation.php y redirigent), ou en ligne de commande php bin/moderation.php lister | recapitulatif | valider ID | masquer ID | supprimer ID (masquer : signature comptée, nom jamais publié ; supprimer : usurpation, abus). Seule l'adresse e-mail est vérifiée par l'application, pas l'identité déclarée.
  5. Administration : https://manifesto-sign.otspi.org/admin.php, ouverte uniquement avec une clé de sécurité (WebAuthn / FIDO2, une YubiKey) et son code PIN : la vérification de l'utilisateur est exigée, ce qui fait deux facteurs (possession de la clé, PIN). Ni mot de passe ni lien de connexion. La page liste les signatures par état (à modérer, publiées, non publiées, en attente de confirmation) avec les actions valider, masquer et supprimer ; pour les demandes en attente de confirmation, renvoyer le lien de confirmation (nouveau lien de 48 heures, au plus toutes les 10 minutes) ou supprimer la demande. Actions en POST depuis le site, suppression confirmée.
    • Enregistrer la première clé : tant qu'aucune clé n'existe, la page propose d'envoyer à la seule adresse contact un lien d'enregistrement valable 24 heures et à usage unique (au plus 3 envois par 10 minutes). Ensuite, ce bouton disparaît : une clé supplémentaire (recommandée, gardée en lieu sûr) s'ajoute depuis l'onglet « Clés de sécurité » d'une session ouverte, ou par un nouveau lien envoyé depuis le serveur avec php bin/admin.php invitation (clé perdue). Chaque enregistrement déclenche une alerte à l'adresse contact.
    • Clés : l'onglet « Clés de sécurité » les liste (modèle par AAGUID, dernière utilisation) et permet de les révoquer, sauf la dernière ; la révocation ferme les sessions ouvertes avec la clé. config.php peut limiter les modèles acceptés (admin_aaguids). En ligne de commande : php bin/admin.php cles | revoquer ID | deconnecter.
    • Session : cookie __Host- HttpOnly, Secure, SameSite=Lax, propre à l'administration ; fermée après 30 minutes d'inactivité, 8 heures au plus. Les défis WebAuthn sont signés (HMAC, 10 minutes), liés à leur usage et à usage unique ; le compteur de signatures de la clé est contrôlé.
  6. public/signataires.php expose en JSON la liste publique : signatures confirmées, avec consentement à la publication et validées, sans adresse e-mail. Le total n'inclut pas les signatures en attente de modération. La page du manifeste de www.otspi.org la lit en direct (en-tête CORS limité à https://www.otspi.org) : une signature apparaît quelques minutes après sa validation (cache de 5 minutes).
  7. cron/purge.php (tâche quotidienne) supprime les demandes non confirmées de plus de 7 jours, ainsi que les invitations, défis et sessions d'administration expirés ; il horodate les signatures en attente et envoie leur preuve, inscrit l'entrée du jour au registre horodaté et envoie la sauvegarde chiffrée. Au moindre problème (autorité d'horodatage injoignable, registre en attente ou rompu, sauvegarde en échec), une alerte part à l'adresse contact, au plus une par passage.
  8. Registre horodaté (public/registre.php, lien en pied de page) : chaque nuit, une entrée fige le total, le nombre de noms publiés et l'empreinte SHA-256 de la liste publique telle que la sert signataires.php, chaînée à l'entrée précédente par son empreinte, puis horodatée (RFC 3161). Qui a conservé la liste d'un jour peut prouver qu'elle n'a pas été modifiée ; la chaîne interdit de réécrire l'historique sans que cela se voie. Le registre ne conserve aucun nom : une signature retirée disparaît de la base, seules les empreintes restent.
  9. Modération : l'adresse contact reçoit chaque nuit un récapitulatif des signatures à modérer (moderation_notify = 'immediat' pour revenir à un e-mail par signature ; php bin/moderation.php recapitulatif pour l'envoyer tout de suite). L'onglet « À modérer » permet de valider ou masquer par lot. Pour chaque adresse, l'administration indique si son domaine correspond à l'organisation déclarée (email_org_hint() : inria.fr pour Inria, univ-lyon1.fr pour Lyon 1, sigles compris), s'il s'agit d'une messagerie grand public, ou d'un autre domaine : c'est une estimation pour la modération, jamais publiée.
  10. Statistiques et export (onglet « Statistiques » de l'administration) : chiffres clés (confirmées, publiées, à modérer, en attente, taux de confirmation), signatures confirmées par jour sur 60 jours (info-bulle au survol, tableau des données), répartition par type d'adresse, et export CSV de toutes les signatures (point-virgule, BOM ; cellules commençant par = + - @ neutralisées contre l'injection de formules).
  11. Partage : après confirmation, la page propose le partage natif du téléphone, des liens LinkedIn, Bluesky, Mastodon et e-mail (simples liens, rien n'est chargé avant le clic), la copie du lien et le code d'intégration du compteur compteur.php : une image SVG « Manifeste OTSPI | N signataires » (?lang=en en anglais), sans script ni traceur, au total de la liste publique.
  12. Sauvegarde (src/sauvegarde.php, chaque nuit ou php bin/sauvegarde.php) : copie cohérente de la base (VACUUM INTO), compressée et chiffrée (CMS, AES-256) pour le seul certificat src/sauvegarde-certificat.pem, envoyée en pièce jointe à backup_to (à défaut contact). Le serveur ne peut pas relire ses sauvegardes : la clé privée est gardée hors du serveur (poste de l'administrateur, à dupliquer dans un coffre de mots de passe). Restauration : openssl cms -decrypt -binary -inform DER -in signatures-AAAA-MM-JJ.sqlite.gz.p7m -inkey cle-privee.pem | gunzip > signatures.sqlite

E-mails : chaque message part en deux versions (multipart/alternative) : le texte brut rédigé dans src/texts.php et un habillage HTML qui en est tiré automatiquement (mail_html() dans src/lib.php : bandeau OTSPI, récapitulatif en tableau, liens en boutons avec l'adresse rappelée dessous, thème sombre), sans image ni ressource externe, donc sans pixel de suivi. En mode essai (mail_dry_run), le HTML est journalisé dans mail_log suivi de .html.

Protections : jeton de formulaire signé avec délai minimal, à usage unique (aucun cookie, aucune session), preuve de travail calculée par le navigateur pendant la saisie (assets/pow.js : SHA-256 du jeton avec 18 bits nuls en tête, soit moins d'une seconde sur un ordinateur ; réglable par pow_bits ; aucun service tiers, JavaScript requis pour signer), refus des adresses d'e-mail jetables (src/disposable-domains.txt, sous-domaines compris), champ piège pour les robots, refus des envois venant d'un autre site (Sec-Fetch-Site, à défaut Origin), limitation de débit par empreinte salée de l'adresse IP ou du préfixe /64 en IPv6 (conservée une heure), plafond global de 200 e-mails de confirmation par heure (mail_hourly_cap), e-mails sans texte libre hors du récapitulatif et liens refusés dans les champs, une seule signature par boîte (les alias +… sont acceptés mais ignorés pour l'unicité), un seul e-mail de confirmation toutes les 10 minutes par adresse, réponse identique que l'adresse soit connue ou non, refus des caractères de contrôle, bidirectionnels et de largeur nulle dans les noms, requêtes préparées, échappement HTML, erreurs journalisées et jamais affichées, en-têtes de sécurité et CSP stricte.

Mesure d'audience : seuls le formulaire (index.php) et la page « signature confirmée » chargent assets/analytics.js (Matomo Tag Manager de stats.otspi.org, sans cookie) et ouvrent leur CSP à ce domaine. Le formulaire charge aussi assets/pow.js (preuve de travail, sans appel extérieur). Les pages dont l'adresse porte un jeton personnel (lien de confirmation, retrait, modération) ne sont pas mesurées. Le formulaire déclare aussi des événements anonymes (attributs data-track et data-track-load, voir analytics.js) : clic sur « Envoyer », code d'erreur éventuel (err_…, jamais les valeurs saisies), page « demande envoyée » et page « signature confirmée » (réponse au POST de confirmation, dont l'adresse confirm.php?lang=… ne porte pas le jeton). analytics.js est commun aux sites d'OTSPI : sa source est dans le dépôt vitrine ; après une copie, incrémenter ?v= dans page() (src/lib.php).

Surveillance et données personnelles dans la durée

  • État de santé : public/sante.php renvoie en JSON, sans donnée personnelle, l'état de la base, de la tâche quotidienne, de la dernière sauvegarde, du registre horodaté, de l'horodatage des signatures et de l'autorité d'horodatage (interrogée au plus toutes les 5 minutes) ; HTTP 200 si tout va bien, 503 sinon. Un contrôle jamais encore exécuté est « en attente ».
  • Surveillance externe : .github/workflows/surveillance.yml lit cette page toutes les 30 minutes, à l'adresse /sante (réécrite par .htaccess : depuis les datacenters, le pare-feu d'o2switch coupe toute requête vers une adresse en .php, ce qui explique aussi les anciens échecs sur signataires.php depuis GitHub Actions) (trois essais) ; en cas d'alerte, le job échoue et GitHub prévient par e-mail la personne qui a activé le workflow planifié.
  • Durée de conservation : campagne_fin (config.php, AAAA-MM-JJ) déclenche la suppression de toutes les signatures deux ans après, comme l'annonce le texte d'information, avec un avis à l'adresse contact 30 et 7 jours avant.
  • Droit d'accès : la page de retrait propose de télécharger, en JSON, toutes les données enregistrées sur la personne avant de décider.

Installation

  1. Copier le dépôt hors du dossier web, le dossier public/ étant la racine du site. Les dossiers src/, data/, bin/ et cron/ ne doivent pas être servis.
  2. Copier config.example.php en config.php et le renseigner (secret aléatoire, adresse, expéditeur). config.php et data/ ne sont jamais versionnés.
  3. Planifier cron/purge.php une fois par jour avec la version en ligne de commande de PHP, en chemin absolu : chez o2switch, 23 3 * * * /usr/local/bin/php ~/otspi-signatures/cron/purge.php >> ~/otspi-signatures/data/purge.log 2>&1 (dans l'environnement du cron, la commande php est php-cgi ; les scripts l'acceptent, mais leur journal se remplit alors d'en-têtes HTTP). Les dates sont à l'heure de Paris (timezone dans config.php pour changer), le PHP de l'hébergeur étant réglé sur UTC.
  4. Prérequis : PHP 8.1 ou plus avec PDO SQLite et OpenSSL, allow_url_fopen activé (appel à l'autorité d'horodatage) ; la fonction mail() opérationnelle, avec SPF et DKIM sur le domaine d'expéditeur.

Déploiement

Le workflow .github/workflows/deploy.yml passe le test de bout en bout à chaque pull request et à chaque push sur main ; sur main, il envoie ensuite bin/, cron/, src/ et public/ en FTPS (TLS obligatoire, certificat vérifié), puis contrôle le site en lecture seule. config.php et data/ ne sont jamais touchés ; le code retiré du dépôt est supprimé du serveur (sauf public/.well-known/, public/cgi-bin/ et public/error_log, créés par le serveur).

Réglages du dépôt GitHub :

  • secrets O2_SIGN_FTP_USERNAME et O2_SIGN_FTP_PASSWORD : compte FTP dédié, cantonné au répertoire de l'application (le parent de public/) ;
  • variable O2_FTP_HOST : hôte FTP o2switch ; variable facultative O2_SIGN_FTP_DIR si l'application n'est pas à la racine du compte FTP ;
  • environnement production (créé au premier déploiement) : on peut y exiger une approbation avant chaque mise en ligne.

Avant un déploiement qui modifie le schéma, sauvegarder data/signatures.sqlite avec ses fichiers -wal et -shm.

Tests

bash tests/flow.sh : test de bout en bout (Docker requis) couvrant l'inscription, la confirmation, la modération, l'administration par clé de sécurité (clé virtuelle tests/authenticator.php : enregistrement, PIN absent, signature altérée, autre origine, rejeu, compteur, révocation), la liste publique, le retrait et les protections anti-abus.

bash tests/navigateur.sh : tests dans un vrai navigateur (Chromium piloté par Playwright, image Docker officielle, partageant le réseau du conteneur PHP pour rester sur http://localhost) : preuve de travail calculée par le navigateur, copie du lien de partage, enregistrement et connexion avec une clé de sécurité virtuelle (refusée sans code PIN), sélection par lot, aucune erreur JavaScript ni CSP. La mesure d'audience y est neutralisée. La CI les passe avant tout déploiement.

Données personnelles

Base légale : consentement. Conservation : demandes non confirmées 7 jours ; signatures pendant la campagne puis deux ans au plus. L'adresse e-mail n'est jamais publiée ni transmise. Le texte d'information affiché sur le formulaire est dans src/texts.php et doit rester aligné avec les mentions légales de www.otspi.org.

About

Application de signature du manifeste OTSPI (PHP, SQLite, double consentement) — manifesto-sign.otspi.org

Resources

Code of conduct

Contributing

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages