import * as plugins from '../../plugins.js'; import * as paths from '../../paths.js'; import { Email } from '../core/classes.email.js'; // MtaService reference removed const readFile = plugins.util.promisify(plugins.fs.readFile); const writeFile = plugins.util.promisify(plugins.fs.writeFile); const generateKeyPair = plugins.util.promisify(plugins.crypto.generateKeyPair); export class DKIMCreator { keysDir; storageManager; // StorageManager instance constructor(keysDir = paths.keysDir, storageManager) { this.keysDir = keysDir; this.storageManager = storageManager; } async getKeyPathsForDomain(domainArg) { return { privateKeyPath: plugins.path.join(this.keysDir, `${domainArg}-private.pem`), publicKeyPath: plugins.path.join(this.keysDir, `${domainArg}-public.pem`), }; } // Check if a DKIM key is present and creates one and stores it to disk otherwise async handleDKIMKeysForDomain(domainArg) { try { await this.readDKIMKeys(domainArg); } catch (error) { console.log(`No DKIM keys found for ${domainArg}. Generating...`); await this.createAndStoreDKIMKeys(domainArg); const dnsValue = await this.getDNSRecordForDomain(domainArg); await plugins.smartfs.directory(paths.dnsRecordsDir).recursive().create(); await plugins.smartfs.file(plugins.path.join(paths.dnsRecordsDir, `${domainArg}.dkimrecord.json`)).write(JSON.stringify(dnsValue, null, 2)); } } async handleDKIMKeysForEmail(email) { const domain = email.from.split('@')[1]; await this.handleDKIMKeysForDomain(domain); } // Read DKIM keys - always use storage manager, migrate from filesystem if needed async readDKIMKeys(domainArg) { // Try to read from storage manager first if (this.storageManager) { try { const [privateKey, publicKey] = await Promise.all([ this.storageManager.get(`/email/dkim/${domainArg}/private.key`), this.storageManager.get(`/email/dkim/${domainArg}/public.key`) ]); if (privateKey && publicKey) { return { privateKey, publicKey }; } } catch (error) { // Fall through to migration check } // Check if keys exist in filesystem and migrate them to storage manager const keyPaths = await this.getKeyPathsForDomain(domainArg); try { const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([ readFile(keyPaths.privateKeyPath), readFile(keyPaths.publicKeyPath), ]); // Convert the buffers to strings const privateKey = privateKeyBuffer.toString(); const publicKey = publicKeyBuffer.toString(); // Migrate to storage manager console.log(`Migrating DKIM keys for ${domainArg} from filesystem to StorageManager`); await Promise.all([ this.storageManager.set(`/email/dkim/${domainArg}/private.key`, privateKey), this.storageManager.set(`/email/dkim/${domainArg}/public.key`, publicKey) ]); return { privateKey, publicKey }; } catch (error) { if (error.code === 'ENOENT') { // Keys don't exist anywhere throw new Error(`DKIM keys not found for domain ${domainArg}`); } throw error; } } else { // No storage manager, use filesystem directly const keyPaths = await this.getKeyPathsForDomain(domainArg); const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([ readFile(keyPaths.privateKeyPath), readFile(keyPaths.publicKeyPath), ]); const privateKey = privateKeyBuffer.toString(); const publicKey = publicKeyBuffer.toString(); return { privateKey, publicKey }; } } // Create a DKIM key pair - changed to public for API access async createDKIMKeys() { const { privateKey, publicKey } = await generateKeyPair('rsa', { modulusLength: 2048, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs1', format: 'pem' }, }); return { privateKey, publicKey }; } // Store a DKIM key pair - uses storage manager if available, else disk async storeDKIMKeys(privateKey, publicKey, privateKeyPath, publicKeyPath) { // Store in storage manager if available if (this.storageManager) { // Extract domain from path (e.g., /path/to/keys/example.com-private.pem -> example.com) const match = privateKeyPath.match(/\/([^\/]+)-private\.pem$/); if (match) { const domain = match[1]; await Promise.all([ this.storageManager.set(`/email/dkim/${domain}/private.key`, privateKey), this.storageManager.set(`/email/dkim/${domain}/public.key`, publicKey) ]); } } // Also store to filesystem for backward compatibility await Promise.all([writeFile(privateKeyPath, privateKey), writeFile(publicKeyPath, publicKey)]); } // Create a DKIM key pair and store it to disk - changed to public for API access async createAndStoreDKIMKeys(domain) { const { privateKey, publicKey } = await this.createDKIMKeys(); const keyPaths = await this.getKeyPathsForDomain(domain); await this.storeDKIMKeys(privateKey, publicKey, keyPaths.privateKeyPath, keyPaths.publicKeyPath); console.log(`DKIM keys for ${domain} created and stored.`); } // Changed to public for API access async getDNSRecordForDomain(domainArg) { await this.handleDKIMKeysForDomain(domainArg); const keys = await this.readDKIMKeys(domainArg); // Remove the PEM header and footer and newlines const pemHeader = '-----BEGIN PUBLIC KEY-----'; const pemFooter = '-----END PUBLIC KEY-----'; const keyContents = keys.publicKey .replace(pemHeader, '') .replace(pemFooter, '') .replace(/\n/g, ''); // Now generate the DKIM DNS TXT record const dnsRecordValue = `v=DKIM1; h=sha256; k=rsa; p=${keyContents}`; return { name: `mta._domainkey.${domainArg}`, type: 'TXT', dnsSecEnabled: null, value: dnsRecordValue, }; } /** * Get DKIM key metadata for a domain */ async getKeyMetadata(domain, selector = 'default') { if (!this.storageManager) { return null; } const metadataKey = `/email/dkim/${domain}/${selector}/metadata`; const metadataStr = await this.storageManager.get(metadataKey); if (!metadataStr) { return null; } return JSON.parse(metadataStr); } /** * Save DKIM key metadata */ async saveKeyMetadata(metadata) { if (!this.storageManager) { return; } const metadataKey = `/email/dkim/${metadata.domain}/${metadata.selector}/metadata`; await this.storageManager.set(metadataKey, JSON.stringify(metadata)); } /** * Check if DKIM keys need rotation */ async needsRotation(domain, selector = 'default', rotationIntervalDays = 90) { const metadata = await this.getKeyMetadata(domain, selector); if (!metadata) { // No metadata means old keys, should rotate return true; } const now = Date.now(); const keyAgeMs = now - metadata.createdAt; const keyAgeDays = keyAgeMs / (1000 * 60 * 60 * 24); return keyAgeDays >= rotationIntervalDays; } /** * Rotate DKIM keys for a domain */ async rotateDkimKeys(domain, currentSelector = 'default', keySize = 2048) { console.log(`Rotating DKIM keys for ${domain}...`); // Generate new selector based on date const now = new Date(); const newSelector = `key${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}`; // Create new keys with custom key size const { privateKey, publicKey } = await generateKeyPair('rsa', { modulusLength: keySize, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs1', format: 'pem' }, }); // Store new keys with new selector const newKeyPaths = await this.getKeyPathsForSelector(domain, newSelector); // Store in storage manager if available if (this.storageManager) { await Promise.all([ this.storageManager.set(`/email/dkim/${domain}/${newSelector}/private.key`, privateKey), this.storageManager.set(`/email/dkim/${domain}/${newSelector}/public.key`, publicKey) ]); } // Also store to filesystem await this.storeDKIMKeys(privateKey, publicKey, newKeyPaths.privateKeyPath, newKeyPaths.publicKeyPath); // Save metadata for new keys const metadata = { domain, selector: newSelector, createdAt: Date.now(), previousSelector: currentSelector, keySize }; await this.saveKeyMetadata(metadata); // Update metadata for old keys const oldMetadata = await this.getKeyMetadata(domain, currentSelector); if (oldMetadata) { oldMetadata.rotatedAt = Date.now(); await this.saveKeyMetadata(oldMetadata); } console.log(`DKIM keys rotated for ${domain}. New selector: ${newSelector}`); return newSelector; } /** * Get key paths for a specific selector */ async getKeyPathsForSelector(domain, selector) { return { privateKeyPath: plugins.path.join(this.keysDir, `${domain}-${selector}-private.pem`), publicKeyPath: plugins.path.join(this.keysDir, `${domain}-${selector}-public.pem`), }; } /** * Read DKIM keys for a specific selector */ async readDKIMKeysForSelector(domain, selector) { // Try to read from storage manager first if (this.storageManager) { try { const [privateKey, publicKey] = await Promise.all([ this.storageManager.get(`/email/dkim/${domain}/${selector}/private.key`), this.storageManager.get(`/email/dkim/${domain}/${selector}/public.key`) ]); if (privateKey && publicKey) { return { privateKey, publicKey }; } } catch (error) { // Fall through to migration check } // Check if keys exist in filesystem and migrate them to storage manager const keyPaths = await this.getKeyPathsForSelector(domain, selector); try { const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([ readFile(keyPaths.privateKeyPath), readFile(keyPaths.publicKeyPath), ]); const privateKey = privateKeyBuffer.toString(); const publicKey = publicKeyBuffer.toString(); // Migrate to storage manager console.log(`Migrating DKIM keys for ${domain}/${selector} from filesystem to StorageManager`); await Promise.all([ this.storageManager.set(`/email/dkim/${domain}/${selector}/private.key`, privateKey), this.storageManager.set(`/email/dkim/${domain}/${selector}/public.key`, publicKey) ]); return { privateKey, publicKey }; } catch (error) { if (error.code === 'ENOENT') { throw new Error(`DKIM keys not found for domain ${domain} with selector ${selector}`); } throw error; } } else { // No storage manager, use filesystem directly const keyPaths = await this.getKeyPathsForSelector(domain, selector); const [privateKeyBuffer, publicKeyBuffer] = await Promise.all([ readFile(keyPaths.privateKeyPath), readFile(keyPaths.publicKeyPath), ]); const privateKey = privateKeyBuffer.toString(); const publicKey = publicKeyBuffer.toString(); return { privateKey, publicKey }; } } /** * Get DNS record for a specific selector */ async getDNSRecordForSelector(domain, selector) { const keys = await this.readDKIMKeysForSelector(domain, selector); // Remove the PEM header and footer and newlines const pemHeader = '-----BEGIN PUBLIC KEY-----'; const pemFooter = '-----END PUBLIC KEY-----'; const keyContents = keys.publicKey .replace(pemHeader, '') .replace(pemFooter, '') .replace(/\n/g, ''); // Generate the DKIM DNS TXT record const dnsRecordValue = `v=DKIM1; h=sha256; k=rsa; p=${keyContents}`; return { name: `${selector}._domainkey.${domain}`, type: 'TXT', dnsSecEnabled: null, value: dnsRecordValue, }; } /** * Clean up old DKIM keys after grace period */ async cleanupOldKeys(domain, gracePeriodDays = 30) { if (!this.storageManager) { return; } // List all selectors for the domain const metadataKeys = await this.storageManager.list(`/email/dkim/${domain}/`); for (const key of metadataKeys) { if (key.endsWith('/metadata')) { const metadataStr = await this.storageManager.get(key); if (metadataStr) { const metadata = JSON.parse(metadataStr); // Check if key is rotated and past grace period if (metadata.rotatedAt) { const gracePeriodMs = gracePeriodDays * 24 * 60 * 60 * 1000; const now = Date.now(); if (now - metadata.rotatedAt > gracePeriodMs) { console.log(`Cleaning up old DKIM keys for ${domain} selector ${metadata.selector}`); // Delete key files const keyPaths = await this.getKeyPathsForSelector(domain, metadata.selector); try { await plugins.fs.promises.unlink(keyPaths.privateKeyPath); await plugins.fs.promises.unlink(keyPaths.publicKeyPath); } catch (error) { console.warn(`Failed to delete old key files: ${error.message}`); } // Delete metadata await this.storageManager.delete(key); } } } } } } } //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY2xhc3Nlcy5ka2ltY3JlYXRvci5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL3RzL21haWwvc2VjdXJpdHkvY2xhc3Nlcy5ka2ltY3JlYXRvci50cyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiQUFBQSxPQUFPLEtBQUssT0FBTyxNQUFNLGtCQUFrQixDQUFDO0FBQzVDLE9BQU8sS0FBSyxLQUFLLE1BQU0sZ0JBQWdCLENBQUM7QUFFeEMsT0FBTyxFQUFFLEtBQUssRUFBRSxNQUFNLDBCQUEwQixDQUFDO0FBQ2pELCtCQUErQjtBQUUvQixNQUFNLFFBQVEsR0FBRyxPQUFPLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLFFBQVEsQ0FBQyxDQUFDO0FBQzdELE1BQU0sU0FBUyxHQUFHLE9BQU8sQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsU0FBUyxDQUFDLENBQUM7QUFDL0QsTUFBTSxlQUFlLEdBQUcsT0FBTyxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLE1BQU0sQ0FBQyxlQUFlLENBQUMsQ0FBQztBQWdCL0UsTUFBTSxPQUFPLFdBQVc7SUFDZCxPQUFPLENBQVM7SUFDaEIsY0FBYyxDQUFPLENBQUMsMEJBQTBCO0lBRXhELFlBQVksT0FBTyxHQUFHLEtBQUssQ0FBQyxPQUFPLEVBQUUsY0FBb0I7UUFDdkQsSUFBSSxDQUFDLE9BQU8sR0FBRyxPQUFPLENBQUM7UUFDdkIsSUFBSSxDQUFDLGNBQWMsR0FBRyxjQUFjLENBQUM7SUFDdkMsQ0FBQztJQUVNLEtBQUssQ0FBQyxvQkFBb0IsQ0FBQyxTQUFpQjtRQUNqRCxPQUFPO1lBQ0wsY0FBYyxFQUFFLE9BQU8sQ0FBQyxJQUFJLENBQUMsSUFBSSxDQUFDLElBQUksQ0FBQyxPQUFPLEVBQUUsR0FBRyxTQUFTLGNBQWMsQ0FBQztZQUMzRSxhQUFhLEVBQUUsT0FBTyxDQUFDLElBQUksQ0FBQyxJQUFJ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