einvoice/test/suite/einvoice_pdf-operations/test.pdf-10.signature-validation.ts
2025-05-25 19:45:37 +00:00

501 lines
15 KiB
TypeScript

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 = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID>SIGNED-001</ID>
<IssueDate>2025-01-25</IssueDate>
<DocumentCurrencyCode>EUR</DocumentCurrencyCode>
<DigitalSignatureAttachment>
<ExternalReference>
<URI>signature.p7s</URI>
<DocumentHash>SHA256:abc123...</DocumentHash>
</ExternalReference>
</DigitalSignatureAttachment>
</Invoice>`;
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 = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice xmlns="urn:oasis:names:specification:ubl:schema:xsd:Invoice-2">
<ID>MULTI-SIG-001</ID>
<Signature>
<ID>SIG-1</ID>
<SignatoryParty>
<PartyName><Name>Invoice System</Name></PartyName>
</SignatoryParty>
</Signature>
<Signature>
<ID>SIG-2</ID>
<SignatoryParty>
<PartyName><Name>Finance Manager</Name></PartyName>
</SignatoryParty>
</Signature>
<Signature>
<ID>SIG-3</ID>
<SignatoryParty>
<PartyName><Name>Auditor</Name></PartyName>
</SignatoryParty>
</Signature>
</Invoice>`;
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 = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice>
<ID>SIG-${valStatus.status}</ID>
<SignatureValidation>
<Status>${valStatus.status}</Status>
<Message>${valStatus.message}</Message>
</SignatureValidation>
</Invoice>`;
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 = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice>
<ID>PRESERVE-SIG-001</ID>
<Note>Added to signed document</Note>
</Invoice>`;
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 = `<?xml version="1.0" encoding="UTF-8"?>
<Invoice>
<ID>TIMESTAMP-001</ID>
<Timestamps>
${timestamps.map(ts => `
<Timestamp type="${ts.type}">
<Time>${ts.time}</Time>
<Authority>${ts.authority}</Authority>
</Timestamp>`).join('')}
</Timestamps>
</Invoice>`;
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();