2024-02-16 12:41:04 +00:00
|
|
|
import * as plugins from '../plugins.js';
|
|
|
|
import * as paths from '../paths.js';
|
2024-02-16 12:28:40 +00:00
|
|
|
|
|
|
|
import { Email } from './mta.classes.email.js';
|
2024-02-16 19:42:26 +00:00
|
|
|
import type { MtaService } from './mta.classes.mta.js';
|
2024-02-16 12:28:40 +00:00
|
|
|
|
|
|
|
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;
|
|
|
|
|
2024-02-16 19:42:26 +00:00
|
|
|
constructor(metaRef: MtaService, keysDir = paths.keysDir) {
|
2024-02-16 12:28:40 +00:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
}
|