dcrouter/test/suite/smtpclient_security/test.csec-07.cipher-suites.ts

507 lines
17 KiB
TypeScript
Raw Normal View History

2025-05-24 17:00:59 +00:00
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';
tap.test('CSEC-07: should handle cipher suites correctly', async (tools) => {
const testId = 'CSEC-07-cipher-suites';
console.log(`\n${testId}: Testing cipher suite handling...`);
let scenarioCount = 0;
// Scenario 1: Strong cipher suite negotiation
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing strong cipher suite negotiation`);
const testServer = await createTestServer({
secure: true,
tlsOptions: {
// Configure strong ciphers only
ciphers: 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:ECDHE-RSA-AES256-GCM-SHA384',
honorCipherOrder: true,
minVersion: 'TLSv1.2'
},
onConnection: async (socket) => {
console.log(' [Server] Client connected with strong ciphers');
// Log cipher info if available
const tlsSocket = socket as any;
if (tlsSocket.getCipher) {
const cipher = tlsSocket.getCipher();
console.log(` [Server] Negotiated cipher: ${cipher.name} (${cipher.version})`);
}
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 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: Message accepted with strong encryption\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,
// Prefer strong ciphers
ciphers: 'HIGH:!aNULL:!MD5:!3DES',
minVersion: 'TLSv1.2'
}
});
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Strong cipher test',
text: 'Testing with strong cipher suites'
});
const result = await smtpClient.sendMail(email);
console.log(' Successfully negotiated strong cipher');
expect(result).toBeDefined();
expect(result.messageId).toBeDefined();
await testServer.server.close();
})();
// Scenario 2: Weak cipher rejection
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing weak cipher rejection`);
const testServer = await createTestServer({
secure: true,
tlsOptions: {
// Only allow strong ciphers
ciphers: 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256',
honorCipherOrder: true,
minVersion: 'TLSv1.2'
},
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 secure.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-secure.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();
}
});
}
});
// Try to connect with weak ciphers only (should fail)
const weakClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: true,
tls: {
rejectUnauthorized: false,
// Try to use weak ciphers
ciphers: 'DES-CBC3-SHA:RC4-SHA',
maxVersion: 'TLSv1.0'
}
});
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Weak cipher test',
text: 'Testing weak cipher rejection'
});
try {
await weakClient.sendMail(email);
console.log(' Unexpected: Weak cipher was accepted');
} catch (error) {
console.log(` Expected error: ${error.message}`);
expect(error.message).toMatch(/handshake|cipher|ssl/i);
}
// Connect with acceptable ciphers
const strongClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: true,
tls: {
rejectUnauthorized: false,
ciphers: 'HIGH:!aNULL',
minVersion: 'TLSv1.2'
}
});
const result = await strongClient.sendMail(email);
console.log(' Successfully connected with strong ciphers');
expect(result).toBeDefined();
await testServer.server.close();
})();
// Scenario 3: Cipher suite priority testing
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing cipher suite priority`);
const preferredCiphers = [
'TLS_AES_256_GCM_SHA384',
'TLS_AES_128_GCM_SHA256',
'ECDHE-RSA-AES256-GCM-SHA384',
'ECDHE-RSA-AES128-GCM-SHA256'
];
const testServer = await createTestServer({
secure: true,
tlsOptions: {
ciphers: preferredCiphers.join(':'),
honorCipherOrder: true, // Server chooses cipher
minVersion: 'TLSv1.2'
},
onConnection: async (socket) => {
console.log(' [Server] Client connected');
const tlsSocket = socket as any;
if (tlsSocket.getCipher) {
const cipher = tlsSocket.getCipher();
console.log(` [Server] Selected cipher: ${cipher.name}`);
// Check if preferred cipher was selected
const cipherIndex = preferredCiphers.findIndex(c =>
cipher.name.includes(c) || c.includes(cipher.name)
);
console.log(` [Server] Cipher priority: ${cipherIndex + 1}/${preferredCiphers.length}`);
}
socket.write('220 priority.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-priority.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 smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: true,
tls: {
rejectUnauthorized: false,
// Client offers ciphers in different order
ciphers: preferredCiphers.slice().reverse().join(':'),
honorCipherOrder: false // Let server choose
}
});
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'Cipher priority test',
text: 'Testing cipher suite selection priority'
});
const result = await smtpClient.sendMail(email);
console.log(' Cipher negotiation completed');
expect(result).toBeDefined();
expect(result.messageId).toBeDefined();
await testServer.server.close();
})();
// Scenario 4: Perfect Forward Secrecy (PFS) ciphers
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing Perfect Forward Secrecy ciphers`);
const testServer = await createTestServer({
secure: true,
tlsOptions: {
// Only PFS ciphers (ECDHE/DHE)
ciphers: 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384',
honorCipherOrder: true,
ecdhCurve: 'auto'
},
onConnection: async (socket) => {
console.log(' [Server] Client connected with PFS');
const tlsSocket = socket as any;
if (tlsSocket.getCipher) {
const cipher = tlsSocket.getCipher();
const hasPFS = cipher.name.includes('ECDHE') || cipher.name.includes('DHE');
console.log(` [Server] Cipher: ${cipher.name} (PFS: ${hasPFS ? 'Yes' : 'No'})`);
}
socket.write('220 pfs.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-pfs.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: Message sent with PFS\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,
// Prefer PFS ciphers
ciphers: 'ECDHE:DHE:!aNULL:!MD5',
ecdhCurve: 'auto'
}
});
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: 'PFS cipher test',
text: 'Testing Perfect Forward Secrecy'
});
const result = await smtpClient.sendMail(email);
console.log(' Successfully used PFS cipher');
expect(result).toBeDefined();
expect(result.messageId).toBeDefined();
await testServer.server.close();
})();
// Scenario 5: Cipher renegotiation
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing cipher renegotiation handling`);
const testServer = await createTestServer({
secure: true,
tlsOptions: {
ciphers: 'HIGH:MEDIUM:!aNULL:!MD5',
// Disable renegotiation for security
secureOptions: plugins.crypto.constants.SSL_OP_NO_RENEGOTIATION
},
onConnection: async (socket) => {
console.log(' [Server] Client connected');
socket.write('220 norenegotiation.example.com ESMTP\r\n');
let messageCount = 0;
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-norenegotiation.example.com\r\n');
socket.write('250 OK\r\n');
} else if (command.startsWith('MAIL FROM:')) {
messageCount++;
console.log(` [Server] Processing message ${messageCount}`);
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 === 'RSET') {
socket.write('250 OK\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,
// Also disable renegotiation on client
secureOptions: plugins.crypto.constants.SSL_OP_NO_RENEGOTIATION
}
});
// Send multiple emails on same connection
for (let i = 0; i < 3; i++) {
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: `Renegotiation test ${i + 1}`,
text: `Testing without cipher renegotiation - email ${i + 1}`
});
const result = await smtpClient.sendMail(email);
console.log(` Email ${i + 1} sent without renegotiation`);
expect(result).toBeDefined();
expect(result.messageId).toBeDefined();
}
await testServer.server.close();
})();
// Scenario 6: Cipher compatibility testing
await (async () => {
scenarioCount++;
console.log(`\nScenario ${scenarioCount}: Testing cipher compatibility`);
const cipherSets = [
{
name: 'TLS 1.3 only',
ciphers: 'TLS_AES_256_GCM_SHA384:TLS_AES_128_GCM_SHA256:TLS_CHACHA20_POLY1305_SHA256',
minVersion: 'TLSv1.3',
maxVersion: 'TLSv1.3'
},
{
name: 'TLS 1.2 compatible',
ciphers: 'ECDHE-RSA-AES256-GCM-SHA384:ECDHE-RSA-AES128-GCM-SHA256',
minVersion: 'TLSv1.2',
maxVersion: 'TLSv1.2'
},
{
name: 'Broad compatibility',
ciphers: 'HIGH:MEDIUM:!aNULL:!MD5:!3DES',
minVersion: 'TLSv1.2',
maxVersion: undefined
}
];
for (const cipherSet of cipherSets) {
console.log(`\n Testing ${cipherSet.name}...`);
const testServer = await createTestServer({
secure: true,
tlsOptions: {
ciphers: cipherSet.ciphers,
minVersion: cipherSet.minVersion as any,
maxVersion: cipherSet.maxVersion as any
},
onConnection: async (socket) => {
const tlsSocket = socket as any;
if (tlsSocket.getCipher && tlsSocket.getProtocol) {
const cipher = tlsSocket.getCipher();
const protocol = tlsSocket.getProtocol();
console.log(` [Server] Protocol: ${protocol}, Cipher: ${cipher.name}`);
}
socket.write('220 compat.example.com ESMTP\r\n');
socket.on('data', (data) => {
const command = data.toString().trim();
if (command.startsWith('EHLO')) {
socket.write('250-compat.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();
}
});
}
});
try {
const smtpClient = createSmtpClient({
host: testServer.hostname,
port: testServer.port,
secure: true,
tls: {
rejectUnauthorized: false,
ciphers: cipherSet.ciphers,
minVersion: cipherSet.minVersion as any,
maxVersion: cipherSet.maxVersion as any
}
});
const email = new plugins.smartmail.Email({
from: 'sender@example.com',
to: ['recipient@example.com'],
subject: `${cipherSet.name} test`,
text: `Testing ${cipherSet.name} cipher configuration`
});
const result = await smtpClient.sendMail(email);
console.log(` Success with ${cipherSet.name}`);
expect(result).toBeDefined();
expect(result.messageId).toBeDefined();
} catch (error) {
console.log(` ${cipherSet.name} not supported in this environment`);
}
await testServer.server.close();
}
})();
console.log(`\n${testId}: All ${scenarioCount} cipher suite scenarios tested ✓`);
});