import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as plugins from '../plugins.js'; import { EInvoice } from '../../../ts/index.js'; import { CorpusLoader } from '../corpus.loader.js'; import { PerformanceTracker } from '../performance.tracker.js'; tap.test('PDF-10: PDF Signature Validation - should validate digital signatures in PDFs', async (t) => { // PDF-10: Verify digital signature validation and preservation // This test ensures signed PDFs are handled correctly const performanceTracker = new PerformanceTracker('PDF-10: PDF Signature Validation'); const corpusLoader = new CorpusLoader(); t.test('Detect signed PDFs', async () => { const startTime = performance.now(); const { PDFDocument } = plugins; // Create a PDF that simulates signature structure const pdfDoc = await PDFDocument.create(); const page = pdfDoc.addPage([595, 842]); page.drawText('Digitally Signed Invoice', { x: 50, y: 750, size: 20 }); // Add signature placeholder page.drawRectangle({ x: 400, y: 50, width: 150, height: 75, borderColor: { red: 0, green: 0, blue: 0 }, borderWidth: 1 }); page.drawText('Digital Signature', { x: 420, y: 85, size: 10 }); page.drawText('[Signed Document]', { x: 420, y: 65, size: 8 }); // Add invoice XML const xmlContent = ` SIGNED-001 2025-01-25 EUR signature.p7s SHA256:abc123... `; await pdfDoc.attach( Buffer.from(xmlContent, 'utf8'), 'invoice.xml', { mimeType: 'application/xml', description: 'Signed invoice data' } ); // Note: pdf-lib doesn't support actual digital signatures // Real signature would require specialized libraries const pdfBytes = await pdfDoc.save(); // Test signature detection const einvoice = new EInvoice(); await einvoice.loadFromPdfBuffer(pdfBytes); console.log('Created PDF with signature placeholder'); const elapsed = performance.now() - startTime; performanceTracker.addMeasurement('detect-signed', elapsed); }); t.test('Signature metadata structure', async () => { const startTime = performance.now(); // Simulate signature metadata that might be found in signed PDFs const signatureMetadata = { signer: { name: 'John Doe', email: 'john.doe@company.com', organization: 'ACME Corporation', organizationUnit: 'Finance Department' }, certificate: { issuer: 'GlobalSign CA', serialNumber: '01:23:45:67:89:AB:CD:EF', validFrom: '2024-01-01T00:00:00Z', validTo: '2026-01-01T00:00:00Z', algorithm: 'SHA256withRSA' }, timestamp: { time: '2025-01-25T10:30:00Z', authority: 'GlobalSign TSA', hash: 'SHA256' }, signatureDetails: { reason: 'Invoice Approval', location: 'Munich, Germany', contactInfo: '+49 89 12345678' } }; const { PDFDocument } = plugins; const pdfDoc = await PDFDocument.create(); // Add metadata as document properties pdfDoc.setTitle('Signed Invoice 2025-001'); pdfDoc.setAuthor(signatureMetadata.signer.name); pdfDoc.setSubject(`Signed by ${signatureMetadata.signer.organization}`); pdfDoc.setKeywords(['signed', 'verified', 'invoice']); pdfDoc.setCreator('EInvoice Signature System'); const page = pdfDoc.addPage(); page.drawText('Invoice with Signature Metadata', { x: 50, y: 750, size: 18 }); // Display signature info on page let yPosition = 650; page.drawText('Digital Signature Information:', { x: 50, y: yPosition, size: 14 }); yPosition -= 30; page.drawText(`Signed by: ${signatureMetadata.signer.name}`, { x: 70, y: yPosition, size: 10 }); yPosition -= 20; page.drawText(`Organization: ${signatureMetadata.signer.organization}`, { x: 70, y: yPosition, size: 10 }); yPosition -= 20; page.drawText(`Date: ${signatureMetadata.timestamp.time}`, { x: 70, y: yPosition, size: 10 }); yPosition -= 20; page.drawText(`Certificate: ${signatureMetadata.certificate.issuer}`, { x: 70, y: yPosition, size: 10 }); yPosition -= 20; page.drawText(`Reason: ${signatureMetadata.signatureDetails.reason}`, { x: 70, y: yPosition, size: 10 }); const pdfBytes = await pdfDoc.save(); console.log('Created PDF with signature metadata structure'); const elapsed = performance.now() - startTime; performanceTracker.addMeasurement('signature-metadata', elapsed); }); t.test('Multiple signatures handling', async () => { const startTime = performance.now(); const { PDFDocument } = plugins; const pdfDoc = await PDFDocument.create(); const page = pdfDoc.addPage(); page.drawText('Multi-Signature Invoice', { x: 50, y: 750, size: 20 }); // Simulate multiple signature fields const signatures = [ { name: 'Creator Signature', signer: 'Invoice System', date: '2025-01-25T09:00:00Z', position: { x: 50, y: 150 } }, { name: 'Approval Signature', signer: 'Finance Manager', date: '2025-01-25T10:00:00Z', position: { x: 220, y: 150 } }, { name: 'Verification Signature', signer: 'Auditor', date: '2025-01-25T11:00:00Z', position: { x: 390, y: 150 } } ]; // Draw signature boxes signatures.forEach(sig => { page.drawRectangle({ x: sig.position.x, y: sig.position.y, width: 150, height: 80, borderColor: { red: 0, green: 0, blue: 0 }, borderWidth: 1 }); page.drawText(sig.name, { x: sig.position.x + 10, y: sig.position.y + 60, size: 10 }); page.drawText(sig.signer, { x: sig.position.x + 10, y: sig.position.y + 40, size: 8 }); page.drawText(sig.date, { x: sig.position.x + 10, y: sig.position.y + 20, size: 8 }); }); // Add invoice with signature references const xmlContent = ` MULTI-SIG-001 SIG-1 Invoice System SIG-2 Finance Manager SIG-3 Auditor `; await pdfDoc.attach( Buffer.from(xmlContent, 'utf8'), 'invoice.xml', { mimeType: 'application/xml' } ); const pdfBytes = await pdfDoc.save(); console.log('Created PDF with multiple signature placeholders'); const elapsed = performance.now() - startTime; performanceTracker.addMeasurement('multiple-signatures', elapsed); }); t.test('Signature validation status', async () => { const startTime = performance.now(); // Simulate different signature validation statuses const validationStatuses = [ { status: 'VALID', color: { red: 0, green: 0.5, blue: 0 }, message: 'Signature Valid' }, { status: 'INVALID', color: { red: 0.8, green: 0, blue: 0 }, message: 'Signature Invalid' }, { status: 'UNKNOWN', color: { red: 0.5, green: 0.5, blue: 0 }, message: 'Signature Unknown' }, { status: 'EXPIRED', color: { red: 0.8, green: 0.4, blue: 0 }, message: 'Certificate Expired' } ]; const { PDFDocument } = plugins; for (const valStatus of validationStatuses) { const pdfDoc = await PDFDocument.create(); const page = pdfDoc.addPage(); page.drawText(`Invoice - Signature ${valStatus.status}`, { x: 50, y: 750, size: 20 }); // Draw status indicator page.drawRectangle({ x: 450, y: 740, width: 100, height: 30, color: valStatus.color, borderColor: { red: 0, green: 0, blue: 0 }, borderWidth: 1 }); page.drawText(valStatus.message, { x: 460, y: 750, size: 10, color: { red: 1, green: 1, blue: 1 } }); const xmlContent = ` SIG-${valStatus.status} ${valStatus.status} ${valStatus.message} `; await pdfDoc.attach( Buffer.from(xmlContent, 'utf8'), 'invoice.xml', { mimeType: 'application/xml' } ); const pdfBytes = await pdfDoc.save(); console.log(`Created PDF with signature status: ${valStatus.status}`); } const elapsed = performance.now() - startTime; performanceTracker.addMeasurement('validation-status', elapsed); }); t.test('Signature preservation during operations', async () => { const startTime = performance.now(); const { PDFDocument } = plugins; // Create original "signed" PDF const originalPdf = await PDFDocument.create(); originalPdf.setTitle('Original Signed Document'); originalPdf.setAuthor('Original Signer'); originalPdf.setSubject('This document has been digitally signed'); const page = originalPdf.addPage(); page.drawText('Original Signed Invoice', { x: 50, y: 750, size: 20 }); // Add signature visual page.drawRectangle({ x: 400, y: 50, width: 150, height: 75, borderColor: { red: 0, green: 0.5, blue: 0 }, borderWidth: 2 }); page.drawText('✓ Digitally Signed', { x: 420, y: 85, size: 12, color: { red: 0, green: 0.5, blue: 0 } }); const originalBytes = await originalPdf.save(); // Process through EInvoice const einvoice = new EInvoice(); // Add new XML while preserving signature const xmlContent = ` PRESERVE-SIG-001 Added to signed document `; try { await einvoice.loadFromPdfBuffer(originalBytes); // In a real implementation, this would need to preserve signatures console.log('Note: Adding content to signed PDFs typically invalidates signatures'); console.log('Incremental updates would be needed to preserve signature validity'); } catch (error) { console.log('Signature preservation challenge:', error.message); } const elapsed = performance.now() - startTime; performanceTracker.addMeasurement('signature-preservation', elapsed); }); t.test('Timestamp validation', async () => { const startTime = performance.now(); const { PDFDocument } = plugins; const pdfDoc = await PDFDocument.create(); const page = pdfDoc.addPage(); page.drawText('Time-stamped Invoice', { x: 50, y: 750, size: 20 }); // Simulate timestamp information const timestamps = [ { type: 'Document Creation', time: '2025-01-25T09:00:00Z', authority: 'Internal TSA' }, { type: 'Signature Timestamp', time: '2025-01-25T10:30:00Z', authority: 'Qualified TSA Provider' }, { type: 'Archive Timestamp', time: '2025-01-25T11:00:00Z', authority: 'Long-term Archive TSA' } ]; let yPos = 650; page.drawText('Timestamp Information:', { x: 50, y: yPos, size: 14 }); timestamps.forEach(ts => { yPos -= 30; page.drawText(`${ts.type}:`, { x: 70, y: yPos, size: 10 }); yPos -= 20; page.drawText(`Time: ${ts.time}`, { x: 90, y: yPos, size: 9 }); yPos -= 15; page.drawText(`TSA: ${ts.authority}`, { x: 90, y: yPos, size: 9 }); }); const xmlContent = ` TIMESTAMP-001 ${timestamps.map(ts => ` ${ts.authority} `).join('')} `; await pdfDoc.attach( Buffer.from(xmlContent, 'utf8'), 'invoice.xml', { mimeType: 'application/xml' } ); const pdfBytes = await pdfDoc.save(); console.log('Created PDF with timestamp information'); const elapsed = performance.now() - startTime; performanceTracker.addMeasurement('timestamp-validation', elapsed); }); t.test('Corpus signed PDF detection', async () => { const startTime = performance.now(); let signedCount = 0; let processedCount = 0; const signatureIndicators: string[] = []; const files = await corpusLoader.getAllFiles(); const pdfFiles = files.filter(f => f.endsWith('.pdf')); // Check PDFs for signature indicators const sampleSize = Math.min(50, pdfFiles.length); const sample = pdfFiles.slice(0, sampleSize); for (const file of sample) { try { const content = await corpusLoader.readFile(file); // Look for signature indicators in PDF content const pdfString = content.toString('binary'); const indicators = [ '/Type /Sig', '/ByteRange', '/SubFilter', '/adbe.pkcs7', '/ETSI.CAdES', 'SignatureField', 'DigitalSignature' ]; let hasSignature = false; for (const indicator of indicators) { if (pdfString.includes(indicator)) { hasSignature = true; if (!signatureIndicators.includes(indicator)) { signatureIndicators.push(indicator); } break; } } if (hasSignature) { signedCount++; console.log(`Potential signed PDF: ${file}`); } processedCount++; } catch (error) { console.log(`Error checking ${file}:`, error.message); } } console.log(`Corpus signature analysis (${processedCount} PDFs):`); console.log(`- PDFs with signature indicators: ${signedCount}`); console.log('Signature indicators found:', signatureIndicators); const elapsed = performance.now() - startTime; performanceTracker.addMeasurement('corpus-signed-pdfs', elapsed); }); // Print performance summary performanceTracker.printSummary(); // Performance assertions const avgTime = performanceTracker.getAverageTime(); expect(avgTime).toBeLessThan(300); // Signature operations should be reasonably fast }); tap.start();