From 439d08b023ce991c75e6f41da701068f14458856 Mon Sep 17 00:00:00 2001 From: Philipp Kunz Date: Thu, 19 Sep 2024 18:23:42 +0200 Subject: [PATCH] fix(ts_server): Update DnsSec class to fully implement key generation and DNSKEY record creation. --- changelog.md | 7 ++ ts_server/classes.dnstools.ts | 183 +++++++++++++++++++++++++--------- ts_server/plugins.ts | 2 + 3 files changed, 145 insertions(+), 47 deletions(-) diff --git a/changelog.md b/changelog.md index b960145..9cbe683 100644 --- a/changelog.md +++ b/changelog.md @@ -1,5 +1,12 @@ # Changelog +## 2024-09-19 - 6.1.1 - fix(ts_server) +Update DnsSec class to fully implement key generation and DNSKEY record creation. + +- Added complete support for ECDSA and ED25519 algorithms in the DnsSec class. +- Implemented DNSKEY generation and KeyTag computation methods. +- Improved error handling and initialized the appropriate cryptographic instances based on the algorithm. + ## 2024-09-18 - 6.1.0 - feat(smartdns) Add DNS Server and DNSSEC tools with comprehensive unit tests diff --git a/ts_server/classes.dnstools.ts b/ts_server/classes.dnstools.ts index a5437b8..adac655 100644 --- a/ts_server/classes.dnstools.ts +++ b/ts_server/classes.dnstools.ts @@ -1,83 +1,172 @@ +// Import necessary plugins from plugins.ts import * as plugins from './plugins.js'; interface DnssecZone { zone: string; - algorithm: string; + algorithm: 'ECDSA' | 'ED25519' | 'RSA'; keySize: number; days: number; } interface DnssecKeyPair { - private: string; - public: string; + privateKey: string; + publicKey: string; } -class DnsSec { +export class DnsSec { private zone: DnssecZone; private keyPair: DnssecKeyPair; - private ec: any; // declare the ec instance + private ec?: plugins.elliptic.ec; // For ECDSA algorithms + private eddsa?: plugins.elliptic.eddsa; // For EdDSA algorithms constructor(zone: DnssecZone) { this.zone = zone; - this.ec = new plugins.elliptic.ec('secp256k1'); // Create an instance of the secp256k1 curve + + // Initialize the appropriate cryptographic instance based on the algorithm + switch (this.zone.algorithm) { + case 'ECDSA': + this.ec = new plugins.elliptic.ec('p256'); // Use P-256 curve for ECDSA + break; + case 'ED25519': + this.eddsa = new plugins.elliptic.eddsa('ed25519'); + break; + case 'RSA': + // RSA implementation would go here + throw new Error('RSA algorithm is not yet implemented.'); + default: + throw new Error(`Unsupported algorithm: ${this.zone.algorithm}`); + } + + // Generate the key pair this.keyPair = this.generateKeyPair(); } private generateKeyPair(): DnssecKeyPair { - const key = this.ec.genKeyPair(); - const privatePem = key.getPrivate().toString('hex'); // get private key in hex format - // @ts-ignore - const publicPem = key.getPublic().toString('hex'); // get public key in hex format + let privateKey: string; + let publicKey: string; - return { - private: privatePem, - public: publicPem - }; - } - - private formatPEM(pem: string, type: string): string { - const start = `-----BEGIN ${type}-----`; - const end = `-----END ${type}-----`; - - const formatted = [start]; - for (let i = 0; i < pem.length; i += 64) { - formatted.push(pem.slice(i, i + 64)); - } - formatted.push(end); - return formatted.join('\n'); - } - - public getDSRecord(): string { - const publicPem = this.keyPair.public; - const publicKey = this.ec.keyFromPublic(publicPem); // Create a public key from the publicPEM - - const digest = publicKey.getPublic(); // get public point - return `DS {id} 8 {algorithm} {digest} {hash-algorithm}\n` - .replace('{id}', '256') // zone hash - .replace('{algorithm}', this.getAlgorithm()) - .replace('{digest}', `0x${digest.getX()}${digest.getY()}`) - .replace('{hash-algorithm}', '2'); - } - - private getAlgorithm(): string { switch (this.zone.algorithm) { case 'ECDSA': - return '8'; + if (!this.ec) throw new Error('EC instance is not initialized.'); + const ecKeyPair = this.ec.genKeyPair(); + privateKey = ecKeyPair.getPrivate('hex'); + publicKey = ecKeyPair.getPublic(false, 'hex'); // Uncompressed format + break; case 'ED25519': - return '15'; + if (!this.eddsa) throw new Error('EdDSA instance is not initialized.'); + const secret = plugins.crypto.randomBytes(32); + const edKeyPair = this.eddsa.keyFromSecret(secret); + privateKey = edKeyPair.getSecret('hex'); + publicKey = edKeyPair.getPublic('hex'); + break; case 'RSA': - return '1'; + // RSA key generation would be implemented here + throw new Error('RSA key generation is not yet implemented.'); default: throw new Error(`Unsupported algorithm: ${this.zone.algorithm}`); } + + return { privateKey, publicKey }; + } + + private getAlgorithmNumber(): number { + switch (this.zone.algorithm) { + case 'ECDSA': + return 13; // ECDSAP256SHA256 + case 'ED25519': + return 15; + case 'RSA': + return 8; // RSASHA256 + default: + throw new Error(`Unsupported algorithm: ${this.zone.algorithm}`); + } + } + + public signData(data: Buffer): Buffer { + // Sign the data using the private key + const keyPair = this.ec!.keyFromPrivate(this.keyPair.privateKey, 'hex'); + const signature = keyPair.sign(plugins.crypto.createHash('sha256').update(data).digest()); + return Buffer.from(signature.toDER()); + } + + private generateDNSKEY(): Buffer { + const flags = 256; // 256 indicates a Zone Signing Key (ZSK) + const protocol = 3; // Must be 3 according to RFC + const algorithm = this.getAlgorithmNumber(); + + let publicKeyData: Buffer; + + switch (this.zone.algorithm) { + case 'ECDSA': + if (!this.ec) throw new Error('EC instance is not initialized.'); + const ecPublicKey = this.ec.keyFromPublic(this.keyPair.publicKey, 'hex').getPublic(); + const x = ecPublicKey.getX().toArrayLike(Buffer, 'be', 32); + const y = ecPublicKey.getY().toArrayLike(Buffer, 'be', 32); + publicKeyData = Buffer.concat([x, y]); + break; + case 'ED25519': + publicKeyData = Buffer.from(this.keyPair.publicKey, 'hex'); + break; + case 'RSA': + // RSA public key extraction would go here + throw new Error('RSA public key extraction is not yet implemented.'); + default: + throw new Error(`Unsupported algorithm: ${this.zone.algorithm}`); + } + + // Construct the DNSKEY RDATA + const dnskeyRdata = Buffer.concat([ + Buffer.from([flags >> 8, flags & 0xff]), // Flags (2 bytes) + Buffer.from([protocol]), // Protocol (1 byte) + Buffer.from([algorithm]), // Algorithm (1 byte) + publicKeyData, // Public Key + ]); + + return dnskeyRdata; + } + + private computeKeyTag(dnskeyRdata: Buffer): number { + // Key Tag calculation as per RFC 4034, Appendix B + let acc = 0; + for (let i = 0; i < dnskeyRdata.length; i++) { + acc += i & 1 ? dnskeyRdata[i] : dnskeyRdata[i] << 8; + } + acc += (acc >> 16) & 0xffff; + return acc & 0xffff; + } + + private getDNSKEYRecord(): string { + const dnskeyRdata = this.generateDNSKEY(); + const flags = 256; + const protocol = 3; + const algorithm = this.getAlgorithmNumber(); + const publicKeyData = dnskeyRdata.slice(4); // Skip flags, protocol, algorithm bytes + const publicKeyBase64 = publicKeyData.toString('base64'); + + return `${this.zone.zone}. IN DNSKEY ${flags} ${protocol} ${algorithm} ${publicKeyBase64}`; + } + + public getDSRecord(): string { + const dnskeyRdata = this.generateDNSKEY(); + const keyTag = this.computeKeyTag(dnskeyRdata); + const algorithm = this.getAlgorithmNumber(); + const digestType = 2; // SHA-256 + const digest = plugins.crypto + .createHash('sha256') + .update(dnskeyRdata) + .digest('hex') + .toUpperCase(); + + return `${this.zone.zone}. IN DS ${keyTag} ${algorithm} ${digestType} ${digest}`; } public getKeyPair(): DnssecKeyPair { return this.keyPair; } - public getDsAndKeyPair(): [DnssecKeyPair, string] { + public getDsAndKeyPair(): { keyPair: DnssecKeyPair; dsRecord: string; dnskeyRecord: string } { const dsRecord = this.getDSRecord(); - return [this.keyPair, dsRecord]; + const dnskeyRecord = this.getDNSKEYRecord(); + return { keyPair: this.keyPair, dsRecord, dnskeyRecord }; } -} \ No newline at end of file +} diff --git a/ts_server/plugins.ts b/ts_server/plugins.ts index 29499aa..af31544 100644 --- a/ts_server/plugins.ts +++ b/ts_server/plugins.ts @@ -1,10 +1,12 @@ // node native +import crypto from 'crypto'; import fs from 'fs'; import http from 'http'; import https from 'https'; import dgram from 'dgram'; export { + crypto, fs, http, https,