| 
									
										
										
										
											2025-10-24 08:09:29 +00:00
										 |  |  | /** | 
					
						
							|  |  |  |  * Certificate Utilities for SMTP Server | 
					
						
							|  |  |  |  * Provides utilities for managing TLS certificates | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-10-24 10:00:25 +00:00
										 |  |  | import * as plugins from '../../../plugins.ts'; | 
					
						
							| 
									
										
										
										
											2025-10-24 08:09:29 +00:00
										 |  |  | import { SmtpLogger } from './utils/logging.ts'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Certificate data | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | export interface ICertificateData { | 
					
						
							| 
									
										
										
										
											2025-10-24 10:00:25 +00:00
										 |  |  |   key: plugins.Buffer; | 
					
						
							|  |  |  |   cert: plugins.Buffer; | 
					
						
							|  |  |  |   ca?: plugins.Buffer; | 
					
						
							| 
									
										
										
										
											2025-10-24 08:09:29 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /** | 
					
						
							|  |  |  |  * Normalize a PEM certificate string | 
					
						
							|  |  |  |  * @param str - Certificate string | 
					
						
							|  |  |  |  * @returns Normalized certificate string | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | function normalizeCertificate(str: string | Buffer): string { | 
					
						
							|  |  |  |   // Handle different input types
 | 
					
						
							|  |  |  |   let inputStr: string; | 
					
						
							|  |  |  |    | 
					
						
							|  |  |  |   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: { | 
					
						
							|  |  |  |   key: string | Buffer; | 
					
						
							|  |  |  |   cert: string | Buffer; | 
					
						
							|  |  |  |   ca?: string | Buffer; | 
					
						
							|  |  |  | }): ICertificateData { | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     // First try to use certificates without normalization
 | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       let keyStr: string; | 
					
						
							|  |  |  |       let certStr: string; | 
					
						
							|  |  |  |       let caStr: string | undefined; | 
					
						
							|  |  |  |        | 
					
						
							|  |  |  |       // 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
 | 
					
						
							| 
									
										
										
										
											2025-10-24 10:00:25 +00:00
										 |  |  |       const secureContext = plugins.tls.createSecureContext({ | 
					
						
							| 
									
										
										
										
											2025-10-24 08:09:29 +00:00
										 |  |  |         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 { | 
					
						
							| 
									
										
										
										
											2025-10-24 10:00:25 +00:00
										 |  |  |         const secureContext = plugins.tls.createSecureContext({ | 
					
						
							| 
									
										
										
										
											2025-10-24 08:09:29 +00:00
										 |  |  |           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: { | 
					
						
							|  |  |  |   keyPath: string; | 
					
						
							|  |  |  |   certPath: string; | 
					
						
							|  |  |  |   caPath?: string; | 
					
						
							|  |  |  | }): ICertificateData { | 
					
						
							|  |  |  |   try { | 
					
						
							|  |  |  |     // Read files directly as Buffers
 | 
					
						
							| 
									
										
										
										
											2025-10-24 10:00:25 +00:00
										 |  |  |     const key = plugins.fs.readFileSync(options.keyPath); | 
					
						
							|  |  |  |     const cert = plugins.fs.readFileSync(options.certPath); | 
					
						
							|  |  |  |     const ca = options.caPath ? plugins.fs.readFileSync(options.caPath) : undefined; | 
					
						
							| 
									
										
										
										
											2025-10-24 08:09:29 +00:00
										 |  |  |      | 
					
						
							|  |  |  |     // 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 { | 
					
						
							| 
									
										
										
										
											2025-10-24 10:00:25 +00:00
										 |  |  |       const secureContext = plugins.tls.createSecureContext({ | 
					
						
							| 
									
										
										
										
											2025-10-24 08:09:29 +00:00
										 |  |  |         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(): ICertificateData { | 
					
						
							|  |  |  |   // 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: ICertificateData, | 
					
						
							|  |  |  |   isServer: boolean = true | 
					
						
							| 
									
										
										
										
											2025-10-24 10:00:25 +00:00
										 |  |  | ): plugins.tls.TlsOptions { | 
					
						
							|  |  |  |   const options: plugins.tls.TlsOptions = { | 
					
						
							| 
									
										
										
										
											2025-10-24 08:09:29 +00:00
										 |  |  |     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.ts)
 | 
					
						
							|  |  |  |     // 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; | 
					
						
							|  |  |  | } |