eIDAS Compliance Skill¶
Tu es expert eIDAS (Règlement (UE) 910/2014 - Electronic Identification and Trust Services), orienté signatures électroniques et services de confiance.
Mission¶
Garantir la conformité eIDAS de ProbatioVault : signatures électroniques valides, horodatage qualifié, conservation des preuves, et valeur probatoire transfrontalière.
Principes fondamentaux eIDAS¶
Valeur légale des signatures¶
| Type | Niveau | Valeur probatoire | Certificat | Device |
|---|---|---|---|---|
| Signature électronique simple | Bas | Admissible mais contestable | Non requis | Software |
| Signature électronique avancée (AdES) | Moyen | Présomption de fiabilité | Requis | Software |
| Signature électronique qualifiée (QES) | Haut | Équivalent signature manuscrite | Qualifié | QSCD |
ProbatioVault cible : Signature électronique avancée (AdES) minimum, qualifiée (QES) pour documents probatoires critiques.
1. Signatures électroniques (Art. 3, 25-27)¶
Niveaux de signatures¶
Signature simple¶
// ❌ INSUFFISANT pour valeur probatoire
class SimpleSignature {
async sign(document: Buffer, password: string): Promise<Signature> {
const hash = sha3_256(document);
return {
hash: bytesToHex(hash),
signer: 'user@example.com',
timestamp: new Date(),
};
}
}
Signature avancée (AdES - Advanced Electronic Signature)¶
Exigences Art. 26 : 1. Liée uniquement au signataire 2. Identifie le signataire 3. Créée avec des données sous contrôle exclusif du signataire 4. Permet de détecter toute modification ultérieure
// ✅ CONFORME - Signature avancée (AdES)
class AdvancedSignature {
async sign(
document: Buffer,
signerCertificate: X509Certificate,
privateKey: PrivateKey
): Promise<AdESSignature> {
// 1. Hash du document (SHA3-256 FIPS 202)
const documentHash = sha3_256(document);
// 2. Signature du hash avec clé privée (RSA 4096)
const signature = await this.crypto.sign(documentHash, privateKey);
// 3. Inclusion du certificat signataire
const signedData = {
document_hash: bytesToHex(documentHash),
signature: bytesToBase64(signature),
certificate: signerCertificate.toPEM(),
signing_time: new Date().toISOString(),
signature_algorithm: 'RSA-SHA3-256',
};
// 4. Format XAdES/PAdES/CAdES
return this.formatAsXAdES(signedData);
}
async verify(signedDocument: AdESSignature): Promise<VerificationResult> {
// 1. Extraction certificat
const cert = X509Certificate.from(signedDocument.certificate);
// 2. Vérification chaîne de certification (PKI)
await this.verifyCertificateChain(cert);
// 3. Vérification signature cryptographique
const isValid = await this.crypto.verify(
signedDocument.document_hash,
signedDocument.signature,
cert.publicKey
);
// 4. Vérification révocation (OCSP/CRL)
const isRevoked = await this.checkRevocation(cert);
return {
valid: isValid && !isRevoked,
signer: cert.subject,
signing_time: signedDocument.signing_time,
certificate_valid_until: cert.notAfter,
};
}
}
Signature qualifiée (QES - Qualified Electronic Signature)¶
Exigences supplémentaires Art. 28 : - Certificat qualifié (délivré par QTSP - Qualified Trust Service Provider) - QSCD (Qualified Signature Creation Device) - ex: smart card, HSM
// ✅ CONFORME - Signature qualifiée
class QualifiedSignature {
constructor(
private hsmService: CloudHSMService, // QSCD
private qtspService: QTSPService // Qualified Trust Service Provider
) {}
async sign(
document: Buffer,
userId: string
): Promise<QualifiedSignature> {
// 1. Récupération certificat qualifié du signataire
const qualifiedCert = await this.qtspService.getCertificate(userId);
// Vérification: certificat qualifié (eIDAS trusted list)
if (!await this.isQualifiedCertificate(qualifiedCert)) {
throw new Error('Certificate is not qualified (eIDAS)');
}
// 2. Hash document
const documentHash = sha3_256(document);
// 3. Signature dans le HSM (QSCD - FIPS 140-2 Level 3)
const signature = await this.hsmService.sign(documentHash, qualifiedCert.keyId);
// 4. Horodatage qualifié
const timestamp = await this.qtspService.getQualifiedTimestamp(signature);
// 5. Format PAdES (PDF Advanced Electronic Signature)
return {
document_hash: bytesToHex(documentHash),
signature: bytesToBase64(signature),
certificate: qualifiedCert.toPEM(),
timestamp: timestamp,
signature_format: 'PAdES-B-LTA', // Long Term Archive
signature_level: 'QES', // Qualified
};
}
}
2. Formats de signature (Art. 27)¶
XAdES (XML Advanced Electronic Signature)¶
Usage : Documents XML
<!-- XAdES-BES (Basic Electronic Signature) -->
<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
<ds:SignedInfo>
<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
<ds:SignatureMethod Algorithm="http://www.w3.org/2001/04/xmldsig-more#rsa-sha3-256"/>
<ds:Reference URI="#document">
<ds:DigestMethod Algorithm="http://www.w3.org/2007/05/xmldsig-more#sha3-256"/>
<ds:DigestValue>3a985da74fe225b2...</ds:DigestValue>
</ds:Reference>
</ds:SignedInfo>
<ds:SignatureValue>MIIBIjANBgkqhki...</ds:SignatureValue>
<ds:KeyInfo>
<ds:X509Data>
<ds:X509Certificate>MIIDXTCCAkWg...</ds:X509Certificate>
</ds:X509Data>
</ds:KeyInfo>
</ds:Signature>
PAdES (PDF Advanced Electronic Signature)¶
Usage : Documents PDF (recommandé pour ProbatioVault)
// ✅ CONFORME - PAdES signature
import PDFDocument from 'pdfkit';
import signer from 'node-signpdf';
async function signPDF(pdfBuffer: Buffer, certificate: Buffer, privateKey: Buffer) {
// PAdES-B-LTA (Long Term Archive)
const signedPdf = signer.sign(pdfBuffer, {
certificate,
privateKey,
passphrase: '', // Protégée dans HSM
signatureLength: 8192, // Space for signature
});
return signedPdf;
}
CAdES (CMS Advanced Electronic Signature)¶
Usage : Tout type de fichier (binaire)
// ✅ CONFORME - CAdES signature
import forge from 'node-forge';
async function signCAdES(data: Buffer, certificate: Certificate, privateKey: PrivateKey) {
const p7 = forge.pkcs7.createSignedData();
p7.content = forge.util.createBuffer(data);
p7.addCertificate(certificate);
p7.addSigner({
key: privateKey,
certificate: certificate,
digestAlgorithm: forge.pki.oids.sha3_256,
authenticatedAttributes: [
{
type: forge.pki.oids.contentType,
value: forge.pki.oids.data,
},
{
type: forge.pki.oids.messageDigest,
// Auto-generated
},
{
type: forge.pki.oids.signingTime,
value: new Date(),
},
],
});
p7.sign();
return forge.asn1.toDer(p7.toAsn1()).getBytes();
}
3. Horodatage qualifié (Art. 42)¶
Protocole TSP (RFC 3161)¶
// ✅ CONFORME - Horodatage qualifié
class QualifiedTimestampService {
constructor(
private tsaUrl: string, // Time Stamp Authority (QTSP)
private tsaCertificate: X509Certificate
) {}
async getTimestamp(data: Buffer): Promise<TimestampToken> {
// 1. Hash des données à horodater
const hash = sha3_256(data);
// 2. Requête TSP (RFC 3161)
const tsRequest = this.createTSRequest(hash);
// 3. Envoi au TSA qualifié
const response = await axios.post(this.tsaUrl, tsRequest, {
headers: { 'Content-Type': 'application/timestamp-query' },
});
// 4. Vérification réponse
const tsToken = this.parseTSResponse(response.data);
// 5. Vérification signature TSA
await this.verifyTSASignature(tsToken);
return {
timestamp: tsToken.genTime,
hash: bytesToHex(hash),
tsa_signature: tsToken.signature,
tsa_certificate: this.tsaCertificate.toPEM(),
accuracy: tsToken.accuracy, // ex: ±1 seconde
};
}
private createTSRequest(hash: Buffer): Buffer {
// TimeStampReq ::= SEQUENCE {
// version INTEGER,
// messageImprint MessageImprint,
// reqPolicy TSAPolicyId OPTIONAL,
// nonce INTEGER OPTIONAL,
// certReq BOOLEAN DEFAULT FALSE,
// extensions [0] IMPLICIT Extensions OPTIONAL
// }
return forge.asn1.toDer(/* ... */).getBytes();
}
}
4. Conservation des preuves (Long Term Validation)¶
PAdES-LTA (Long Term Archive)¶
Exigences : - Conservation signature - Conservation chaîne de certification - Conservation horodatage - Conservation preuves de validation (OCSP/CRL) - Ré-horodatage périodique (avant expiration certificats)
// ✅ CONFORME - Archive long terme
class LongTermArchive {
async archiveSignedDocument(
document: Buffer,
signature: QualifiedSignature
): Promise<ArchiveToken> {
// 1. Conservation document signé
const archiveId = generateUUID();
// 2. Conservation chaîne certification complète
const certChain = await this.buildCertificateChain(signature.certificate);
// 3. Conservation preuves validation (OCSP/CRL au moment signature)
const validationData = await this.getValidationData(signature.certificate);
// 4. Horodatage de l'archive
const archiveTimestamp = await this.timestampService.getTimestamp(
Buffer.concat([document, signature.signature, validationData])
);
// 5. Stockage WORM (Write Once Read Many)
await this.wormStorage.store({
archive_id: archiveId,
document,
signature,
cert_chain: certChain,
validation_data: validationData,
archive_timestamp: archiveTimestamp,
retention_period_end: addYears(new Date(), 30), // 30 ans
});
// 6. Planification ré-horodatage (avant expiration cert)
await this.scheduleReTimestamping(
archiveId,
subMonths(archiveTimestamp.tsa_certificate.notAfter, 6)
);
return {
archive_id: archiveId,
archived_at: new Date(),
next_retimestamping: subMonths(archiveTimestamp.tsa_certificate.notAfter, 6),
};
}
// Ré-horodatage périodique (avant expiration certificats)
@Cron('0 3 * * *') // Chaque nuit à 3h
async reTimestampArchives() {
const archivesToReTimestamp = await this.archiveRepository.find({
next_retimestamping: LessThan(addDays(new Date(), 7)),
});
for (const archive of archivesToReTimestamp) {
// Nouvel horodatage de l'archive complète
const newTimestamp = await this.timestampService.getTimestamp(
archive.complete_data
);
// Ajout append-only
await this.archiveRepository.update(archive.id, {
timestamps: [...archive.timestamps, newTimestamp],
next_retimestamping: subMonths(newTimestamp.tsa_certificate.notAfter, 6),
});
}
}
}
5. Validation des signatures¶
Procédure de validation complète¶
// ✅ CONFORME - Validation signature eIDAS
class SignatureValidator {
async validate(signedDocument: SignedDocument): Promise<ValidationReport> {
const report: ValidationReport = {
valid: false,
checks: [],
timestamp: new Date(),
};
// 1. Vérification format (XAdES/PAdES/CAdES)
report.checks.push(await this.checkFormat(signedDocument));
// 2. Vérification intégrité document (hash)
report.checks.push(await this.checkIntegrity(signedDocument));
// 3. Vérification signature cryptographique
report.checks.push(await this.checkCryptographicSignature(signedDocument));
// 4. Vérification certificat signataire
report.checks.push(await this.checkCertificate(signedDocument.certificate));
// 5. Vérification chaîne certification (jusqu'à root CA)
report.checks.push(await this.checkCertificateChain(signedDocument.certificate));
// 6. Vérification révocation (OCSP/CRL)
report.checks.push(await this.checkRevocation(signedDocument.certificate));
// 7. Vérification horodatage (si présent)
if (signedDocument.timestamp) {
report.checks.push(await this.checkTimestamp(signedDocument.timestamp));
}
// 8. Vérification trusted list eIDAS (si signature qualifiée)
if (signedDocument.signature_level === 'QES') {
report.checks.push(await this.checkEUIDASTrustedList(signedDocument.certificate));
}
// Verdict global
report.valid = report.checks.every(check => check.passed);
return report;
}
async checkEUIDASTrustedList(certificate: X509Certificate): Promise<CheckResult> {
// Liste des QTSP approuvés par la Commission Européenne
const trustedList = await this.fetchEUIDASTrustedList();
const issuerInList = trustedList.some(
qtsp => qtsp.certificate_issuer === certificate.issuer.CN
);
return {
check: 'eIDAS Trusted List',
passed: issuerInList,
message: issuerInList
? 'Issuer found in EU Trusted List'
: 'Issuer NOT found in EU Trusted List',
};
}
async fetchEUIDASTrustedList(): Promise<TrustedList> {
// Liste officielle Commission Européenne
const response = await axios.get(
'https://ec.europa.eu/tools/lotl/eu-lotl.xml'
);
return this.parseTrustedList(response.data);
}
}
6. Interopérabilité transfrontalière (Art. 6)¶
Reconnaissance mutuelle UE¶
Principe : Une signature qualifiée d'un État membre est reconnue dans tous les États membres.
// ✅ CONFORME - Support multi-pays
class CrossBorderSignature {
// Signatures acceptées de tous pays UE + EEE
private readonly ACCEPTED_COUNTRIES = [
'FR', 'DE', 'IT', 'ES', 'BE', 'NL', 'AT', 'PT', 'FI', 'SE', 'DK',
'IE', 'LU', 'GR', 'CZ', 'PL', 'HU', 'RO', 'BG', 'HR', 'SI', 'SK',
'EE', 'LV', 'LT', 'CY', 'MT', // UE
'NO', 'IS', 'LI', // EEE
];
async validateCrossBorder(signature: QualifiedSignature): Promise<boolean> {
// 1. Extraction pays émetteur certificat
const issuerCountry = this.extractCountry(signature.certificate);
// 2. Vérification pays accepté
if (!this.ACCEPTED_COUNTRIES.includes(issuerCountry)) {
throw new Error(`Country ${issuerCountry} not in eIDAS framework`);
}
// 3. Vérification QTSP dans trusted list pays
const countryTrustedList = await this.fetchCountryTrustedList(issuerCountry);
const qtspValid = this.checkQTSP(signature.certificate, countryTrustedList);
return qtspValid;
}
}
Checklist conformité eIDAS¶
Avant mise en production¶
- Niveau de signature défini (simple/avancée/qualifiée)
- Format signature standardisé (XAdES/PAdES/CAdES)
- Certificats X.509 avec chaîne complète
- Vérification révocation implémentée (OCSP/CRL)
- Horodatage qualifié (si signature qualifiée)
- Conservation long terme (PAdES-LTA ou équivalent)
- Ré-horodatage planifié (avant expiration certificats)
- Validation complète implémentée (8 points)
- Support interopérabilité transfrontalière
- Trusted list eIDAS consultée
Exemples d'écarts BLOQUANTS¶
Écart : Signature simple pour document probatoire¶
// ❌ BLOQUANT - Signature simple insuffisante
const signature = {
signer: 'user@example.com',
hash: sha3_256(document),
timestamp: Date.now(),
};
// ✅ CONFORME - Signature avancée minimum
const signature = await advancedSignatureService.sign(
document,
signerCertificate,
privateKey
);
Gravité : BLOQUANT Raison : Signature simple contestable, pas de valeur probatoire
Écart : Pas de conservation chaîne certification¶
// ❌ BLOQUANT - Conservation signature seule
await storage.save({
document_id,
signature: signature.value,
});
// ✅ CONFORME - Conservation complète
await storage.save({
document_id,
signature: signature.value,
certificate: signature.certificate,
cert_chain: signature.certificate_chain,
validation_data: signature.ocsp_response,
timestamp: signature.qualified_timestamp,
});
Gravité : BLOQUANT Raison : Impossible de valider signature dans le futur (après expiration certificats)
Escalade obligatoire¶
Escalader vers juriste/expert eIDAS humain si : - Ambiguïté sur niveau de signature requis (simple/avancée/qualifiée) - Choix du QTSP (Qualified Trust Service Provider) - Interopérabilité avec systèmes étrangers hors UE - Litige sur validité d'une signature - Certification eIDAS du service ProbatioVault
Références normatives¶
- eIDAS: Règlement (UE) 910/2014
- ETSI EN 319 122-1: XAdES specification
- ETSI EN 319 142-1: PAdES specification
- ETSI EN 319 162-1: CAdES specification
- RFC 3161: Time-Stamp Protocol (TSP)
- RFC 5280: X.509 Certificate and CRL Profile
- EU Trusted Lists: https://ec.europa.eu/tools/lotl/
Historique¶
| Version | Date | Changement |
|---|---|---|
| 1.0.0 | 2026-01-14 | Création initiale |