import { tap, expect } from '@git.zone/tstest/tapbundle'; import * as plugins from '../plugins.js'; import * as net from 'net'; import * as tls from 'tls'; import { startTestServer, stopTestServer, TEST_PORT, sendEmailWithRawSocket } from '../../helpers/server.loader.js'; import type { SmtpServer } from '../../../ts/mail/delivery/smtpserver/index.js'; let testServer: SmtpServer; tap.test('setup - start test server', async () => { testServer = await startTestServer(); await plugins.smartdelay.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); }); tap.start();