import { expect, tap } from '@git.zone/tstest/tapbundle'; import * as plugins from '../plugins.js'; import { EInvoice } from '../../../ts/index.js'; import { CorpusLoader } from '../../helpers/corpus.loader.js'; import { PerformanceTracker as StaticPerformanceTracker } from '../performance.tracker.js'; import { rgb } from 'pdf-lib'; // PDF-10: Verify digital signature validation and preservation // This test ensures signed PDFs are handled correctly // Simple instance-based performance tracker for this test class SimplePerformanceTracker { private measurements: Map = new Map(); private name: string; constructor(name: string) { this.name = name; } addMeasurement(key: string, time: number): void { if (!this.measurements.has(key)) { this.measurements.set(key, []); } this.measurements.get(key)!.push(time); } getAverageTime(): number { let total = 0; let count = 0; for (const times of this.measurements.values()) { for (const time of times) { total += time; count++; } } return count > 0 ? total / count : 0; } printSummary(): void { console.log(`\n${this.name} - Performance Summary:`); for (const [key, times] of this.measurements) { const avg = times.reduce((a, b) => a + b, 0) / times.length; const min = Math.min(...times); const max = Math.max(...times); console.log(` ${key}: avg=${avg.toFixed(2)}ms, min=${min.toFixed(2)}ms, max=${max.toFixed(2)}ms (${times.length} runs)`); } console.log(` Overall average: ${this.getAverageTime().toFixed(2)}ms`); } } const performanceTracker = new SimplePerformanceTracker('PDF-10: PDF Signature Validation'); tap.test('PDF-10: 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: rgb(0, 0, 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 = await EInvoice.fromPdf(pdfBytes); console.log('Created PDF with signature placeholder'); const elapsed = performance.now() - startTime; performanceTracker.addMeasurement('detect-signed', elapsed); }); tap.test('PDF-10: 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); }); tap.test('PDF-10: 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: rgb(0, 0, 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); }); tap.test('PDF-10: Signature Validation Status', async () => { const startTime = performance.now(); // Simulate different signature validation statuses const validationStatuses = [ { status: 'VALID', color: rgb(0, 0.5, 0), message: 'Signature Valid' }, { status: 'INVALID', color: rgb(0.8, 0, 0), message: 'Signature Invalid' }, { status: 'UNKNOWN', color: rgb(0.5, 0.5, 0), message: 'Signature Unknown' }, { status: 'EXPIRED', color: rgb(0.8, 0.4, 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: rgb(0, 0, 0), borderWidth: 1 }); page.drawText(valStatus.message, { x: 460, y: 750, size: 10, color: rgb(1, 1, 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); }); tap.test('PDF-10: 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: rgb(0, 0.5, 0), borderWidth: 2 }); page.drawText('[OK] Digitally Signed', { x: 420, y: 85, size: 12, color: rgb(0, 0.5, 0) }); const originalBytes = await originalPdf.save(); // Process through EInvoice // Add new XML while preserving signature const xmlContent = ` PRESERVE-SIG-001 Added to signed document `; try { const einvoice = await EInvoice.fromPdf(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); }); tap.test('PDF-10: 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); }); tap.test('PDF-10: Corpus Signed PDF Detection', async () => { const startTime = performance.now(); let signedCount = 0; let processedCount = 0; const signatureIndicators: string[] = []; // Get PDF files from different categories const categories = ['ZUGFERD_V1_CORRECT', 'ZUGFERD_V2_CORRECT', 'ZUGFERD_V2_FAIL', 'UNSTRUCTURED'] as const; const allPdfFiles: Array<{ path: string; size: number }> = []; for (const category of categories) { try { const files = await CorpusLoader.loadCategory(category); const pdfFiles = files.filter(f => f.path.toLowerCase().endsWith('.pdf')); allPdfFiles.push(...pdfFiles); } catch (error) { console.log(`Could not load category ${category}: ${error.message}`); } } // Check PDFs for signature indicators const sampleSize = Math.min(50, allPdfFiles.length); const sample = allPdfFiles.slice(0, sampleSize); for (const file of sample) { try { const content = await CorpusLoader.loadFile(file.path); // 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.path}`); } processedCount++; } catch (error) { console.log(`Error checking ${file.path}:`, 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); }); tap.test('PDF-10: Performance Summary', async () => { // Print performance summary performanceTracker.printSummary(); // Performance assertions const avgTime = performanceTracker.getAverageTime(); expect(avgTime).toBeLessThan(300); // Signature operations should be reasonably fast }); tap.start();