Aller au contenu

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