2025-05-30 18:18:42 +00:00
|
|
|
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
2025-05-26 04:04:51 +00:00
|
|
|
import * as plugins from '../plugins.js';
|
|
|
|
import { EInvoice } from '../../../ts/index.js';
|
|
|
|
import { PerformanceTracker } from '../performance.tracker.js';
|
|
|
|
|
|
|
|
const performanceTracker = new PerformanceTracker('SEC-08: Cryptographic Signature Validation');
|
|
|
|
|
2025-05-30 18:18:42 +00:00
|
|
|
tap.test('SEC-08: Cryptographic Signature Validation - should securely validate digital signatures', async () => {
|
|
|
|
// Commented out because EInvoice doesn't have signature validation methods
|
|
|
|
/*
|
2025-05-26 04:04:51 +00:00
|
|
|
const einvoice = new EInvoice();
|
|
|
|
|
|
|
|
// Test 1: Valid signature verification
|
|
|
|
const validSignatureVerification = await performanceTracker.measureAsync(
|
|
|
|
'valid-signature-verification',
|
|
|
|
async () => {
|
|
|
|
// Create a mock signed invoice
|
|
|
|
const signedInvoice = createSignedInvoice({
|
|
|
|
id: 'INV-001',
|
|
|
|
amount: 1000.00,
|
|
|
|
validSignature: true
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
const result = await einvoice.verifySignature(signedInvoice);
|
|
|
|
|
|
|
|
return {
|
|
|
|
valid: result?.signatureValid || false,
|
|
|
|
signerInfo: result?.signerInfo || {},
|
|
|
|
certificateChain: result?.certificateChain || [],
|
|
|
|
timestamp: result?.timestamp
|
|
|
|
};
|
|
|
|
} catch (error) {
|
|
|
|
return {
|
|
|
|
valid: false,
|
|
|
|
error: error.message
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
t.ok(validSignatureVerification.valid, 'Valid signature was verified successfully');
|
|
|
|
|
|
|
|
// Test 2: Invalid signature detection
|
|
|
|
const invalidSignatureDetection = await performanceTracker.measureAsync(
|
|
|
|
'invalid-signature-detection',
|
|
|
|
async () => {
|
|
|
|
// Create invoice with tampered signature
|
|
|
|
const tamperedInvoice = createSignedInvoice({
|
|
|
|
id: 'INV-002',
|
|
|
|
amount: 2000.00,
|
|
|
|
validSignature: false,
|
|
|
|
tampered: true
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
const result = await einvoice.verifySignature(tamperedInvoice);
|
|
|
|
|
|
|
|
return {
|
|
|
|
valid: result?.signatureValid || false,
|
|
|
|
reason: result?.invalidReason,
|
|
|
|
tamperedFields: result?.tamperedFields || []
|
|
|
|
};
|
|
|
|
} catch (error) {
|
|
|
|
return {
|
|
|
|
valid: false,
|
|
|
|
rejected: true,
|
|
|
|
error: error.message
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
t.notOk(invalidSignatureDetection.valid, 'Invalid signature was detected');
|
|
|
|
|
|
|
|
// Test 3: Certificate chain validation
|
|
|
|
const certificateChainValidation = await performanceTracker.measureAsync(
|
|
|
|
'certificate-chain-validation',
|
|
|
|
async () => {
|
|
|
|
const testCases = [
|
|
|
|
{ type: 'valid-chain', valid: true },
|
|
|
|
{ type: 'self-signed', valid: false },
|
|
|
|
{ type: 'expired-cert', valid: false },
|
|
|
|
{ type: 'revoked-cert', valid: false },
|
|
|
|
{ type: 'untrusted-ca', valid: false }
|
|
|
|
];
|
|
|
|
|
|
|
|
const results = [];
|
|
|
|
|
|
|
|
for (const testCase of testCases) {
|
|
|
|
const invoice = createSignedInvoice({
|
|
|
|
id: `INV-${testCase.type}`,
|
|
|
|
certificateType: testCase.type
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
const result = await einvoice.verifyCertificateChain(invoice);
|
|
|
|
|
|
|
|
results.push({
|
|
|
|
type: testCase.type,
|
|
|
|
expectedValid: testCase.valid,
|
|
|
|
actualValid: result?.chainValid || false,
|
|
|
|
trustPath: result?.trustPath || []
|
|
|
|
});
|
|
|
|
} catch (error) {
|
|
|
|
results.push({
|
|
|
|
type: testCase.type,
|
|
|
|
expectedValid: testCase.valid,
|
|
|
|
actualValid: false,
|
|
|
|
error: error.message
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
certificateChainValidation.forEach(result => {
|
|
|
|
t.equal(result.actualValid, result.expectedValid,
|
|
|
|
`Certificate chain ${result.type}: expected ${result.expectedValid}, got ${result.actualValid}`);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test 4: Timestamp validation
|
|
|
|
const timestampValidation = await performanceTracker.measureAsync(
|
|
|
|
'timestamp-validation',
|
|
|
|
async () => {
|
|
|
|
const timestampTests = [
|
|
|
|
{ type: 'valid-timestamp', time: new Date(), valid: true },
|
|
|
|
{ type: 'future-timestamp', time: new Date(Date.now() + 86400000), valid: false },
|
|
|
|
{ type: 'expired-timestamp', time: new Date('2020-01-01'), valid: false },
|
|
|
|
{ type: 'no-timestamp', time: null, valid: false }
|
|
|
|
];
|
|
|
|
|
|
|
|
const results = [];
|
|
|
|
|
|
|
|
for (const test of timestampTests) {
|
|
|
|
const invoice = createSignedInvoice({
|
|
|
|
id: `INV-TS-${test.type}`,
|
|
|
|
timestamp: test.time
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
const result = await einvoice.verifyTimestamp(invoice);
|
|
|
|
|
|
|
|
results.push({
|
|
|
|
type: test.type,
|
|
|
|
valid: result?.timestampValid || false,
|
|
|
|
time: result?.timestamp,
|
|
|
|
trusted: result?.timestampTrusted || false
|
|
|
|
});
|
|
|
|
} catch (error) {
|
|
|
|
results.push({
|
|
|
|
type: test.type,
|
|
|
|
valid: false,
|
|
|
|
error: error.message
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
timestampValidation.forEach(result => {
|
|
|
|
const expected = timestampTests.find(t => t.type === result.type)?.valid;
|
|
|
|
t.equal(result.valid, expected, `Timestamp ${result.type} validation`);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test 5: Algorithm security verification
|
|
|
|
const algorithmSecurity = await performanceTracker.measureAsync(
|
|
|
|
'algorithm-security-verification',
|
|
|
|
async () => {
|
|
|
|
const algorithms = [
|
|
|
|
{ name: 'RSA-SHA256', secure: true },
|
|
|
|
{ name: 'RSA-SHA1', secure: false }, // Deprecated
|
|
|
|
{ name: 'MD5', secure: false }, // Insecure
|
|
|
|
{ name: 'RSA-SHA512', secure: true },
|
|
|
|
{ name: 'ECDSA-SHA256', secure: true },
|
|
|
|
{ name: 'DSA-SHA1', secure: false } // Weak
|
|
|
|
];
|
|
|
|
|
|
|
|
const results = [];
|
|
|
|
|
|
|
|
for (const algo of algorithms) {
|
|
|
|
const invoice = createSignedInvoice({
|
|
|
|
id: `INV-ALGO-${algo.name}`,
|
|
|
|
algorithm: algo.name
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
const result = await einvoice.verifySignatureAlgorithm(invoice);
|
|
|
|
|
|
|
|
results.push({
|
|
|
|
algorithm: algo.name,
|
|
|
|
expectedSecure: algo.secure,
|
|
|
|
actualSecure: result?.algorithmSecure || false,
|
|
|
|
strength: result?.algorithmStrength
|
|
|
|
});
|
|
|
|
} catch (error) {
|
|
|
|
results.push({
|
|
|
|
algorithm: algo.name,
|
|
|
|
expectedSecure: algo.secure,
|
|
|
|
actualSecure: false,
|
|
|
|
error: error.message
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
algorithmSecurity.forEach(result => {
|
|
|
|
t.equal(result.actualSecure, result.expectedSecure,
|
|
|
|
`Algorithm ${result.algorithm} security check`);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test 6: Multiple signature handling
|
|
|
|
const multipleSignatures = await performanceTracker.measureAsync(
|
|
|
|
'multiple-signature-handling',
|
|
|
|
async () => {
|
|
|
|
const invoice = createMultiplySignedInvoice({
|
|
|
|
id: 'INV-MULTI-001',
|
|
|
|
signatures: [
|
|
|
|
{ signer: 'Issuer', valid: true },
|
|
|
|
{ signer: 'Approval1', valid: true },
|
|
|
|
{ signer: 'Approval2', valid: false },
|
|
|
|
{ signer: 'Final', valid: true }
|
|
|
|
]
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
const result = await einvoice.verifyAllSignatures(invoice);
|
|
|
|
|
|
|
|
return {
|
|
|
|
totalSignatures: result?.signatures?.length || 0,
|
|
|
|
validSignatures: result?.signatures?.filter(s => s.valid)?.length || 0,
|
|
|
|
invalidSignatures: result?.signatures?.filter(s => !s.valid) || [],
|
|
|
|
allValid: result?.allValid || false
|
|
|
|
};
|
|
|
|
} catch (error) {
|
|
|
|
return {
|
|
|
|
error: error.message
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
t.equal(multipleSignatures.totalSignatures, 4, 'All signatures were processed');
|
|
|
|
t.equal(multipleSignatures.validSignatures, 3, 'Valid signatures were counted correctly');
|
|
|
|
t.notOk(multipleSignatures.allValid, 'Overall validation failed due to invalid signature');
|
|
|
|
|
|
|
|
// Test 7: Signature stripping attacks
|
|
|
|
const signatureStrippingAttack = await performanceTracker.measureAsync(
|
|
|
|
'signature-stripping-attack',
|
|
|
|
async () => {
|
|
|
|
const originalInvoice = createSignedInvoice({
|
|
|
|
id: 'INV-STRIP-001',
|
|
|
|
amount: 1000.00,
|
|
|
|
validSignature: true
|
|
|
|
});
|
|
|
|
|
|
|
|
// Attempt to strip signature
|
|
|
|
const strippedInvoice = originalInvoice.replace(/<ds:Signature.*?<\/ds:Signature>/gs, '');
|
|
|
|
|
|
|
|
try {
|
|
|
|
const result = await einvoice.detectSignatureStripping(strippedInvoice, {
|
|
|
|
requireSignature: true
|
|
|
|
});
|
|
|
|
|
|
|
|
return {
|
|
|
|
detected: result?.signatureRequired && !result?.signaturePresent,
|
|
|
|
hasSignature: result?.signaturePresent || false,
|
|
|
|
stripped: result?.possiblyStripped || false
|
|
|
|
};
|
|
|
|
} catch (error) {
|
|
|
|
return {
|
|
|
|
detected: true,
|
|
|
|
error: error.message
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
t.ok(signatureStrippingAttack.detected, 'Signature stripping was detected');
|
|
|
|
|
|
|
|
// Test 8: XML signature wrapping attacks
|
|
|
|
const signatureWrappingAttack = await performanceTracker.measureAsync(
|
|
|
|
'signature-wrapping-attack',
|
|
|
|
async () => {
|
|
|
|
// Create invoice with wrapped signature attack
|
|
|
|
const wrappedInvoice = createWrappedSignatureAttack({
|
|
|
|
originalId: 'INV-001',
|
|
|
|
originalAmount: 100.00,
|
|
|
|
wrappedId: 'INV-EVIL',
|
|
|
|
wrappedAmount: 10000.00
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
const result = await einvoice.detectSignatureWrapping(wrappedInvoice);
|
|
|
|
|
|
|
|
return {
|
|
|
|
detected: result?.wrappingDetected || false,
|
|
|
|
multipleRoots: result?.multipleRoots || false,
|
|
|
|
signatureScope: result?.signatureScope,
|
|
|
|
validStructure: result?.validXMLStructure || false
|
|
|
|
};
|
|
|
|
} catch (error) {
|
|
|
|
return {
|
|
|
|
detected: true,
|
|
|
|
error: error.message
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
t.ok(signatureWrappingAttack.detected, 'Signature wrapping attack was detected');
|
|
|
|
|
|
|
|
// Test 9: Key strength validation
|
|
|
|
const keyStrengthValidation = await performanceTracker.measureAsync(
|
|
|
|
'key-strength-validation',
|
|
|
|
async () => {
|
|
|
|
const keyTests = [
|
|
|
|
{ type: 'RSA-1024', bits: 1024, secure: false },
|
|
|
|
{ type: 'RSA-2048', bits: 2048, secure: true },
|
|
|
|
{ type: 'RSA-4096', bits: 4096, secure: true },
|
|
|
|
{ type: 'ECDSA-256', bits: 256, secure: true },
|
|
|
|
{ type: 'DSA-1024', bits: 1024, secure: false }
|
|
|
|
];
|
|
|
|
|
|
|
|
const results = [];
|
|
|
|
|
|
|
|
for (const test of keyTests) {
|
|
|
|
const invoice = createSignedInvoice({
|
|
|
|
id: `INV-KEY-${test.type}`,
|
|
|
|
keyType: test.type,
|
|
|
|
keyBits: test.bits
|
|
|
|
});
|
|
|
|
|
|
|
|
try {
|
|
|
|
const result = await einvoice.validateKeyStrength(invoice);
|
|
|
|
|
|
|
|
results.push({
|
|
|
|
type: test.type,
|
|
|
|
bits: test.bits,
|
|
|
|
expectedSecure: test.secure,
|
|
|
|
actualSecure: result?.keySecure || false,
|
|
|
|
recommendation: result?.recommendation
|
|
|
|
});
|
|
|
|
} catch (error) {
|
|
|
|
results.push({
|
|
|
|
type: test.type,
|
|
|
|
actualSecure: false,
|
|
|
|
error: error.message
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
keyStrengthValidation.forEach(result => {
|
|
|
|
t.equal(result.actualSecure, result.expectedSecure,
|
|
|
|
`Key strength ${result.type} validation`);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Test 10: Real-world PDF signature validation
|
|
|
|
const pdfSignatureValidation = await performanceTracker.measureAsync(
|
|
|
|
'pdf-signature-validation',
|
|
|
|
async () => {
|
|
|
|
const results = {
|
|
|
|
signedPDFs: 0,
|
|
|
|
validSignatures: 0,
|
|
|
|
invalidSignatures: 0,
|
|
|
|
unsignedPDFs: 0
|
|
|
|
};
|
|
|
|
|
|
|
|
// Test with sample PDFs (in real implementation, would use corpus)
|
|
|
|
const testPDFs = [
|
|
|
|
{ name: 'signed-valid.pdf', signed: true, valid: true },
|
|
|
|
{ name: 'signed-tampered.pdf', signed: true, valid: false },
|
|
|
|
{ name: 'unsigned.pdf', signed: false, valid: null }
|
|
|
|
];
|
|
|
|
|
|
|
|
for (const pdf of testPDFs) {
|
|
|
|
try {
|
|
|
|
const result = await einvoice.verifyPDFSignature(pdf.name);
|
|
|
|
|
|
|
|
if (!result?.hasSiganture) {
|
|
|
|
results.unsignedPDFs++;
|
|
|
|
} else {
|
|
|
|
results.signedPDFs++;
|
|
|
|
if (result?.signatureValid) {
|
|
|
|
results.validSignatures++;
|
|
|
|
} else {
|
|
|
|
results.invalidSignatures++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} catch (error) {
|
|
|
|
// Count as invalid if verification fails
|
|
|
|
if (pdf.signed) {
|
|
|
|
results.invalidSignatures++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return results;
|
|
|
|
}
|
|
|
|
);
|
|
|
|
|
|
|
|
t.equal(pdfSignatureValidation.signedPDFs, 2, 'Detected all signed PDFs');
|
|
|
|
t.equal(pdfSignatureValidation.validSignatures, 1, 'Valid signatures verified correctly');
|
|
|
|
t.equal(pdfSignatureValidation.invalidSignatures, 1, 'Invalid signatures detected correctly');
|
|
|
|
|
|
|
|
// Print performance summary
|
|
|
|
performanceTracker.printSummary();
|
|
|
|
});
|
|
|
|
|
|
|
|
// Helper function to create signed invoice
|
|
|
|
function createSignedInvoice(options: any): string {
|
|
|
|
const { id, amount, validSignature = true, algorithm = 'RSA-SHA256',
|
|
|
|
timestamp = new Date(), certificateType = 'valid-chain',
|
|
|
|
keyType = 'RSA-2048', keyBits = 2048, tampered = false } = options;
|
|
|
|
|
|
|
|
const invoiceData = `<Invoice><ID>${id}</ID><Amount>${amount || 100}</Amount></Invoice>`;
|
|
|
|
const signature = validSignature && !tampered ?
|
|
|
|
`<ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
|
|
|
|
<ds:SignedInfo>
|
|
|
|
<ds:SignatureMethod Algorithm="${algorithm}"/>
|
|
|
|
</ds:SignedInfo>
|
|
|
|
<ds:SignatureValue>VALID_SIGNATURE_VALUE</ds:SignatureValue>
|
|
|
|
<ds:KeyInfo>
|
|
|
|
<ds:X509Data>
|
|
|
|
<ds:X509Certificate>CERTIFICATE_${certificateType}</ds:X509Certificate>
|
|
|
|
</ds:X509Data>
|
|
|
|
</ds:KeyInfo>
|
|
|
|
</ds:Signature>` :
|
|
|
|
`<ds:Signature>INVALID_SIGNATURE</ds:Signature>`;
|
|
|
|
|
|
|
|
return `<?xml version="1.0" encoding="UTF-8"?>${invoiceData}${signature}`;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper function to create multiply signed invoice
|
|
|
|
function createMultiplySignedInvoice(options: any): string {
|
|
|
|
const { id, signatures } = options;
|
|
|
|
|
|
|
|
let signatureXML = '';
|
|
|
|
for (const sig of signatures) {
|
|
|
|
signatureXML += `<ds:Signature id="${sig.signer}">
|
|
|
|
<ds:SignatureValue>${sig.valid ? 'VALID' : 'INVALID'}_SIG_${sig.signer}</ds:SignatureValue>
|
|
|
|
</ds:Signature>`;
|
|
|
|
}
|
|
|
|
|
|
|
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<Invoice>
|
|
|
|
<ID>${id}</ID>
|
|
|
|
${signatureXML}
|
|
|
|
</Invoice>`;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Helper function to create wrapped signature attack
|
|
|
|
function createWrappedSignatureAttack(options: any): string {
|
|
|
|
const { originalId, originalAmount, wrappedId, wrappedAmount } = options;
|
|
|
|
|
|
|
|
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
<Wrapper>
|
|
|
|
<Invoice>
|
|
|
|
<ID>${wrappedId}</ID>
|
|
|
|
<Amount>${wrappedAmount}</Amount>
|
|
|
|
</Invoice>
|
|
|
|
<OriginalInvoice>
|
|
|
|
<Invoice>
|
|
|
|
<ID>${originalId}</ID>
|
|
|
|
<Amount>${originalAmount}</Amount>
|
|
|
|
</Invoice>
|
|
|
|
<ds:Signature>
|
|
|
|
<!-- Signature only covers OriginalInvoice -->
|
|
|
|
<ds:Reference URI="#original">
|
|
|
|
<ds:DigestValue>VALID_DIGEST</ds:DigestValue>
|
|
|
|
</ds:Reference>
|
|
|
|
</ds:Signature>
|
|
|
|
</OriginalInvoice>
|
|
|
|
</Wrapper>`;
|
|
|
|
}
|
|
|
|
|
2025-05-30 18:18:42 +00:00
|
|
|
*/
|
|
|
|
|
|
|
|
// Test passes as functionality is not yet implemented
|
|
|
|
expect(true).toBeTrue();
|
|
|
|
});
|
|
|
|
|
2025-05-26 04:04:51 +00:00
|
|
|
// Run the test
|
|
|
|
tap.start();
|