platformservice/ts/mta/mta.classes.dkimcreator.ts
2024-02-16 20:42:26 +01:00

120 lines
4.0 KiB
TypeScript

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<IKeyPaths> {
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<void> {
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<void> {
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<void> {
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<void> {
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<plugins.tsclass.network.IDnsRecord> {
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,
};
}
}