/** * Test complet du système de signature et vérification * * Ce script : * 1. Lit le fichier test-contrat.pdf * 2. Appelle la Lambda Odentas Sign pour signer * 3. Extrait le hash de signature du PDF signé * 4. Upload le PDF signé sur S3 * 5. Crée l'entrée de vérification via l'API * 6. Génère le PDF de preuve avec QR code * 7. Sauvegarde les fichiers résultants */ import fs from 'fs'; import path from 'path'; import { fileURLToPath } from 'url'; import crypto from 'crypto'; import AWS from 'aws-sdk'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); // Configuration AWS AWS.config.update({ region: 'eu-west-3', accessKeyId: process.env.AWS_ACCESS_KEY_ID, secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY, }); const lambda = new AWS.Lambda(); const s3 = new AWS.S3(); // Configuration const TEST_PDF_PATH = path.join(__dirname, 'test-contrat.pdf'); const OUTPUT_DIR = path.join(__dirname, 'test-signature-output'); const S3_BUCKET = 'odentas-espace-paie-documents'; async function main() { console.log('🚀 Test complet du système de signature électronique\n'); // Créer le dossier de sortie if (!fs.existsSync(OUTPUT_DIR)) { fs.mkdirSync(OUTPUT_DIR); } // Étape 1 : Lire le PDF à signer console.log('📄 Étape 1 : Lecture du PDF à signer...'); const pdfBuffer = fs.readFileSync(TEST_PDF_PATH); console.log(` ✅ PDF lu (${pdfBuffer.length} bytes)\n`); // Étape 2 : Upload du PDF source sur S3 console.log('☁️ Étape 2 : Upload du PDF source sur S3...'); const sourceKey = await uploadToS3ForSigning(pdfBuffer); console.log(` ✅ S3 Key : ${sourceKey}\n`); // Étape 3 : Signer le PDF avec Odentas Sign console.log('✍️ Étape 3 : Signature du PDF avec Odentas Sign...'); const signedS3Key = await signPdfWithLambda(sourceKey); console.log(` ✅ PDF signé : ${signedS3Key}\n`); // Étape 4 : Télécharger le PDF signé console.log('📥 Étape 4 : Téléchargement du PDF signé...'); const signedPdfBuffer = await downloadFromS3(signedS3Key); const signedPdfPath = path.join(OUTPUT_DIR, 'test-contrat-signe.pdf'); fs.writeFileSync(signedPdfPath, signedPdfBuffer); console.log(` ✅ PDF signé sauvegardé : ${signedPdfPath}`); console.log(` 📊 Taille : ${signedPdfBuffer.length} bytes\n`); // Étape 5 : Extraire le hash de signature console.log('🔍 Étape 5 : Extraction du hash de signature...'); const signatureHash = extractSignatureHash(signedPdfBuffer); console.log(` ✅ Hash SHA-256 : ${signatureHash}\n`); // Étape 6 : Générer URL présignée console.log('🔗 Étape 6 : Génération URL présignée...'); const s3Url = await getPresignedUrl(signedS3Key); console.log(` ✅ URL S3 : ${s3Url}\n`); // Étape 7 : Créer l'entrée de vérification console.log('🔐 Étape 7 : Création de la preuve de signature...'); const verificationData = { document_name: 'Test Contrat CDDU - Jean Dupont', pdf_url: s3Url, signer_name: 'Jean Dupont', signer_email: 'jean.dupont@example.com', signature_hash: signatureHash, signature_hex: signedPdfBuffer.toString('hex').substring(0, 200), // Premiers 200 chars certificate_info: { issuer: 'CN=Odentas Media SAS, O=Odentas Media, C=FR', subject: 'CN=Jean Dupont, O=Odentas Media SAS', valid_from: new Date().toISOString(), valid_until: new Date(Date.now() + 365 * 24 * 60 * 60 * 1000).toISOString(), serial_number: crypto.randomBytes(8).toString('hex'), }, timestamp: { tsa_url: 'freetsa.org/tsr', timestamp: new Date().toISOString(), hash: signatureHash, }, organization_id: '550e8400-e29b-41d4-a716-446655440000', // UUID test }; console.log(' 📝 Données de vérification préparées\n'); // Étape 8 : Générer le PDF de preuve (simulation) console.log('📄 Étape 8 : Génération du PDF de preuve...'); console.log(' ℹ️ Le PDF de preuve sera généré par l\'API Next.js avec le QR code'); console.log(' 📍 Emplacement S3 : s3://odentas-sign/evidence/proofs/'); console.log(` 📝 Nom du fichier : TEST-${Date.now()}.pdf`); console.log(' ✅ Structure prête\n'); // Étape 9 : Afficher les résultats console.log('✅ TEST COMPLET TERMINÉ !\n'); console.log('📦 Fichiers générés :'); console.log(` • PDF signé : ${signedPdfPath}`); console.log(` • URL S3 : ${s3Url}`); console.log(`\n🔗 Prochaines étapes :`); console.log(' 1. Appliquer la migration Supabase'); console.log(' 2. Lancer le serveur dev : npm run dev'); console.log(' 3. Visiter : http://localhost:3000/test-signature-verification'); console.log(' 4. Coller ces données dans le formulaire pour créer la preuve\n'); // Sauvegarder les données de test const testDataPath = path.join(OUTPUT_DIR, 'verification-data.json'); fs.writeFileSync(testDataPath, JSON.stringify(verificationData, null, 2)); console.log(` 📄 Données sauvegardées : ${testDataPath}\n`); } async function signPdfWithLambda(sourceKey) { const params = { FunctionName: 'odentas-pades-sign', InvocationType: 'RequestResponse', Payload: JSON.stringify({ sourceKey: sourceKey, requestRef: `TEST-${Date.now()}`, }), }; console.log(' ⏳ Appel de la Lambda odentas-pades-sign...'); try { const result = await lambda.invoke(params).promise(); if (result.FunctionError) { const errorPayload = JSON.parse(result.Payload); throw new Error(`Lambda error: ${errorPayload.errorMessage}`); } const response = JSON.parse(result.Payload); if (response.statusCode !== 200) { const body = JSON.parse(response.body); throw new Error(`Lambda returned status ${response.statusCode}: ${JSON.stringify(body)}`); } const body = JSON.parse(response.body); if (!body.signed_pdf_s3_key) { throw new Error(`Missing signed_pdf_s3_key in response. Body: ${JSON.stringify(body)}`); } console.log(` 📊 SHA-256: ${body.sha256}`); return body.signed_pdf_s3_key; } catch (error) { console.error(' ❌ Erreur lors de la signature:', error.message); throw error; } } function extractSignatureHash(pdfBuffer) { // Extraire le contenu signé via ByteRange const pdfString = pdfBuffer.toString('latin1'); // Trouver le ByteRange const byteRangeMatch = pdfString.match(/\/ByteRange\s*\[\s*(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s*\]/); if (!byteRangeMatch) { console.warn(' ⚠️ ByteRange non trouvé, calcul du hash sur tout le PDF'); return crypto.createHash('sha256').update(pdfBuffer).digest('hex'); } const [_, o1, l1, o2, l2] = byteRangeMatch.map(Number); console.log(` 📍 ByteRange trouvé : [${o1}, ${l1}, ${o2}, ${l2}]`); // Extraire les deux segments const segment1 = pdfBuffer.slice(o1, o1 + l1); const segment2 = pdfBuffer.slice(o2, o2 + l2); // Calculer le hash const hash = crypto.createHash('sha256'); hash.update(segment1); hash.update(segment2); return hash.digest('hex'); } async function uploadToS3ForSigning(buffer) { const filename = `source/test-contrat-${Date.now()}.pdf`; const params = { Bucket: 'odentas-sign', // Bucket de la Lambda Key: filename, Body: buffer, ContentType: 'application/pdf', }; console.log(` ⏳ Upload vers s3://odentas-sign/${filename}...`); try { await s3.putObject(params).promise(); return filename; } catch (error) { console.error(' ❌ Erreur lors de l\'upload S3:', error.message); throw error; } } async function downloadFromS3(key) { const params = { Bucket: 'odentas-sign', Key: key, }; console.log(` ⏳ Téléchargement depuis s3://odentas-sign/${key}...`); try { const data = await s3.getObject(params).promise(); return data.Body; } catch (error) { console.error(' ❌ Erreur lors du téléchargement S3:', error.message); throw error; } } async function getPresignedUrl(key) { const params = { Bucket: 'odentas-sign', Key: key, Expires: 7 * 24 * 60 * 60, // 7 jours }; return s3.getSignedUrl('getObject', params); } async function uploadToS3(buffer, prefix) { const filename = `${prefix}/test-contrat-${Date.now()}.pdf`; const params = { Bucket: S3_BUCKET, Key: filename, Body: buffer, ContentType: 'application/pdf', ACL: 'private', }; console.log(` ⏳ Upload vers s3://${S3_BUCKET}/${filename}...`); try { await s3.putObject(params).promise(); // Générer une URL présignée (valide 7 jours) const presignedUrl = s3.getSignedUrl('getObject', { Bucket: S3_BUCKET, Key: filename, Expires: 7 * 24 * 60 * 60, // 7 jours }); return presignedUrl; } catch (error) { console.error(' ❌ Erreur lors de l\'upload S3:', error.message); throw error; } } // Exécuter main().catch(error => { console.error('\n❌ ERREUR FATALE:', error); process.exit(1); });