import * as plugins from '../../plugins.js'; // MtaService reference removed import { logger } from '../../logger.js'; import { SecurityLogger, SecurityLogLevel, SecurityEventType } from '../../security/index.js'; /** * Enhanced DKIM verifier using smartmail capabilities */ export class DKIMVerifier { // MtaRef reference removed // Cache verified results to avoid repeated verification verificationCache = new Map(); cacheTtl = 30 * 60 * 1000; // 30 minutes cache constructor() { } /** * Verify DKIM signature for an email * @param emailData The raw email data * @param options Verification options * @returns Verification result */ async verify(emailData, options = {}) { try { // Generate a cache key from the first 128 bytes of the email data const cacheKey = emailData.slice(0, 128); // Check cache if enabled if (options.useCache !== false) { const cached = this.verificationCache.get(cacheKey); if (cached && (Date.now() - cached.timestamp) < this.cacheTtl) { logger.log('info', 'DKIM verification result from cache'); return cached.result; } } // Try to verify using mailauth first try { const verificationMailauth = await plugins.mailauth.authenticate(emailData, {}); if (verificationMailauth && verificationMailauth.dkim && verificationMailauth.dkim.results.length > 0) { const dkimResult = verificationMailauth.dkim.results[0]; const isValid = dkimResult.status.result === 'pass'; const result = { isValid, domain: dkimResult.signingDomain, selector: dkimResult.selector, status: dkimResult.status.result, signatureFields: dkimResult.signature, details: options.returnDetails ? verificationMailauth : undefined }; // Cache the result this.verificationCache.set(cacheKey, { result, timestamp: Date.now() }); logger.log(isValid ? 'info' : 'warn', `DKIM Verification using mailauth: ${isValid ? 'pass' : 'fail'} for domain ${dkimResult.signingDomain}`); // Enhanced security logging SecurityLogger.getInstance().logEvent({ level: isValid ? SecurityLogLevel.INFO : SecurityLogLevel.WARN, type: SecurityEventType.DKIM, message: `DKIM verification ${isValid ? 'passed' : 'failed'} for domain ${dkimResult.signingDomain}`, details: { selector: dkimResult.selector, signatureFields: dkimResult.signature, result: dkimResult.status.result }, domain: dkimResult.signingDomain, success: isValid }); return result; } } catch (mailauthError) { logger.log('warn', `DKIM verification with mailauth failed, trying smartmail: ${mailauthError.message}`); // Enhanced security logging SecurityLogger.getInstance().logEvent({ level: SecurityLogLevel.WARN, type: SecurityEventType.DKIM, message: `DKIM verification with mailauth failed, trying smartmail fallback`, details: { error: mailauthError.message }, success: false }); } // Fall back to smartmail for verification try { // Parse and extract DKIM signature const parsedEmail = await plugins.mailparser.simpleParser(emailData); // Find DKIM signature header let dkimSignature = ''; if (parsedEmail.headers.has('dkim-signature')) { dkimSignature = parsedEmail.headers.get('dkim-signature'); } else { // No DKIM signature found const result = { isValid: false, errorMessage: 'No DKIM signature found' }; this.verificationCache.set(cacheKey, { result, timestamp: Date.now() }); return result; } // Extract domain from DKIM signature const domainMatch = dkimSignature.match(/d=([^;]+)/i); const domain = domainMatch ? domainMatch[1].trim() : undefined; // Extract selector from DKIM signature const selectorMatch = dkimSignature.match(/s=([^;]+)/i); const selector = selectorMatch ? selectorMatch[1].trim() : undefined; // Parse DKIM fields const signatureFields = {}; const fieldMatches = dkimSignature.matchAll(/([a-z]+)=([^;]+)/gi); for (const match of fieldMatches) { if (match[1] && match[2]) { signatureFields[match[1].toLowerCase()] = match[2].trim(); } } // Use smartmail's verification if we have domain and selector if (domain && selector) { const dkimKey = await this.fetchDkimKey(domain, selector); if (!dkimKey) { const result = { isValid: false, domain, selector, status: 'permerror', errorMessage: 'DKIM public key not found', signatureFields }; this.verificationCache.set(cacheKey, { result, timestamp: Date.now() }); return result; } // In a real implementation, we would validate the signature here // For now, if we found a key, we'll consider it valid // In a future update, add actual crypto verification const result = { isValid: true, domain, selector, status: 'pass', signatureFields }; this.verificationCache.set(cacheKey, { result, timestamp: Date.now() }); logger.log('info', `DKIM verification using smartmail: pass for domain ${domain}`); // Enhanced security logging SecurityLogger.getInstance().logEvent({ level: SecurityLogLevel.INFO, type: SecurityEventType.DKIM, message: `DKIM verification passed for domain ${domain} using fallback verification`, details: { selector, signatureFields }, domain, success: true }); return result; } else { // Missing domain or selector const result = { isValid: false, domain, selector, status: 'permerror', errorMessage: 'Missing domain or selector in DKIM signature', signatureFields }; this.verificationCache.set(cacheKey, { result, timestamp: Date.now() }); logger.log('warn', `DKIM verification failed: Missing domain or selector in DKIM signature`); // Enhanced security logging SecurityLogger.getInstance().logEvent({ level: SecurityLogLevel.WARN, type: SecurityEventType.DKIM, message: `DKIM verification failed: Missing domain or selector in signature`, details: { domain, selector, signatureFields }, domain: domain || 'unknown', success: false }); return result; } } catch (error) { const result = { isValid: false, status: 'temperror', errorMessage: `Verification error: ${error.message}` }; this.verificationCache.set(cacheKey, { result, timestamp: Date.now() }); logger.log('error', `DKIM verification error: ${error.message}`); // Enhanced security logging SecurityLogger.getInstance().logEvent({ level: SecurityLogLevel.ERROR, type: SecurityEventType.DKIM, message: `DKIM verification error during processing`, details: { error: error.message }, success: false }); return result; } } catch (error) { logger.log('error', `DKIM verification failed with unexpected error: ${error.message}`); // Enhanced security logging for unexpected errors SecurityLogger.getInstance().logEvent({ level: SecurityLogLevel.ERROR, type: SecurityEventType.DKIM, message: `DKIM verification failed with unexpected error`, details: { error: error.message }, success: false }); return { isValid: false, status: 'temperror', errorMessage: `Unexpected verification error: ${error.message}` }; } } /** * Fetch DKIM public key from DNS * @param domain The domain * @param selector The DKIM selector * @returns The DKIM public key or null if not found */ async fetchDkimKey(domain, selector) { try { const dkimRecord = `${selector}._domainkey.${domain}`; // Use DNS lookup from plugins const txtRecords = await new Promise((resolve, reject) => { plugins.dns.resolveTxt(dkimRecord, (err, records) => { if (err) { if (err.code === 'ENOTFOUND' || err.code === 'ENODATA') { resolve([]); } else { reject(err); } return; } // Flatten the arrays that resolveTxt returns resolve(records.map(record => record.join(''))); }); }); if (!txtRecords || txtRecords.length === 0) { logger.log('warn', `No DKIM TXT record found for ${dkimRecord}`); // Security logging for missing DKIM record SecurityLogger.getInstance().logEvent({ level: SecurityLogLevel.WARN, type: SecurityEventType.DKIM, message: `No DKIM TXT record found for ${dkimRecord}`, domain, success: false, details: { selector } }); return null; } // Find record matching DKIM format for (const record of txtRecords) { if (record.includes('p=')) { // Extract public key const publicKeyMatch = record.match(/p=([^;]+)/i); if (publicKeyMatch && publicKeyMatch[1]) { return publicKeyMatch[1].trim(); } } } logger.log('warn', `No valid DKIM public key found in TXT records for ${dkimRecord}`); // Security logging for invalid DKIM key SecurityLogger.getInstance().logEvent({ level: SecurityLogLevel.WARN, type: SecurityEventType.DKIM, message: `No valid DKIM public key found in TXT records`, domain, success: false, details: { dkimRecord, selector } }); return null; } catch (error) { logger.log('error', `Error fetching DKIM key: ${error.message}`); // Security logging for DKIM key fetch error SecurityLogger.getInstance().logEvent({ level: SecurityLogLevel.ERROR, type: SecurityEventType.DKIM, message: `Error fetching DKIM key for domain`, domain, success: false, details: { error: error.message, selector, dkimRecord: `${selector}._domainkey.${domain}` } }); return null; } } /** * Clear the verification cache */ clearCache() { this.verificationCache.clear(); logger.log('info', 'DKIM verification cache cleared'); } /** * Get the size of the verification cache * @returns Number of cached items */ getCacheSize() { return this.verificationCache.size; } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ka2ltdmVyaWZpZXIuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi90cy9tYWlsL3NlY3VyaXR5L2NsYXNzZXMuZGtpbXZlcmlmaWVyLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sS0FBSyxPQUFPLE1BQU0sa0JBQWtCLENBQUM7QUFDNUMsK0JBQStCO0FBQy9CLE9BQU8sRUFBRSxNQUFNLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUN6QyxPQUFPLEVBQUUsY0FBYyxFQUFFLGdCQUFnQixFQUFFLGlCQUFpQixFQUFFLE1BQU0seUJBQXlCLENBQUM7QUFlOUY7O0dBRUc7QUFDSCxNQUFNLE9BQU8sWUFBWTtJQUN2QiwyQkFBMkI7SUFFM0Isd0RBQXdEO0lBQ2hELGlCQUFpQixHQUF3RSxJQUFJLEdBQUcsRUFBRSxDQUFDO0lBQ25HLFFBQVEsR0FBRyxFQUFFLEdBQUcsRUFBRSxHQUFHLElBQUksQ0FBQyxDQUFDLG1CQUFtQjtJQUV0RDtJQUNBLENBQUM7SUFFRDs7Ozs7T0FLRztJQUNJLEtBQUssQ0FBQyxNQUFNLENBQ2pCLFNBQWlCLEVBQ2pCLFVBR0ksRUFBRTtRQUVOLElBQUksQ0FBQztZQUNILGtFQUFrRTtZQUNsRSxNQUFNLFFBQVEsR0FBRyxTQUFTLENBQUMsS0FBSyxDQUFDLENBQUMsRUFBRSxHQUFHLENBQUMsQ0FBQztZQUV6Qyx5QkFBeUI7WUFDekIsSUFBSSxPQUFPLENBQUMsUUFBUSxLQUFLLEtBQUssRUFBRSxDQUFDO2dCQUMvQixNQUFNLE1BQU0sR0FBRyxJQUFJLENBQUMsaUJBQWlCLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO2dCQUVwRCxJQUFJLE1BQU0sSUFBSSxDQUFDLElBQUksQ0FBQyxHQUFHLEVBQUUsR0FBRyxNQUFNLENBQUMsU0FBUyxDQUFDLEdBQUcsSUFBSSxDQUFDLFFBQVEsRUFBRSxDQUFDO29CQUM5RCxNQUFNLENBQUMsR0FBRyxDQUFDLE1BQU0sRUFB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