/** * Certificate Utilities for SMTP Server * Provides utilities for managing TLS certificates */ import * as fs from 'fs'; import * as tls from 'tls'; import { SmtpLogger } from './utils/logging.js'; /** * Normalize a PEM certificate string * @param str - Certificate string * @returns Normalized certificate string */ function normalizeCertificate(str) { // Handle different input types let inputStr; if (Buffer.isBuffer(str)) { // Convert Buffer to string using utf8 encoding inputStr = str.toString('utf8'); } else if (typeof str === 'string') { inputStr = str; } else { throw new Error('Certificate must be a string or Buffer'); } if (!inputStr) { throw new Error('Empty certificate data'); } // Remove any whitespace around the string let normalizedStr = inputStr.trim(); // Make sure it has proper PEM format if (!normalizedStr.includes('-----BEGIN ')) { throw new Error('Invalid certificate format: Missing BEGIN marker'); } if (!normalizedStr.includes('-----END ')) { throw new Error('Invalid certificate format: Missing END marker'); } // Normalize line endings (replace Windows-style \r\n with Unix-style \n) normalizedStr = normalizedStr.replace(/\r\n/g, '\n'); // Only normalize if the certificate appears to have formatting issues // Check if the certificate is already properly formatted const lines = normalizedStr.split('\n'); let needsReformatting = false; // Check for common formatting issues: // 1. Missing line breaks after header/before footer // 2. Lines that are too long or too short (except header/footer) // 3. Multiple consecutive blank lines for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (line.startsWith('-----BEGIN ') || line.startsWith('-----END ')) { continue; // Skip header/footer lines } if (line.length === 0) { continue; // Skip empty lines } // Check if content lines are reasonable length (base64 is typically 64 chars per line) if (line.length > 76) { // Allow some flexibility beyond standard 64 needsReformatting = true; break; } } // Only reformat if necessary if (needsReformatting) { const beginMatch = normalizedStr.match(/^(-----BEGIN [^-]+-----)(.*)$/s); const endMatch = normalizedStr.match(/(.*)(-----END [^-]+-----)$/s); if (beginMatch && endMatch) { const header = beginMatch[1]; const footer = endMatch[2]; let content = normalizedStr.substring(header.length, normalizedStr.length - footer.length); // Clean up only line breaks and carriage returns, preserve base64 content content = content.replace(/[\n\r]/g, '').trim(); // Add proper line breaks (every 64 characters) let formattedContent = ''; for (let i = 0; i < content.length; i += 64) { formattedContent += content.substring(i, Math.min(i + 64, content.length)) + '\n'; } // Reconstruct the certificate return header + '\n' + formattedContent + footer; } } return normalizedStr; } /** * Load certificates from PEM format strings * @param options - Certificate options * @returns Certificate data with Buffer format */ export function loadCertificatesFromString(options) { try { // First try to use certificates without normalization try { let keyStr; let certStr; let caStr; // Convert inputs to strings without aggressive normalization if (Buffer.isBuffer(options.key)) { keyStr = options.key.toString('utf8'); } else { keyStr = options.key; } if (Buffer.isBuffer(options.cert)) { certStr = options.cert.toString('utf8'); } else { certStr = options.cert; } if (options.ca) { if (Buffer.isBuffer(options.ca)) { caStr = options.ca.toString('utf8'); } else { caStr = options.ca; } } // Simple cleanup - only normalize line endings keyStr = keyStr.trim().replace(/\r\n/g, '\n'); certStr = certStr.trim().replace(/\r\n/g, '\n'); if (caStr) { caStr = caStr.trim().replace(/\r\n/g, '\n'); } // Convert to buffers const keyBuffer = Buffer.from(keyStr, 'utf8'); const certBuffer = Buffer.from(certStr, 'utf8'); const caBuffer = caStr ? Buffer.from(caStr, 'utf8') : undefined; // Test the certificates first const secureContext = tls.createSecureContext({ key: keyBuffer, cert: certBuffer, ca: caBuffer }); SmtpLogger.info('Successfully validated certificates without normalization'); return { key: keyBuffer, cert: certBuffer, ca: caBuffer }; } catch (simpleError) { SmtpLogger.warn(`Simple certificate loading failed, trying normalization: ${simpleError instanceof Error ? simpleError.message : String(simpleError)}`); // DEBUG: Log certificate details when simple loading fails SmtpLogger.warn('Certificate loading failure details', { keyType: typeof options.key, certType: typeof options.cert, keyIsBuffer: Buffer.isBuffer(options.key), certIsBuffer: Buffer.isBuffer(options.cert), keyLength: options.key ? options.key.length : 0, certLength: options.cert ? options.cert.length : 0, keyPreview: options.key ? (typeof options.key === 'string' ? options.key.substring(0, 50) : options.key.toString('utf8').substring(0, 50)) : 'null', certPreview: options.cert ? (typeof options.cert === 'string' ? options.cert.substring(0, 50) : options.cert.toString('utf8').substring(0, 50)) : 'null' }); } // Fallback: Try to fix and normalize certificates try { // Normalize certificates (handles both string and Buffer inputs) const key = normalizeCertificate(options.key); const cert = normalizeCertificate(options.cert); const ca = options.ca ? normalizeCertificate(options.ca) : undefined; // Convert normalized strings to Buffer with explicit utf8 encoding const keyBuffer = Buffer.from(key, 'utf8'); const certBuffer = Buffer.from(cert, 'utf8'); const caBuffer = ca ? Buffer.from(ca, 'utf8') : undefined; // Log for debugging SmtpLogger.debug('Certificate properties', { keyLength: keyBuffer.length, certLength: certBuffer.length, caLength: caBuffer ? caBuffer.length : 0 }); // Validate the certificates by attempting to create a secure context try { const secureContext = tls.createSecureContext({ key: keyBuffer, cert: certBuffer, ca: caBuffer }); // If createSecureContext doesn't throw, the certificates are valid SmtpLogger.info('Successfully validated certificate format'); } catch (validationError) { // Log detailed error information for debugging SmtpLogger.error(`Certificate validation error: ${validationError instanceof Error ? validationError.message : String(validationError)}`); SmtpLogger.debug('Certificate validation details', { keyPreview: keyBuffer.toString('utf8').substring(0, 100) + '...', certPreview: certBuffer.toString('utf8').substring(0, 100) + '...', keyLength: keyBuffer.length, certLength: certBuffer.length }); throw validationError; } return { key: keyBuffer, cert: certBuffer, ca: caBuffer }; } catch (innerError) { SmtpLogger.warn(`Certificate normalization failed: ${innerError instanceof Error ? innerError.message : String(innerError)}`); throw innerError; } } catch (error) { SmtpLogger.error(`Error loading certificates: ${error instanceof Error ? error.message : String(error)}`); throw error; } } /** * Load certificates from files * @param options - Certificate file paths * @returns Certificate data with Buffer format */ export function loadCertificatesFromFiles(options) { try { // Read files directly as Buffers const key = fs.readFileSync(options.keyPath); const cert = fs.readFileSync(options.certPath); const ca = options.caPath ? fs.readFileSync(options.caPath) : undefined; // Log for debugging SmtpLogger.debug('Certificate file properties', { keyLength: key.length, certLength: cert.length, caLength: ca ? ca.length : 0 }); // Validate the certificates by attempting to create a secure context try { const secureContext = tls.createSecureContext({ key, cert, ca }); // If createSecureContext doesn't throw, the certificates are valid SmtpLogger.info('Successfully validated certificate files'); } catch (validationError) { SmtpLogger.error(`Certificate file validation error: ${validationError instanceof Error ? validationError.message : String(validationError)}`); throw validationError; } return { key, cert, ca }; } catch (error) { SmtpLogger.error(`Error loading certificate files: ${error instanceof Error ? error.message : String(error)}`); throw error; } } /** * Generate self-signed certificates for testing * @returns Certificate data with Buffer format */ export function generateSelfSignedCertificates() { // This is for fallback/testing only - log a warning SmtpLogger.warn('Generating self-signed certificates for testing - DO NOT USE IN PRODUCTION'); // Create selfsigned certificates using node-forge or similar library // For now, use hardcoded certificates as a last resort const key = Buffer.from(`-----BEGIN PRIVATE KEY----- MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDEgJW1HdJPACGB ifoL3PB+HdAVA2nUmMfq43JbIUPXGTxCtzmQhuV04WjITwFw1loPx3ReHh4KR5yJ BVdzUDocHuauMmBycHAjv7mImR/VkuK/SwT0Q5G/9/M55o6HUNol0UKt+uZuBy1r ggFTdTDLw86i9UG5CZbWF/Yb/DTRoAkCr7iLnaZhhhqcdh5BGj7JBylIAV5RIW1y xQxJVJZQT2KgCeCnHRRvYRQ7tVzUQBcSvtW4zYtqK4C39BgRyLUZQVYB7siGT/uP YJE7R73u0xEgDMFWR1pItUYcVQXHQJ+YsLVCzqI22Mik7URdwxoSHSXRYKn6wnKg 4JYg65JnAgMBAAECggEAM2LlwRhwP0pnLlLHiPE4jJ3Qdz/NUF0hLnRhcUwW1iJ1 03jzCQ4QZ3etfL9O2hVJg49J+QUG50FNduLq4SE7GZj1dEJ/YNnlk9PpI8GSpLuA mGTUKofIEJjNy5gKR0c6/rfgP8UXYSbRnTnZwIXVkUYuAUJLJTBVcJlcvCwJ3/zz C8789JyOO1CNwF3zEIALdW5X5se8V+sw5iHDrHVxkR2xgsYpBBOylFfBxbMvV5o1 i+QOD1HaXdmIvjBCnHqrjX5SDnAYwHBSB9y6WbwC+Th76QHkRNcHZH86PJVdLEUi tBPQmQh+SjDRaZzDJvURnOFks+eEsCPVPZnQ4wgnAQKBgQD8oHwGZIZRUjnXULNc vJoPcjLpvdHRO0kXTJHtG2au2i9jVzL9SFwH1lHQM0XdXPnR2BK4Gmgc2dRnSB9n YPPvCgyL2RS0Y7W98yEcgBgwVOJHnPQGRNwxUfCTHgmCQ7lXjQKKG51+dBfOYP3j w8VYbS2pqxZtzzZ5zhk2BrZJdwKBgQDHDZC+NU80f7rLEr5vpwx9epTArwXre8oj nGgzZ9/lE14qDnITBuZPUHWc4/7U1CCmP0vVH6nFVvhN9ra9QCTJBzQ5aj0l3JM7 9j8R5QZIPqOu4+aqf0ZFEgmpBK2SAYqNrJ+YVa2T/zLF44Jlr5WiLkPTUyMxV5+k P4ZK8QP7wQKBgQCbeLuRWCuVKNYgYjm9TA55BbJL82J+MvhcbXUccpUksJQRxMV3 98PBUW0Qw38WciJxQF4naSKD/jXYndD+wGzpKMIU+tKU+sEYMnuFnx13++K8XrAe NQPHDsK1wRgXk5ygOHx78xnZbMmwBXNLwQXIhyO8FJpwJHj2CtYvjb+2xwKBgQCn KW/RiAHvG6GKjCHCOTlx2qLPxUiXYCk2xwvRnNfY5+2PFoqMI/RZLT/41kTda1fA TDw+j4Uu/fF2ChPadwRiUjXZzZx/UjcMJXTpQ2kpbGJ11U/cL4+Tk0S6wz+HoS7z w3vXT9UoDyFxDBjuMQJxJWTjmymaYUtNnz4iMuRqwQKBgH+HKbYHCZaIzXRMEO5S T3xDMYH59dTEKKXEOA1KJ9Zo5XSD8NE9SQ+9etoOcEq8tdYS45OkHD3VyFQa7THu 58awjTdkpSmMPsw3AElOYDYJgD9oxKtTjwkXHqMjDBQZrXqzOImOAJhEVL+XH3LP lv6RZ47YRC88T+P6n1yg6BPp -----END PRIVATE KEY-----`, 'utf8'); const cert = Buffer.from(`-----BEGIN CERTIFICATE----- MIIDCTCCAfGgAwIBAgIUHxmGQOQoiSbzqh6hIe+7h9xDXIUwDQYJKoZIhvcNAQEL BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MDUyMTE2MDAzM1oXDTI2MDUy MTE2MDAzM1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF AAOCAQ8AMIIBCgKCAQEAxICVtR3STwAhgYn6C9zwfh3QFQNp1JjH6uNyWyFD1xk8 Qrc5kIbldOFoyE8BcNZaD8d0Xh4eCkeciwOV3FwHR4brjJgcnRwI7+5iJkf1ZLiv 0sE9EORv/fzOeaOh1DaJdFCrfrmbgdgOUm62WNQOB2hq0kggjh/S1K+TBfF+8QFs XQyW7y7mHecNgCgK/pI5b1irdajRc7nLvzM/U8qNn4jjrLsRoYqBPpn7aLKIBrmN pNSIe18q8EYWkdmWBcnsZpAYv75SJG8E0lAYpMv9OEUIwsPh7AYUdkZqKtFxVxV5 bYlA5ZfnVnWrWEwRXaVdFFRXIjP+EFkGYYWThbvAIb0TPQIDAQABo1MwUTAdBgNV HQ4EFgQUiW1MoYR8YK9KJTyip5oFoUVJoCgwHwYDVR0jBBgwFoAUiW1MoYR8YK9K JTyip5oFoUVJoCgwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEA BToM8SbUQXwJ9rTlQB2QI2GJaFwTpCFoQZwGUOCkwGLM3nOPLEbNPMDoIKGPwenB P1xL8uJEgYRqP6UG/xy3HsxYsLCxuoxGGP2QjuiQKnFl0n85usZ5flCxmLC5IzYx FLcR6WPTdj6b5JX0tM8Bi6toQ9Pj3u3dSVPZKRLYvJvZKt1PXI8qsHD/LvNa2wGG Zi1BQFAr2cScNYa+p6IYDJi9TBNxoBIHNTzQPfWaen4MHRJqUNZCzQXcOnU/NW5G +QqQSEMmk8yGucEHWUMFrEbABVgYuBslICEEtBZALB2jZJYSaJnPOJCcmFrxUv61 ORWZbz+8rBL0JIeA7eFxEA== -----END CERTIFICATE-----`, 'utf8'); return { key, cert }; } /** * Create TLS options for secure server or STARTTLS * @param certificates - Certificate data * @param isServer - Whether this is for server (true) or client (false) * @returns TLS options */ export function createTlsOptions(certificates, isServer = true) { const options = { key: certificates.key, cert: certificates.cert, ca: certificates.ca, // Support a wider range of TLS versions for better compatibility minVersion: 'TLSv1', // Support older TLS versions (minimum TLS 1.0) maxVersion: 'TLSv1.3', // Support latest TLS version (1.3) // Cipher suites for broad compatibility ciphers: 'HIGH:MEDIUM:!aNULL:!eNULL:!NULL:!ADH:!RC4', // For testing, allow unauthorized (self-signed certs) rejectUnauthorized: false, // Longer handshake timeout for reliability handshakeTimeout: 30000, // TLS renegotiation option (removed - not supported in newer Node.js) // Increase timeout for better reliability under test conditions sessionTimeout: 600, // Let the client choose the cipher for better compatibility honorCipherOrder: false, // For debugging enableTrace: true, // Disable secure options to allow more flexibility secureOptions: 0 }; // Server-specific options if (isServer) { options.ALPNProtocols = ['smtp']; // Accept non-ALPN connections (legacy clients) } return options; } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2VydGlmaWNhdGUtdXRpbHMuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi90cy9tYWlsL2RlbGl2ZXJ5L3NtdHBzZXJ2ZXIvY2VydGlmaWNhdGUtdXRpbHMudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7OztHQUdHO0FBRUgsT0FBTyxLQUFLLEVBQUUsTUFBTSxJQUFJLENBQUM7QUFDekIsT0FBTyxLQUFLLEdBQUcsTUFBTSxLQUFLLENBQUM7QUFDM0IsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBV2hEOzs7O0dBSUc7QUFDSCxTQUFTLG9CQUFvQixDQUFDLEdBQW9CO0lBQ2hELCtCQUErQjtJQUMvQixJQUFJLFFBQWdCLENBQUM7SUFFckIsSUFBSSxNQUFNLENBQUMsUUFBUSxDQUFDLEdBQUcsQ0FBQyxFQUFFLENBQUM7UUFDekIsK0NBQStDO1FBQy9DLFFBQVEsR0FBRyxHQUFHLENBQUMsUUFBUSxDQUFDLE1BQU0sQ0FBQyxDQUFDO0lBQ2xDLENBQUM7U0FBTSxJQUFJLE9BQU8sR0FBRyxLQUFLLFFBQVEsRUFBRSxDQUFDO1FBQ25DLFFBQVEsR0FBRyxHQUFHLENBQUM7SUFDakIsQ0FBQztTQUFNLENBQUM7UUFDTixNQUFNLElBQUksS0FBSyxDQUFDLHdDQUF3QyxDQUFDLENBQUM7SUFDNUQsQ0FBQztJQUVELElBQUksQ0FBQyxRQUFRLEVBQUUsQ0FBQztRQUNkLE1BQU0sSUFBSSxLQUFLLENBQUMsd0JBQXdCLENBQUMsQ0FBQztJQUM1QyxDQUFDO0lBRUQsMENBQTBDO0lBQzFDLElBQUksYUFBYSxHQUFHLFFBQVEsQ0FBQyxJQUFJLEVBQUUsQ0FBQztJQUVwQyxxQ0FBcUM7SUFDckMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxRQUFRLENBQUMsYUFBYSxDQUFDLEVBQUUsQ0FBQztRQUMzQyxNQUFNLElBQUksS0FBSyxDQUFDLGtEQUFrRCxDQUFDLENBQUM7SUFDdEUsQ0FBQztJQUVELElBQUks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