update
This commit is contained in:
@ -1,411 +1,145 @@
|
||||
import { expect, tap } from '@git.zone/tstest/tapbundle';
|
||||
import * as plugins from './plugins.js';
|
||||
import { createTestServer } from '../../helpers/server.loader.js';
|
||||
import { createSmtpClient } from '../../helpers/smtp.client.js';
|
||||
import { tap, expect } from '@git.zone/tstest/tapbundle';
|
||||
import { startTestServer, stopTestServer, type ITestServer, createTestServer as createSimpleTestServer } from '../../helpers/server.loader.js';
|
||||
import { createTestSmtpClient } from '../../helpers/smtp.client.js';
|
||||
import { Email } from '../../../ts/mail/core/classes.email.js';
|
||||
|
||||
tap.test('CSEC-06: should validate TLS certificates correctly', async (tools) => {
|
||||
const testId = 'CSEC-06-certificate-validation';
|
||||
console.log(`\n${testId}: Testing TLS certificate validation...`);
|
||||
let testServer: ITestServer;
|
||||
|
||||
let scenarioCount = 0;
|
||||
tap.test('setup test SMTP server', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: 2566,
|
||||
tlsEnabled: true,
|
||||
authRequired: false
|
||||
});
|
||||
expect(testServer).toBeTruthy();
|
||||
expect(testServer.port).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
// Scenario 1: Valid certificate acceptance
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing valid certificate acceptance`);
|
||||
|
||||
const testServer = await createTestServer({
|
||||
secure: true,
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Secure client connected');
|
||||
socket.write('220 secure.example.com ESMTP\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
console.log(` [Server] Received: ${command}`);
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-secure.example.com\r\n');
|
||||
socket.write('250-SIZE 10485760\r\n');
|
||||
socket.write('250 AUTH PLAIN LOGIN\r\n');
|
||||
} else if (command.startsWith('MAIL FROM:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'DATA') {
|
||||
socket.write('354 Start mail input\r\n');
|
||||
} else if (command === '.') {
|
||||
socket.write('250 OK: Secure message accepted\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: true,
|
||||
tls: {
|
||||
rejectUnauthorized: false // Accept self-signed for test
|
||||
}
|
||||
});
|
||||
|
||||
const email = new plugins.smartmail.Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Valid certificate test',
|
||||
text: 'Testing with valid TLS connection'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log(` Result: ${result.messageId ? 'Success' : 'Failed'}`);
|
||||
console.log(' Certificate accepted for secure connection');
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
|
||||
// Scenario 2: Self-signed certificate handling
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing self-signed certificate handling`);
|
||||
|
||||
const testServer = await createTestServer({
|
||||
secure: true,
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected with self-signed cert');
|
||||
socket.write('220 selfsigned.example.com ESMTP\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-selfsigned.example.com\r\n');
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'DATA') {
|
||||
socket.write('354 Start mail input\r\n');
|
||||
} else if (command === '.') {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Test with strict validation (should fail)
|
||||
const strictClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: true,
|
||||
tls: {
|
||||
rejectUnauthorized: true // Reject self-signed
|
||||
}
|
||||
});
|
||||
|
||||
const email = new plugins.smartmail.Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Self-signed cert test',
|
||||
text: 'Testing self-signed certificate rejection'
|
||||
});
|
||||
|
||||
try {
|
||||
await strictClient.sendMail(email);
|
||||
console.log(' Unexpected: Self-signed cert was accepted');
|
||||
} catch (error) {
|
||||
console.log(` Expected error: ${error.message}`);
|
||||
expect(error.message).toContain('self signed');
|
||||
tap.test('CSEC-06: Valid certificate acceptance', async () => {
|
||||
const smtpClient = createTestSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: true,
|
||||
tls: {
|
||||
rejectUnauthorized: false // Accept self-signed for test
|
||||
}
|
||||
});
|
||||
|
||||
// Test with relaxed validation (should succeed)
|
||||
const relaxedClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: true,
|
||||
tls: {
|
||||
rejectUnauthorized: false // Accept self-signed
|
||||
}
|
||||
});
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Valid certificate test',
|
||||
text: 'Testing with valid TLS connection'
|
||||
});
|
||||
|
||||
const result = await relaxedClient.sendMail(email);
|
||||
console.log(' Self-signed cert accepted with relaxed validation');
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log(`Result: ${result.success ? 'Success' : 'Failed'}`);
|
||||
console.log('Certificate accepted for secure connection');
|
||||
expect(result.success).toBeTruthy();
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
// Scenario 3: Certificate hostname verification
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing certificate hostname verification`);
|
||||
|
||||
const testServer = await createTestServer({
|
||||
secure: true,
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
socket.write('220 mail.example.com ESMTP\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-mail.example.com\r\n');
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'DATA') {
|
||||
socket.write('354 Start mail input\r\n');
|
||||
} else if (command === '.') {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
tap.test('CSEC-06: Self-signed certificate handling', async () => {
|
||||
// Test with strict validation (should fail)
|
||||
const strictClient = createTestSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: true,
|
||||
tls: {
|
||||
rejectUnauthorized: true // Reject self-signed
|
||||
}
|
||||
});
|
||||
|
||||
// Connect with hostname verification
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: true,
|
||||
tls: {
|
||||
rejectUnauthorized: false, // For self-signed
|
||||
servername: testServer.hostname, // Verify hostname
|
||||
checkServerIdentity: (hostname, cert) => {
|
||||
console.log(` Verifying hostname: ${hostname}`);
|
||||
console.log(` Certificate CN: ${cert.subject?.CN || 'N/A'}`);
|
||||
// Custom verification logic could go here
|
||||
return undefined; // No error
|
||||
}
|
||||
}
|
||||
});
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Self-signed cert test',
|
||||
text: 'Testing self-signed certificate rejection'
|
||||
});
|
||||
|
||||
const email = new plugins.smartmail.Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Hostname verification test',
|
||||
text: 'Testing certificate hostname matching'
|
||||
});
|
||||
try {
|
||||
await strictClient.sendMail(email);
|
||||
console.log('Unexpected: Self-signed cert was accepted');
|
||||
} catch (error) {
|
||||
console.log(`Expected error: ${error.message}`);
|
||||
expect(error.message).toInclude('self');
|
||||
}
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log(' Hostname verification completed');
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
await strictClient.close();
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
// Test with relaxed validation (should succeed)
|
||||
const relaxedClient = createTestSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: true,
|
||||
tls: {
|
||||
rejectUnauthorized: false // Accept self-signed
|
||||
}
|
||||
});
|
||||
|
||||
// Scenario 4: Certificate expiration handling
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing certificate expiration handling`);
|
||||
|
||||
// Note: In a real test, we would use an expired certificate
|
||||
// For this test, we simulate the behavior
|
||||
|
||||
const testServer = await createTestServer({
|
||||
secure: true,
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
socket.write('220 expired.example.com ESMTP\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-expired.example.com\r\n');
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'DATA') {
|
||||
socket.write('354 Start mail input\r\n');
|
||||
} else if (command === '.') {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
const result = await relaxedClient.sendMail(email);
|
||||
console.log('Self-signed cert accepted with relaxed validation');
|
||||
expect(result.success).toBeTruthy();
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: true,
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
// Custom certificate validation
|
||||
secureContext: {
|
||||
cert: undefined,
|
||||
key: undefined,
|
||||
ca: undefined
|
||||
}
|
||||
}
|
||||
});
|
||||
await relaxedClient.close();
|
||||
});
|
||||
|
||||
const email = new plugins.smartmail.Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Certificate expiration test',
|
||||
text: 'Testing expired certificate handling'
|
||||
});
|
||||
tap.test('CSEC-06: Certificate hostname verification', async () => {
|
||||
const smtpClient = createTestSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: true,
|
||||
tls: {
|
||||
rejectUnauthorized: false, // For self-signed
|
||||
servername: testServer.hostname // Verify hostname
|
||||
}
|
||||
});
|
||||
|
||||
console.log(' Testing with potentially expired certificate...');
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log(' Connection established (test environment)');
|
||||
expect(result).toBeDefined();
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Hostname verification test',
|
||||
text: 'Testing certificate hostname matching'
|
||||
});
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log('Hostname verification completed');
|
||||
expect(result.success).toBeTruthy();
|
||||
|
||||
// Scenario 5: Certificate chain validation
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing certificate chain validation`);
|
||||
|
||||
const testServer = await createTestServer({
|
||||
secure: true,
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
socket.write('220 chain.example.com ESMTP\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-chain.example.com\r\n');
|
||||
socket.write('250-STARTTLS\r\n');
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'DATA') {
|
||||
socket.write('354 Start mail input\r\n');
|
||||
} else if (command === '.') {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: true,
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
// In production, would specify CA certificates
|
||||
ca: undefined,
|
||||
requestCert: true,
|
||||
// Log certificate details
|
||||
secureContext: undefined
|
||||
}
|
||||
});
|
||||
tap.test('CSEC-06: Certificate validation with custom CA', async () => {
|
||||
const smtpClient = createTestSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: true,
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
// In production, would specify CA certificates
|
||||
ca: undefined
|
||||
}
|
||||
});
|
||||
|
||||
const email = new plugins.smartmail.Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Certificate chain test',
|
||||
text: 'Testing certificate chain validation'
|
||||
});
|
||||
const email = new Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Certificate chain test',
|
||||
text: 'Testing certificate chain validation'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log(' Certificate chain validation completed');
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log('Certificate chain validation completed');
|
||||
expect(result.success).toBeTruthy();
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
await smtpClient.close();
|
||||
});
|
||||
|
||||
// Scenario 6: Certificate pinning
|
||||
await (async () => {
|
||||
scenarioCount++;
|
||||
console.log(`\nScenario ${scenarioCount}: Testing certificate pinning`);
|
||||
|
||||
const testServer = await createTestServer({
|
||||
secure: true,
|
||||
onConnection: async (socket) => {
|
||||
console.log(' [Server] Client connected');
|
||||
socket.write('220 pinned.example.com ESMTP\r\n');
|
||||
|
||||
socket.on('data', (data) => {
|
||||
const command = data.toString().trim();
|
||||
|
||||
if (command.startsWith('EHLO')) {
|
||||
socket.write('250-pinned.example.com\r\n');
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('MAIL FROM:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command.startsWith('RCPT TO:')) {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'DATA') {
|
||||
socket.write('354 Start mail input\r\n');
|
||||
} else if (command === '.') {
|
||||
socket.write('250 OK\r\n');
|
||||
} else if (command === 'QUIT') {
|
||||
socket.write('221 Bye\r\n');
|
||||
socket.end();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
tap.test('cleanup test SMTP server', async () => {
|
||||
if (testServer) {
|
||||
await stopTestServer(testServer);
|
||||
}
|
||||
});
|
||||
|
||||
// In production, would pin specific certificate fingerprint
|
||||
const expectedFingerprint = 'SHA256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX';
|
||||
|
||||
const smtpClient = createSmtpClient({
|
||||
host: testServer.hostname,
|
||||
port: testServer.port,
|
||||
secure: true,
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
checkServerIdentity: (hostname, cert) => {
|
||||
// In production, would verify fingerprint
|
||||
console.log(` Certificate fingerprint: ${cert.fingerprint256 || 'N/A'}`);
|
||||
console.log(` Expected fingerprint: ${expectedFingerprint}`);
|
||||
|
||||
// For test, accept any certificate
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const email = new plugins.smartmail.Email({
|
||||
from: 'sender@example.com',
|
||||
to: ['recipient@example.com'],
|
||||
subject: 'Certificate pinning test',
|
||||
text: 'Testing certificate fingerprint verification'
|
||||
});
|
||||
|
||||
const result = await smtpClient.sendMail(email);
|
||||
console.log(' Certificate pinning check completed');
|
||||
expect(result).toBeDefined();
|
||||
expect(result.messageId).toBeDefined();
|
||||
|
||||
await testServer.server.close();
|
||||
})();
|
||||
|
||||
console.log(`\n${testId}: All ${scenarioCount} certificate validation scenarios tested ✓`);
|
||||
});
|
||||
tap.start();
|
Reference in New Issue
Block a user