update
This commit is contained in:
487
test/suite/einvoice_security/test.sec-08.signature-validation.ts
Normal file
487
test/suite/einvoice_security/test.sec-08.signature-validation.ts
Normal file
@ -0,0 +1,487 @@
|
||||
import { tap } from '@git.zone/tstest/tapbundle';
|
||||
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');
|
||||
|
||||
tap.test('SEC-08: Cryptographic Signature Validation - should securely validate digital signatures', async (t) => {
|
||||
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>`;
|
||||
}
|
||||
|
||||
// Run the test
|
||||
tap.start();
|
Reference in New Issue
Block a user