dcrouter/test/suite/smtpserver_security/test.sec-09.tls-certificate-validation.ts
2025-05-25 19:05:43 +00:00

312 lines
8.9 KiB
TypeScript

import { tap, expect } from '@git.zone/tstest/tapbundle';
import * as plugins from '../../../ts/plugins.js';
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 = 2525;
let testServer: ITestServer;
tap.test('setup - start test server', async (toolsArg) => {
testServer = await startTestServer({ port: TEST_PORT });
await toolsArg.delayFor(1000);
});
tap.test('TLS Certificate Validation - STARTTLS certificate check', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
let step = 'greeting';
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (step === 'greeting' && dataBuffer.includes('220 ')) {
step = 'ehlo';
socket.write('EHLO testclient\r\n');
dataBuffer = '';
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
const supportsStarttls = dataBuffer.toLowerCase().includes('starttls');
console.log('STARTTLS supported:', supportsStarttls);
if (supportsStarttls) {
step = 'starttls';
socket.write('STARTTLS\r\n');
dataBuffer = '';
} else {
console.log('STARTTLS not supported, testing plain connection');
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
} else if (step === 'starttls' && dataBuffer.includes('220')) {
console.log('Ready to start TLS');
// Upgrade to TLS
const tlsOptions = {
socket: socket,
rejectUnauthorized: false, // For self-signed certificates in testing
requestCert: true
};
const tlsSocket = tls.connect(tlsOptions);
tlsSocket.on('secureConnect', () => {
console.log('TLS connection established');
// Get certificate information
const cert = tlsSocket.getPeerCertificate();
console.log('Certificate present:', !!cert);
if (cert && Object.keys(cert).length > 0) {
console.log('Certificate subject:', cert.subject);
console.log('Certificate issuer:', cert.issuer);
console.log('Certificate valid from:', cert.valid_from);
console.log('Certificate valid to:', cert.valid_to);
// Check certificate validity
const now = new Date();
const validFrom = new Date(cert.valid_from);
const validTo = new Date(cert.valid_to);
const isValid = now >= validFrom && now <= validTo;
console.log('Certificate currently valid:', isValid);
expect(true).toEqual(true); // Certificate present
}
// Test EHLO over TLS
tlsSocket.write('EHLO testclient\r\n');
});
tlsSocket.on('data', (data) => {
const response = data.toString();
console.log('TLS response:', response);
if (response.includes('250')) {
console.log('EHLO over TLS successful');
expect(true).toEqual(true);
tlsSocket.write('QUIT\r\n');
tlsSocket.end();
done.resolve();
}
});
tlsSocket.on('error', (err) => {
console.error('TLS error:', err);
done.reject(err);
});
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
await done.promise;
});
tap.test('TLS Certificate Validation - Direct TLS connection', async (tools) => {
const done = tools.defer();
// Try connecting with TLS directly (implicit TLS)
const tlsOptions = {
host: 'localhost',
port: TEST_PORT,
rejectUnauthorized: false,
timeout: 30000
};
const socket = tls.connect(tlsOptions);
socket.on('secureConnect', () => {
console.log('Direct TLS connection established');
const cert = socket.getPeerCertificate();
if (cert && Object.keys(cert).length > 0) {
console.log('Certificate found on direct TLS connection');
expect(true).toEqual(true);
}
socket.end();
done.resolve();
});
socket.on('error', (err) => {
// Direct TLS might not be supported, try plain connection
console.log('Direct TLS not supported, this is expected for STARTTLS servers');
expect(true).toEqual(true);
done.resolve();
});
socket.on('timeout', () => {
console.log('Direct TLS connection timeout');
socket.destroy();
done.resolve();
});
await done.promise;
});
tap.test('TLS Certificate Validation - Certificate verification with strict mode', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
let step = 'greeting';
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (step === 'greeting' && dataBuffer.includes('220 ')) {
step = 'ehlo';
socket.write('EHLO testclient\r\n');
dataBuffer = '';
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
if (dataBuffer.toLowerCase().includes('starttls')) {
step = 'starttls';
socket.write('STARTTLS\r\n');
dataBuffer = '';
} else {
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
} else if (step === 'starttls' && dataBuffer.includes('220')) {
// Try with strict certificate verification
const tlsOptions = {
socket: socket,
rejectUnauthorized: true, // Strict mode
servername: 'localhost' // For SNI
};
const tlsSocket = tls.connect(tlsOptions);
tlsSocket.on('secureConnect', () => {
console.log('TLS connection with strict verification successful');
const authorized = tlsSocket.authorized;
console.log('Certificate authorized:', authorized);
if (!authorized) {
console.log('Authorization error:', tlsSocket.authorizationError);
}
expect(true).toEqual(true); // Connection established
tlsSocket.write('QUIT\r\n');
tlsSocket.end();
done.resolve();
});
tlsSocket.on('error', (err) => {
console.log('Certificate verification error (expected for self-signed):', err.message);
expect(true).toEqual(true); // Error is expected for self-signed certificates
socket.end();
done.resolve();
});
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
await done.promise;
});
tap.test('TLS Certificate Validation - Cipher suite information', async (tools) => {
const done = tools.defer();
const socket = net.createConnection({
host: 'localhost',
port: TEST_PORT,
timeout: 30000
});
let dataBuffer = '';
let step = 'greeting';
socket.on('data', (data) => {
dataBuffer += data.toString();
console.log('Server response:', data.toString());
if (step === 'greeting' && dataBuffer.includes('220 ')) {
step = 'ehlo';
socket.write('EHLO testclient\r\n');
dataBuffer = '';
} else if (step === 'ehlo' && dataBuffer.includes('250')) {
if (dataBuffer.toLowerCase().includes('starttls')) {
step = 'starttls';
socket.write('STARTTLS\r\n');
dataBuffer = '';
} else {
socket.write('QUIT\r\n');
socket.end();
done.resolve();
}
} else if (step === 'starttls' && dataBuffer.includes('220')) {
const tlsOptions = {
socket: socket,
rejectUnauthorized: false
};
const tlsSocket = tls.connect(tlsOptions);
tlsSocket.on('secureConnect', () => {
console.log('TLS connection established');
// Get cipher information
const cipher = tlsSocket.getCipher();
if (cipher) {
console.log('Cipher name:', cipher.name);
console.log('Cipher version:', cipher.version);
console.log('Cipher standardName:', cipher.standardName);
}
// Get protocol version
const protocol = tlsSocket.getProtocol();
console.log('TLS Protocol:', protocol);
// Verify modern TLS version
expect(['TLSv1.2', 'TLSv1.3']).toContain(protocol);
tlsSocket.write('QUIT\r\n');
tlsSocket.end();
done.resolve();
});
tlsSocket.on('error', (err) => {
console.error('TLS error:', err);
done.reject(err);
});
}
});
socket.on('error', (err) => {
console.error('Socket error:', err);
done.reject(err);
});
await done.promise;
});
tap.test('cleanup - stop test server', async () => {
await stopTestServer(testServer);
});
export default tap.start();