/**
 * 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, 