update
This commit is contained in:
273
test/suite/connection/test.tls-versions.ts
Normal file
273
test/suite/connection/test.tls-versions.ts
Normal file
@@ -0,0 +1,273 @@
|
||||
import { tap, expect } from '@git.zone/tapbundle';
|
||||
import * as net from 'net';
|
||||
import * as tls from 'tls';
|
||||
import { startTestServer, stopTestServer } from '../../helpers/server.loader.js';
|
||||
import type { ITestServer } from '../../helpers/server.loader.js';
|
||||
|
||||
const TEST_PORT = 30030;
|
||||
const TEST_PORT_TLS = 30465;
|
||||
const TEST_TIMEOUT = 30000;
|
||||
|
||||
let testServer: ITestServer;
|
||||
let testServerTls: ITestServer;
|
||||
|
||||
tap.test('setup - start SMTP servers for TLS version tests', async () => {
|
||||
testServer = await startTestServer({
|
||||
port: TEST_PORT,
|
||||
hostname: 'localhost'
|
||||
});
|
||||
|
||||
testServerTls = await startTestServer({
|
||||
port: TEST_PORT_TLS,
|
||||
hostname: 'localhost',
|
||||
tlsEnabled: true
|
||||
});
|
||||
|
||||
expect(testServer).toBeInstanceOf(Object);
|
||||
expect(testServerTls).toBeInstanceOf(Object);
|
||||
});
|
||||
|
||||
tap.test('TLS Versions - should support STARTTLS capability', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
try {
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
timeout: TEST_TIMEOUT
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
socket.once('connect', () => resolve());
|
||||
socket.once('error', reject);
|
||||
});
|
||||
|
||||
// Get banner
|
||||
await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
// Send EHLO
|
||||
socket.write('EHLO testhost\r\n');
|
||||
|
||||
const ehloResponse = await new Promise<string>((resolve) => {
|
||||
let data = '';
|
||||
const handler = (chunk: Buffer) => {
|
||||
data += chunk.toString();
|
||||
if (data.includes('\r\n') && (data.match(/^250 /m) || data.match(/^250-.*\r\n250 /ms))) {
|
||||
socket.removeListener('data', handler);
|
||||
resolve(data);
|
||||
}
|
||||
};
|
||||
socket.on('data', handler);
|
||||
});
|
||||
|
||||
console.log('EHLO response:', ehloResponse);
|
||||
|
||||
// Check for STARTTLS support
|
||||
const supportsStarttls = ehloResponse.includes('250-STARTTLS') || ehloResponse.includes('250 STARTTLS');
|
||||
console.log('STARTTLS supported:', supportsStarttls);
|
||||
|
||||
if (supportsStarttls) {
|
||||
// Test STARTTLS upgrade
|
||||
socket.write('STARTTLS\r\n');
|
||||
|
||||
const starttlsResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
expect(starttlsResponse).toInclude('220');
|
||||
console.log('STARTTLS ready response received');
|
||||
|
||||
// Would upgrade to TLS here in a real implementation
|
||||
// For testing, we just verify the capability
|
||||
}
|
||||
|
||||
// Clean up
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
|
||||
// STARTTLS is optional but common
|
||||
expect(true).toBeTrue();
|
||||
|
||||
} finally {
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('TLS Versions - should support modern TLS versions on secure port', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
try {
|
||||
// Test TLS 1.2
|
||||
console.log('Testing TLS 1.2 support...');
|
||||
const tls12Result = await testTlsVersion('TLSv1.2', TEST_PORT_TLS);
|
||||
console.log('TLS 1.2 result:', tls12Result);
|
||||
|
||||
// Test TLS 1.3
|
||||
console.log('Testing TLS 1.3 support...');
|
||||
const tls13Result = await testTlsVersion('TLSv1.3', TEST_PORT_TLS);
|
||||
console.log('TLS 1.3 result:', tls13Result);
|
||||
|
||||
// At least one modern version should be supported
|
||||
const supportsModernTls = tls12Result.success || tls13Result.success;
|
||||
expect(supportsModernTls).toBeTrue();
|
||||
|
||||
if (tls12Result.success) {
|
||||
console.log('TLS 1.2 supported with cipher:', tls12Result.cipher);
|
||||
}
|
||||
if (tls13Result.success) {
|
||||
console.log('TLS 1.3 supported with cipher:', tls13Result.cipher);
|
||||
}
|
||||
|
||||
} finally {
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('TLS Versions - should reject obsolete TLS versions', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
try {
|
||||
// Test TLS 1.0 (should be rejected by modern servers)
|
||||
console.log('Testing TLS 1.0 (obsolete)...');
|
||||
const tls10Result = await testTlsVersion('TLSv1', TEST_PORT_TLS);
|
||||
|
||||
// Test TLS 1.1 (should be rejected by modern servers)
|
||||
console.log('Testing TLS 1.1 (obsolete)...');
|
||||
const tls11Result = await testTlsVersion('TLSv1.1', TEST_PORT_TLS);
|
||||
|
||||
// Modern servers should reject these old versions
|
||||
// But some might still support them for compatibility
|
||||
console.log(`TLS 1.0 ${tls10Result.success ? 'accepted (legacy support)' : 'rejected (good)'}`);
|
||||
console.log(`TLS 1.1 ${tls11Result.success ? 'accepted (legacy support)' : 'rejected (good)'}`);
|
||||
|
||||
// Either behavior is acceptable - log the results
|
||||
expect(true).toBeTrue();
|
||||
|
||||
} finally {
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('TLS Versions - should provide cipher information', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
try {
|
||||
const tlsOptions = {
|
||||
host: 'localhost',
|
||||
port: TEST_PORT_TLS,
|
||||
rejectUnauthorized: false,
|
||||
timeout: TEST_TIMEOUT
|
||||
};
|
||||
|
||||
const socket = await new Promise<tls.TLSSocket>((resolve, reject) => {
|
||||
const tlsSocket = tls.connect(tlsOptions, () => {
|
||||
resolve(tlsSocket);
|
||||
});
|
||||
|
||||
tlsSocket.on('error', reject);
|
||||
|
||||
setTimeout(() => reject(new Error('TLS connection timeout')), 5000);
|
||||
});
|
||||
|
||||
// Get connection details
|
||||
const cipher = socket.getCipher();
|
||||
const protocol = socket.getProtocol();
|
||||
const authorized = socket.authorized;
|
||||
|
||||
console.log('TLS connection established:');
|
||||
console.log('- Protocol:', protocol);
|
||||
console.log('- Cipher:', cipher.name);
|
||||
console.log('- Key exchange:', cipher.standardName);
|
||||
console.log('- Authorized:', authorized);
|
||||
|
||||
expect(protocol).toBeDefined();
|
||||
expect(cipher.name).toBeDefined();
|
||||
|
||||
// Send SMTP greeting to verify encrypted connection works
|
||||
const banner = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
expect(banner).toInclude('220');
|
||||
console.log('Received SMTP banner over TLS');
|
||||
|
||||
// Clean up
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
|
||||
} finally {
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to test specific TLS version
|
||||
async function testTlsVersion(version: string, port: number): Promise<{success: boolean, cipher?: any, error?: string}> {
|
||||
return new Promise((resolve) => {
|
||||
const tlsOptions: any = {
|
||||
host: 'localhost',
|
||||
port: port,
|
||||
rejectUnauthorized: false,
|
||||
timeout: 5000
|
||||
};
|
||||
|
||||
// Set version constraints based on requested version
|
||||
switch (version) {
|
||||
case 'TLSv1':
|
||||
tlsOptions.minVersion = 'TLSv1';
|
||||
tlsOptions.maxVersion = 'TLSv1';
|
||||
break;
|
||||
case 'TLSv1.1':
|
||||
tlsOptions.minVersion = 'TLSv1.1';
|
||||
tlsOptions.maxVersion = 'TLSv1.1';
|
||||
break;
|
||||
case 'TLSv1.2':
|
||||
tlsOptions.minVersion = 'TLSv1.2';
|
||||
tlsOptions.maxVersion = 'TLSv1.2';
|
||||
break;
|
||||
case 'TLSv1.3':
|
||||
tlsOptions.minVersion = 'TLSv1.3';
|
||||
tlsOptions.maxVersion = 'TLSv1.3';
|
||||
break;
|
||||
}
|
||||
|
||||
const socket = tls.connect(tlsOptions, () => {
|
||||
const cipher = socket.getCipher();
|
||||
const protocol = socket.getProtocol();
|
||||
|
||||
socket.destroy();
|
||||
resolve({
|
||||
success: true,
|
||||
cipher: {
|
||||
name: cipher.name,
|
||||
standardName: cipher.standardName,
|
||||
protocol: protocol
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('error', (error) => {
|
||||
resolve({
|
||||
success: false,
|
||||
error: error.message
|
||||
});
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
socket.destroy();
|
||||
resolve({
|
||||
success: false,
|
||||
error: 'Connection timeout'
|
||||
});
|
||||
}, 5000);
|
||||
});
|
||||
}
|
||||
|
||||
tap.test('cleanup - stop SMTP servers', async () => {
|
||||
await stopTestServer(testServer);
|
||||
await stopTestServer(testServerTls);
|
||||
expect(true).toBeTrue();
|
||||
});
|
||||
|
||||
tap.start();
|
||||
Reference in New Issue
Block a user