- Page publique /verify/[id] affichant Odentas Seal, TSA, certificat - API /api/signatures/create-verification pour créer preuves - Générateur PDF de preuve avec QR code (jsPDF) - Hook useSignatureProof() pour intégration facile - Table Supabase signature_verifications avec RLS public - Page de test /test-signature-verification - Documentation complète du système Les signataires peuvent scanner le QR code ou visiter l'URL pour vérifier l'authenticité et l'intégrité de leur document signé.
289 lines
8.2 KiB
Markdown
289 lines
8.2 KiB
Markdown
# Système de Vérification de Signature Électronique
|
|
|
|
## Vue d'ensemble
|
|
|
|
Ce système permet aux signataires de **vérifier l'authenticité et l'intégrité** de leurs documents signés électroniquement via une page web publique accessible par **URL ou QR code**.
|
|
|
|
## Architecture
|
|
|
|
### 1. Page de Vérification Publique
|
|
**Fichier**: `app/verify/[id]/page.tsx`
|
|
|
|
- Route dynamique: `https://espace-paie.odentas.com/verify/{uuid}`
|
|
- Accessible sans authentification (RLS public en lecture)
|
|
- Affiche tous les détails techniques de la signature
|
|
|
|
### 2. Table Supabase
|
|
**Fichier**: `supabase/migrations/20251028_signature_verifications.sql`
|
|
|
|
```sql
|
|
signature_verifications
|
|
├── id (UUID) → Identifiant unique de vérification
|
|
├── document_name → Nom du document signé
|
|
├── pdf_url → URL du PDF signé
|
|
├── signed_at → Date/heure de signature
|
|
├── signer_name → Nom du signataire
|
|
├── signer_email → Email du signataire
|
|
├── signature_hash → Hash SHA-256 du contenu signé
|
|
├── signature_hex → Signature complète en hexadécimal
|
|
├── certificate_info (JSONB) → Infos du certificat
|
|
├── timestamp (JSONB) → Horodatage TSA
|
|
├── verification_status (JSONB) → Statuts de validation
|
|
├── contract_id → Lien vers le contrat
|
|
└── organization_id → Lien vers l'organisation
|
|
```
|
|
|
|
### 3. API de Création
|
|
**Fichier**: `app/api/signatures/create-verification/route.ts`
|
|
|
|
```typescript
|
|
POST /api/signatures/create-verification
|
|
Body: {
|
|
document_name: string,
|
|
pdf_url: string,
|
|
signer_name: string,
|
|
signer_email: string,
|
|
signature_hash: string,
|
|
organization_id: string,
|
|
// Optionnel:
|
|
signature_hex?: string,
|
|
certificate_info?: object,
|
|
timestamp?: object,
|
|
contract_id?: string
|
|
}
|
|
|
|
Returns: {
|
|
verification_id: string,
|
|
verification_url: string,
|
|
qr_code_data_url: string (PNG en base64)
|
|
}
|
|
```
|
|
|
|
### 4. Générateur de PDF de Preuve
|
|
**Fichier**: `lib/signature-proof-pdf.ts`
|
|
|
|
Génère un PDF A4 professionnel contenant:
|
|
- En-tête Odentas Sign
|
|
- Informations du document signé
|
|
- **QR code** (70x70mm, centré)
|
|
- URL de vérification
|
|
- Détails techniques (hash, algorithme, certificat)
|
|
- Note explicative sur le certificat auto-signé
|
|
|
|
### 5. Hook React
|
|
**Fichier**: `hooks/useSignatureProof.ts`
|
|
|
|
```typescript
|
|
const { createSignatureProof, isCreating } = useSignatureProof();
|
|
|
|
const proof = await createSignatureProof({
|
|
document_name: "Contrat CDDU",
|
|
pdf_url: "https://...",
|
|
signer_name: "Jean Dupont",
|
|
signer_email: "jean@example.com",
|
|
signature_hash: "dafe9a5e...",
|
|
organization_id: "uuid"
|
|
});
|
|
|
|
// Retourne:
|
|
// - verification_url
|
|
// - qr_code_data_url
|
|
// - proof_pdf_blob
|
|
```
|
|
|
|
## Workflow d'Utilisation
|
|
|
|
### Lors de la Signature d'un Document
|
|
|
|
1. **Le document est signé** avec Odentas Sign (Lambda PAdES)
|
|
2. **Extraction des données** :
|
|
- Hash SHA-256 du contenu signé
|
|
- Certificat de signature
|
|
- Horodatage TSA (si présent)
|
|
3. **Appel API** `/api/signatures/create-verification`
|
|
4. **Génération** :
|
|
- Entrée dans `signature_verifications`
|
|
- QR code pointant vers `/verify/{id}`
|
|
- PDF de preuve avec QR code
|
|
5. **Téléchargement** :
|
|
- Document signé (PDF avec signature PAdES)
|
|
- Preuve de signature (PDF avec QR code)
|
|
|
|
### Vérification par le Signataire
|
|
|
|
1. **Scanner le QR code** ou visiter l'URL
|
|
2. **Page publique** affiche :
|
|
- ✅ Statut global (Valide / Techniquement Valide)
|
|
- 📄 Informations du document
|
|
- 🛡️ **Odentas Seal** (sceau électronique)
|
|
- Format PAdES-BASELINE-B
|
|
- Intégrité vérifiée (hash SHA-256)
|
|
- Algorithme RSASSA-PSS
|
|
- Détails du certificat
|
|
- 🕐 **Odentas TSA** (horodatage)
|
|
- RFC 3161 conforme
|
|
- Autorité de temps
|
|
- Empreinte horodatée
|
|
- 🔍 **Vérification technique**
|
|
- Structure PAdES valide
|
|
- ByteRange correct
|
|
- signing-certificate-v2 présent
|
|
- MessageDigest intact
|
|
- Statut du certificat (auto-signé ou qualifié)
|
|
|
|
## Éléments Affichés
|
|
|
|
### Odentas Seal
|
|
- **Format** : PAdES-BASELINE-B (ETSI TS 102 778)
|
|
- **Intégrité** : Hash SHA-256 du document
|
|
- **Algorithme** : RSASSA-PSS avec SHA-256
|
|
- **Certificat** :
|
|
- Émetteur
|
|
- Sujet
|
|
- Période de validité
|
|
- Numéro de série
|
|
|
|
### Odentas TSA (si présent)
|
|
- **Standard** : RFC 3161
|
|
- **Autorité** : URL du TSA
|
|
- **Timestamp** : Date/heure certifiée
|
|
- **Empreinte** : Hash horodaté
|
|
|
|
### Vérification Technique
|
|
- ✅ Structure PAdES valide
|
|
- ✅ ByteRange correct et complet
|
|
- ✅ Attribut signing-certificate-v2 présent
|
|
- ✅ MessageDigest intact
|
|
- ⚠️ Certificat auto-signé (ou ✅ si qualifié)
|
|
|
|
## Statuts de Validation
|
|
|
|
### "Signature Valide" ✅
|
|
- Certificat **qualifié** reconnu par les autorités européennes
|
|
- Tous les contrôles techniques passent
|
|
- **Valeur légale** : QES (Qualified Electronic Signature)
|
|
|
|
### "Signature Techniquement Valide" ⚠️
|
|
- Certificat **auto-signé** (Odentas Media SAS)
|
|
- Tous les contrôles techniques passent
|
|
- **Valeur légale** : SES (Simple Electronic Signature)
|
|
- Note explicative affichée
|
|
|
|
## Sécurité
|
|
|
|
### Row Level Security (RLS)
|
|
```sql
|
|
-- Lecture publique (vérification accessible à tous)
|
|
CREATE POLICY "Vérifications publiques" ON signature_verifications
|
|
FOR SELECT USING (true);
|
|
|
|
-- Seul le système peut créer/modifier
|
|
CREATE POLICY "Système peut gérer" ON signature_verifications
|
|
FOR ALL USING (auth.uid() IS NOT NULL);
|
|
```
|
|
|
|
### Données Publiques
|
|
Les informations exposées sont **volontairement publiques** :
|
|
- Nom du signataire
|
|
- Email du signataire
|
|
- Nom du document
|
|
- Hash SHA-256
|
|
- Certificat de signature
|
|
|
|
**Aucune donnée sensible** (contenu du document, IBAN, etc.) n'est stockée.
|
|
|
|
## Intégration dans l'Application
|
|
|
|
### Dans les Contrats CDDU/RG
|
|
|
|
Après signature via Docuseal + Odentas Sign :
|
|
|
|
```typescript
|
|
import { useSignatureProof } from "@/hooks/useSignatureProof";
|
|
|
|
const { createSignatureProof } = useSignatureProof();
|
|
|
|
// Après réception du document signé
|
|
const proof = await createSignatureProof({
|
|
document_name: `Contrat ${employee.prenom} ${employee.nom}`,
|
|
pdf_url: signedPdfUrl,
|
|
signer_name: `${employee.prenom} ${employee.nom}`,
|
|
signer_email: employee.email,
|
|
signature_hash: extractedHash,
|
|
contract_id: contract.id,
|
|
organization_id: contract.organization_id,
|
|
});
|
|
|
|
// Télécharger les deux fichiers:
|
|
// 1. Document signé (PDF PAdES)
|
|
// 2. Preuve de signature (PDF avec QR code)
|
|
downloadFile(signedPdfUrl, "contrat-signe.pdf");
|
|
downloadFile(URL.createObjectURL(proof.proof_pdf_blob), "preuve-signature.pdf");
|
|
```
|
|
|
|
## Design
|
|
|
|
### Page de Vérification
|
|
- **Layout** : Gradient slate 50→100
|
|
- **Cards** : Blanc avec shadow-xl et border-radius 16px
|
|
- **Statuts** :
|
|
- ✅ Vert (green-600) pour valide
|
|
- ⚠️ Orange (orange-600) pour techniquement valide
|
|
- ❌ Rouge (red-600) pour invalide
|
|
- **Icônes** : Lucide React (CheckCircle2, Shield, Clock, FileText)
|
|
|
|
### PDF de Preuve
|
|
- **Format** : A4 portrait
|
|
- **En-tête** : Indigo (Odentas branding)
|
|
- **QR Code** : 70x70mm, centré, haute qualité
|
|
- **Sections** :
|
|
- Document signé (fond slate-100)
|
|
- QR code avec URL
|
|
- Détails techniques
|
|
- Note importante (fond orange-50)
|
|
|
|
## Variables d'Environnement
|
|
|
|
```env
|
|
NEXT_PUBLIC_APP_URL=https://espace-paie.odentas.com
|
|
```
|
|
|
|
Utilisée pour générer les URLs de vérification.
|
|
|
|
## Migration Supabase
|
|
|
|
```bash
|
|
# Appliquer la migration
|
|
supabase db push
|
|
|
|
# Ou manuellement
|
|
psql -h db.xxx.supabase.co -U postgres -d postgres -f supabase/migrations/20251028_signature_verifications.sql
|
|
```
|
|
|
|
## Exemples d'URLs
|
|
|
|
```
|
|
https://espace-paie.odentas.com/verify/550e8400-e29b-41d4-a716-446655440000
|
|
```
|
|
|
|
## TODO / Améliorations Futures
|
|
|
|
- [ ] Support multi-signatures (plusieurs signataires)
|
|
- [ ] Historique de vérifications (analytics)
|
|
- [ ] Notifications email avec lien de vérification
|
|
- [ ] API publique de vérification (pour intégrations tierces)
|
|
- [ ] Support certificats qualifiés (badge "Qualifié eIDAS")
|
|
- [ ] Archivage long terme (LTV avec DSS/LTA)
|
|
- [ ] Export PDF/A pour archivage légal
|
|
|
|
## Conformité
|
|
|
|
- ✅ **PAdES-BASELINE-B** : ETSI TS 102 778
|
|
- ✅ **RFC 3161** : Horodatage électronique
|
|
- ✅ **RFC 5035** : signing-certificate-v2
|
|
- ✅ **RGPD** : Données publiques consenties
|
|
- ⚠️ **eIDAS** : SES (auto-signé) ou QES (avec certificat qualifié)
|
|
|
|
---
|
|
|
|
**Odentas Media SAS** - Signature électronique sécurisée
|