This commit is contained in:
2025-05-24 00:23:35 +00:00
parent 0907949f8a
commit cb52446f65
76 changed files with 1401 additions and 867 deletions

View File

@ -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();
}
});