Flow complet de signature avec Ed25519 et garanties de sécurité.
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
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"
Le frontend appelle :
GET /api/v1/users/meSi non connecté → Redirection OAuth2 Si connecté → Affichage du bouton de 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"
}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
Le frontend affiche :
- ✅ Signature confirmée
- Horodatage
- Lien vers la liste des signatures
{
"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)
GET /api/v1/documents/policy_2025/signaturesL'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).
import "crypto/ed25519"
func VerifySignature(publicKey ed25519.PublicKey, payload, signature []byte) bool {
hash := sha256.Sum256(payload)
return ed25519.Verify(publicKey, hash[:], signature)
}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"
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.
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
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
Le nonce unique empêche la réutilisation d'une signature :
nonce := fmt.Sprintf("%s-%d", userSub, time.Now().UnixNano())Les signatures sont limitées à 100 requêtes/minute par IP.
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
Document: "GDPR Training 2025"
Expected signers: 50 employés
Features :
- Tracking de complétion (42/50 = 84%)
- Rappels email automatiques
- Export des signatures
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.
Voir API Documentation pour tous les endpoints liés aux signatures.