update
This commit is contained in:
@ -4,17 +4,19 @@ import * as tls from 'tls';
|
||||
import { startTestServer, stopTestServer, type ITestServer } from '../../helpers/server.loader.js';
|
||||
|
||||
const TEST_PORT = 2525;
|
||||
const TEST_PORT_TLS = 30466;
|
||||
|
||||
let testServer: ITestServer;
|
||||
const TEST_TIMEOUT = 30000;
|
||||
|
||||
tap.test('TLS Ciphers - should advertise STARTTLS for cipher negotiation', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
// Start test server
|
||||
const testServer = await startTestServer();
|
||||
testServer = await startTestServer({ port: TEST_PORT, tlsEnabled: true });
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));try {
|
||||
try {
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT,
|
||||
@ -64,38 +66,83 @@ tap.test('TLS Ciphers - should advertise STARTTLS for cipher negotiation', async
|
||||
expect(true).toEqual(true);
|
||||
|
||||
} finally {
|
||||
await stopTestServer();
|
||||
await stopTestServer(testServer);
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
|
||||
tap.test('TLS Ciphers - should negotiate secure cipher suites', async (tools) => {
|
||||
tap.test('TLS Ciphers - should negotiate secure cipher suites via STARTTLS', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
// Start test server on TLS port
|
||||
const testServer = await startTestServer();
|
||||
// Start test server
|
||||
testServer = await startTestServer({ port: TEST_PORT, tlsEnabled: true });
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));try {
|
||||
const tlsOptions = {
|
||||
try {
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT_TLS,
|
||||
rejectUnauthorized: false,
|
||||
port: TEST_PORT,
|
||||
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);
|
||||
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);
|
||||
});
|
||||
|
||||
// Check for STARTTLS
|
||||
if (!ehloResponse.includes('STARTTLS')) {
|
||||
console.log('Server does not support STARTTLS - skipping cipher test');
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
done.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Send STARTTLS
|
||||
socket.write('STARTTLS\r\n');
|
||||
|
||||
const starttlsResponse = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
expect(starttlsResponse).toInclude('220');
|
||||
|
||||
// Upgrade to TLS
|
||||
const tlsSocket = tls.connect({
|
||||
socket: socket,
|
||||
servername: 'localhost',
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
tlsSocket.once('secureConnect', () => resolve());
|
||||
tlsSocket.once('error', reject);
|
||||
});
|
||||
|
||||
// Get cipher information
|
||||
const cipher = socket.getCipher();
|
||||
const cipher = tlsSocket.getCipher();
|
||||
console.log('Negotiated cipher suite:');
|
||||
console.log('- Name:', cipher.name);
|
||||
console.log('- Standard name:', cipher.standardName);
|
||||
@ -108,19 +155,12 @@ tap.test('TLS Ciphers - should negotiate secure cipher suites', async (tools) =>
|
||||
expect(cipher.name).toBeDefined();
|
||||
expect(cipherSecurity.secure).toEqual(true);
|
||||
|
||||
// Send SMTP command to verify encrypted communication
|
||||
const banner = await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
expect(banner).toInclude('220');
|
||||
|
||||
// Clean up
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
tlsSocket.write('QUIT\r\n');
|
||||
tlsSocket.end();
|
||||
|
||||
} finally {
|
||||
await stopTestServer();
|
||||
await stopTestServer(testServer);
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
@ -128,11 +168,59 @@ tap.test('TLS Ciphers - should negotiate secure cipher suites', async (tools) =>
|
||||
tap.test('TLS Ciphers - should reject weak cipher suites', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
// Start test server on TLS port
|
||||
const testServer = await startTestServer();
|
||||
// Start test server
|
||||
testServer = await startTestServer({ port: TEST_PORT, tlsEnabled: true });
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));try {
|
||||
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);
|
||||
});
|
||||
|
||||
// Check for STARTTLS
|
||||
if (!ehloResponse.includes('STARTTLS')) {
|
||||
console.log('Server does not support STARTTLS - skipping weak cipher test');
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
done.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Send STARTTLS
|
||||
socket.write('STARTTLS\r\n');
|
||||
|
||||
await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
// Try to connect with weak ciphers only
|
||||
const weakCiphers = [
|
||||
'DES-CBC3-SHA',
|
||||
@ -144,26 +232,25 @@ tap.test('TLS Ciphers - should reject weak cipher suites', async (tools) => {
|
||||
|
||||
console.log('Testing connection with weak ciphers only...');
|
||||
|
||||
const tlsOptions = {
|
||||
host: 'localhost',
|
||||
port: TEST_PORT_TLS,
|
||||
rejectUnauthorized: false,
|
||||
timeout: 5000,
|
||||
ciphers: weakCiphers.join(':')
|
||||
};
|
||||
|
||||
const connectionResult = await new Promise<{success: boolean, error?: string}>((resolve) => {
|
||||
const socket = tls.connect(tlsOptions, () => {
|
||||
const tlsSocket = tls.connect({
|
||||
socket: socket,
|
||||
servername: 'localhost',
|
||||
rejectUnauthorized: false,
|
||||
ciphers: weakCiphers.join(':')
|
||||
});
|
||||
|
||||
tlsSocket.once('secureConnect', () => {
|
||||
// If connection succeeds, server accepts weak ciphers
|
||||
const cipher = socket.getCipher();
|
||||
socket.destroy();
|
||||
const cipher = tlsSocket.getCipher();
|
||||
tlsSocket.destroy();
|
||||
resolve({
|
||||
success: true,
|
||||
error: `Server accepted weak cipher: ${cipher.name}`
|
||||
});
|
||||
});
|
||||
|
||||
socket.on('error', (err) => {
|
||||
tlsSocket.once('error', (err) => {
|
||||
// Connection failed - good, server rejects weak ciphers
|
||||
resolve({
|
||||
success: false,
|
||||
@ -172,7 +259,7 @@ tap.test('TLS Ciphers - should reject weak cipher suites', async (tools) => {
|
||||
});
|
||||
|
||||
setTimeout(() => {
|
||||
socket.destroy();
|
||||
tlsSocket.destroy();
|
||||
resolve({
|
||||
success: false,
|
||||
error: 'Connection timeout'
|
||||
@ -190,7 +277,7 @@ tap.test('TLS Ciphers - should reject weak cipher suites', async (tools) => {
|
||||
expect(true).toEqual(true);
|
||||
|
||||
} finally {
|
||||
await stopTestServer();
|
||||
await stopTestServer(testServer);
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
@ -198,11 +285,59 @@ tap.test('TLS Ciphers - should reject weak cipher suites', async (tools) => {
|
||||
tap.test('TLS Ciphers - should support forward secrecy', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
// Start test server on TLS port
|
||||
const testServer = await startTestServer();
|
||||
// Start test server
|
||||
testServer = await startTestServer({ port: TEST_PORT, tlsEnabled: true });
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));try {
|
||||
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);
|
||||
});
|
||||
|
||||
// Check for STARTTLS
|
||||
if (!ehloResponse.includes('STARTTLS')) {
|
||||
console.log('Server does not support STARTTLS - skipping forward secrecy test');
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
done.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Send STARTTLS
|
||||
socket.write('STARTTLS\r\n');
|
||||
|
||||
await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
// Prefer ciphers with forward secrecy (ECDHE, DHE)
|
||||
const forwardSecrecyCiphers = [
|
||||
'ECDHE-RSA-AES128-GCM-SHA256',
|
||||
@ -213,25 +348,20 @@ tap.test('TLS Ciphers - should support forward secrecy', async (tools) => {
|
||||
'DHE-RSA-AES256-GCM-SHA384'
|
||||
];
|
||||
|
||||
const tlsOptions = {
|
||||
host: 'localhost',
|
||||
port: TEST_PORT_TLS,
|
||||
const tlsSocket = tls.connect({
|
||||
socket: socket,
|
||||
servername: 'localhost',
|
||||
rejectUnauthorized: false,
|
||||
timeout: TEST_TIMEOUT,
|
||||
ciphers: forwardSecrecyCiphers.join(':')
|
||||
};
|
||||
});
|
||||
|
||||
const socket = await new Promise<tls.TLSSocket>((resolve, reject) => {
|
||||
const tlsSocket = tls.connect(tlsOptions, () => {
|
||||
resolve(tlsSocket);
|
||||
});
|
||||
|
||||
tlsSocket.on('error', reject);
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
tlsSocket.once('secureConnect', () => resolve());
|
||||
tlsSocket.once('error', reject);
|
||||
setTimeout(() => reject(new Error('TLS connection timeout')), 5000);
|
||||
});
|
||||
|
||||
const cipher = socket.getCipher();
|
||||
const cipher = tlsSocket.getCipher();
|
||||
console.log('Forward secrecy cipher negotiated:', cipher.name);
|
||||
|
||||
// Check if cipher provides forward secrecy
|
||||
@ -245,14 +375,14 @@ tap.test('TLS Ciphers - should support forward secrecy', async (tools) => {
|
||||
}
|
||||
|
||||
// Clean up
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
tlsSocket.write('QUIT\r\n');
|
||||
tlsSocket.end();
|
||||
|
||||
// Forward secrecy is recommended but not required
|
||||
expect(true).toEqual(true);
|
||||
|
||||
} finally {
|
||||
await stopTestServer();
|
||||
await stopTestServer(testServer);
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
@ -260,34 +390,77 @@ tap.test('TLS Ciphers - should support forward secrecy', async (tools) => {
|
||||
tap.test('TLS Ciphers - should list all supported ciphers', async (tools) => {
|
||||
const done = tools.defer();
|
||||
|
||||
// Start test server on TLS port
|
||||
const testServer = await startTestServer();
|
||||
// Start test server
|
||||
testServer = await startTestServer({ port: TEST_PORT, tlsEnabled: true });
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));try {
|
||||
try {
|
||||
// Get list of ciphers supported by Node.js
|
||||
const supportedCiphers = tls.getCiphers();
|
||||
console.log(`Node.js supports ${supportedCiphers.length} cipher suites`);
|
||||
|
||||
// Test connection with default ciphers
|
||||
const tlsOptions = {
|
||||
const socket = net.createConnection({
|
||||
host: 'localhost',
|
||||
port: TEST_PORT_TLS,
|
||||
rejectUnauthorized: false,
|
||||
port: TEST_PORT,
|
||||
timeout: TEST_TIMEOUT
|
||||
};
|
||||
});
|
||||
|
||||
const socket = await new Promise<tls.TLSSocket>((resolve, reject) => {
|
||||
const tlsSocket = tls.connect(tlsOptions, () => {
|
||||
resolve(tlsSocket);
|
||||
});
|
||||
|
||||
tlsSocket.on('error', reject);
|
||||
|
||||
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);
|
||||
});
|
||||
|
||||
// Check for STARTTLS
|
||||
if (!ehloResponse.includes('STARTTLS')) {
|
||||
console.log('Server does not support STARTTLS - skipping cipher list test');
|
||||
socket.write('QUIT\r\n');
|
||||
socket.end();
|
||||
done.resolve();
|
||||
return;
|
||||
}
|
||||
|
||||
// Send STARTTLS
|
||||
socket.write('STARTTLS\r\n');
|
||||
|
||||
await new Promise<string>((resolve) => {
|
||||
socket.once('data', (chunk) => resolve(chunk.toString()));
|
||||
});
|
||||
|
||||
// Test connection with default ciphers
|
||||
const tlsSocket = tls.connect({
|
||||
socket: socket,
|
||||
servername: 'localhost',
|
||||
rejectUnauthorized: false
|
||||
});
|
||||
|
||||
await new Promise<void>((resolve, reject) => {
|
||||
tlsSocket.once('secureConnect', () => resolve());
|
||||
tlsSocket.once('error', reject);
|
||||
setTimeout(() => reject(new Error('TLS connection timeout')), 5000);
|
||||
});
|
||||
|
||||
const negotiatedCipher = socket.getCipher();
|
||||
const negotiatedCipher = tlsSocket.getCipher();
|
||||
console.log('\nServer selected cipher:', negotiatedCipher.name);
|
||||
|
||||
// Categorize the cipher
|
||||
@ -303,12 +476,12 @@ tap.test('TLS Ciphers - should list all supported ciphers', async (tools) => {
|
||||
});
|
||||
|
||||
// Clean up
|
||||
socket.end();
|
||||
tlsSocket.end();
|
||||
|
||||
expect(negotiatedCipher.name).toBeDefined();
|
||||
|
||||
} finally {
|
||||
await stopTestServer();
|
||||
await stopTestServer(testServer);
|
||||
done.resolve();
|
||||
}
|
||||
});
|
||||
|
Reference in New Issue
Block a user