- Fix HomePage.vue to update signatureCount after user signs - Fix EmbedPage.vue to use signatureCount from API instead of signatures.length - Adapt E2E tests to reflect signature visibility security model - Create missing API documentation (docs/en/api.md, docs/fr/api.md) - Document Docker healthcheck in deployment guides - Document signature endpoint access control rules - Fix MagicLink auto-detection description in READMEs
6.5 KiB
Signatures Cryptographiques
Flow complet de signature avec Ed25519 et garanties de sécurité.
Principe
Ackify utilise Ed25519 (courbe elliptique) pour créer des signatures cryptographiques non-répudiables.
Garanties :
- ✅ Non-répudiation - La signature prouve l'identité du signataire
- ✅ Intégrité - Le hash SHA-256 détecte toute modification
- ✅ Horodatage immutable - Triggers PostgreSQL empêchent la backdating
- ✅ Unicité - Une seule signature par utilisateur/document
Flow de Signature
1. Utilisateur accède au document
https://sign.company.com/?doc=policy_2025
Le frontend Vue.js charge et affiche :
- Titre du document (si metadata existe)
- Nombre de signatures existantes
- Bouton "Sign this document"
2. Vérification de session
Le frontend appelle :
GET /api/v1/users/me
Si non connecté → Redirection OAuth2 Si connecté → Affichage du bouton de signature
3. Signature
Au clic sur "Sign", le frontend :
- Obtient un token CSRF :
GET /api/v1/csrf
- Envoie la signature :
POST /api/v1/signatures
Content-Type: application/json
X-CSRF-Token: abc123
{
"doc_id": "policy_2025"
}
4. Backend Processing
Le backend (Go) :
- Vérifie la session - Utilisateur authentifié
- Génère la signature Ed25519 :
payload := fmt.Sprintf("%s:%s:%s:%s", docID, userSub, userEmail, timestamp) hash := sha256.Sum256([]byte(payload)) signature := ed25519.Sign(privateKey, hash[:]) - Calcule prev_hash - Hash de la dernière signature (chaînage)
- Insère en base :
INSERT INTO signatures (doc_id, user_sub, user_email, signed_at, payload_hash, signature, nonce, prev_hash) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) - Retourne la signature au frontend
5. Confirmation
Le frontend affiche :
- ✅ Signature confirmée
- Horodatage
- Lien vers la liste des signatures
Structure de la Signature
{
"docId": "policy_2025",
"userEmail": "alice@company.com",
"userName": "Alice Smith",
"signedAt": "2025-01-15T14:30:00Z",
"payloadHash": "sha256:e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"signature": "ed25519:3045022100...",
"nonce": "abc123xyz",
"prevHash": "sha256:prev..."
}
Champs :
payloadHash- SHA-256 du payload (doc_id:user_sub:email:timestamp)signature- Signature Ed25519 en base64nonce- Protection anti-replayprevHash- Hash de la signature précédente (blockchain-like)
Vérification de Signature
Manuelle (via API)
GET /api/v1/documents/policy_2025/signatures
Contrôle d'Accès
L'endpoint de liste des signatures a des restrictions d'accès pour protéger la vie privée des utilisateurs :
| Type d'utilisateur | Ce qu'il voit |
|---|---|
| Propriétaire du document (created_by) | Toutes les signatures avec emails |
| Admin (dans ACKIFY_ADMIN_EMAILS) | Toutes les signatures avec emails |
| Utilisateur authentifié (non propriétaire) | Uniquement sa propre signature (s'il a signé) |
| Non authentifié | Liste vide |
Note
: Le compteur de signatures est toujours visible par tous via le champ
signatureCountdans les réponses document. Seule la liste détaillée (avec les adresses email) est restreinte.
Exemples de réponses :
En tant que propriétaire/admin :
{
"data": [
{"userEmail": "alice@example.com", "signedAt": "..."},
{"userEmail": "bob@example.com", "signedAt": "..."},
{"userEmail": "charlie@example.com", "signedAt": "..."}
]
}
En tant qu'utilisateur authentifié non-propriétaire (qui a signé) :
{
"data": [
{"userEmail": "bob@example.com", "signedAt": "..."}
]
}
En tant que non-authentifié :
{
"data": []
}
Le même contrôle d'accès s'applique à l'endpoint des signataires attendus (/expected-signers).
Programmation (Go)
import "crypto/ed25519"
func VerifySignature(publicKey ed25519.PublicKey, payload, signature []byte) bool {
hash := sha256.Sum256(payload)
return ed25519.Verify(publicKey, hash[:], signature)
}
Contraintes PostgreSQL
Une signature par user/document
UNIQUE (doc_id, user_sub)
Comportement :
- Si l'utilisateur tente de signer 2 fois → Erreur 409 Conflict
- Le frontend détecte cela et affiche "Already signed"
Immutabilité de created_at
Trigger PostgreSQL :
CREATE TRIGGER prevent_signatures_created_at_update
BEFORE UPDATE ON signatures
FOR EACH ROW
EXECUTE FUNCTION prevent_created_at_update();
Garantie : Impossible de backdater une signature.
Chaînage (Blockchain-like)
Chaque signature référence la précédente via prev_hash :
Signature 1 → hash1
Signature 2 → hash2 (prev_hash = hash1)
Signature 3 → hash3 (prev_hash = hash2)
Détection de tampering :
- Si une signature est modifiée, le
prev_hashde la suivante ne correspond plus - Permet de détecter toute modification de l'historique
Sécurité
Clé Privée Ed25519
Générée automatiquement au premier démarrage ou via :
ACKIFY_ED25519_PRIVATE_KEY=$(openssl rand -base64 64)
Important :
- La clé privée ne quitte jamais le serveur
- Stockée en mémoire uniquement (pas en base)
- Backup requis si vous voulez garder la même clé après redéploiement
Protection Anti-Replay
Le nonce unique empêche la réutilisation d'une signature :
nonce := fmt.Sprintf("%s-%d", userSub, time.Now().UnixNano())
Rate Limiting
Les signatures sont limitées à 100 requêtes/minute par IP.
Cas d'Usage
Validation de lecture de politique
Document: "Security Policy 2025"
URL: https://sign.company.com/?doc=security_policy_2025
Workflow :
- Admin envoie le lien aux employés
- Chaque employé clique, lit, et signe
- Admin voit la completion dans
/admin
Accusé de réception formation
Document: "GDPR Training 2025"
Expected signers: 50 employés
Features :
- Tracking de complétion (42/50 = 84%)
- Rappels email automatiques
- Export des signatures
Acknowledgment contractuel
Document: "Terms of Service v3"
Checksum: SHA-256 du PDF
Vérification :
- Utilisateur calcule le checksum du PDF
- Compare avec la metadata stockée
- Signe si identique
Voir Checksums pour plus de détails.
API Reference
Voir API Documentation pour tous les endpoints liés aux signatures.