import * as plugins from '../plugins.js'; import * as paths from '../paths.js'; import { Email } from './mta.classes.email.js'; import type { MtaService } from './mta.classes.mta.js'; 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 interface IKeyPaths { privateKeyPath: string; publicKeyPath: string; } export class DKIMCreator { private keysDir: string; constructor(metaRef: MtaService, keysDir = paths.keysDir) { this.keysDir = keysDir; } public async getKeyPathsForDomain(domainArg: string): Promise { 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 public async handleDKIMKeysForDomain(domainArg: string): Promise { 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); plugins.smartfile.fs.ensureDirSync(paths.dnsRecordsDir); plugins.smartfile.memory.toFsSync(JSON.stringify(dnsValue, null, 2), plugins.path.join(paths.dnsRecordsDir, `${domainArg}.dkimrecord.json`)); } } public async handleDKIMKeysForEmail(email: Email): Promise { const domain = email.from.split('@')[1]; await this.handleDKIMKeysForDomain(domain); } // Read DKIM keys from disk public async readDKIMKeys(domainArg: string): Promise<{ privateKey: string; publicKey: string }> { const keyPaths = await this.getKeyPathsForDomain(domainArg); 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(); return { privateKey, publicKey }; } // Create a DKIM key pair private async createDKIMKeys(): Promise<{ privateKey: string; publicKey: string }> { 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 to disk private async storeDKIMKeys( privateKey: string, publicKey: string, privateKeyPath: string, publicKeyPath: string ): Promise { await Promise.all([writeFile(privateKeyPath, privateKey), writeFile(publicKeyPath, publicKey)]); } // Create a DKIM key pair and store it to disk private async createAndStoreDKIMKeys(domain: string): Promise { 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.`); } private async getDNSRecordForDomain(domainArg: string): Promise { 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, }; } }