fix(ts_server): Update DnsSec class to fully implement key generation and DNSKEY record creation.

This commit is contained in:
Philipp Kunz 2024-09-19 18:23:42 +02:00
parent 1536475306
commit 439d08b023
3 changed files with 145 additions and 47 deletions

View File

@ -1,5 +1,12 @@
# Changelog # 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) ## 2024-09-18 - 6.1.0 - feat(smartdns)
Add DNS Server and DNSSEC tools with comprehensive unit tests Add DNS Server and DNSSEC tools with comprehensive unit tests

View File

@ -1,83 +1,172 @@
// Import necessary plugins from plugins.ts
import * as plugins from './plugins.js'; import * as plugins from './plugins.js';
interface DnssecZone { interface DnssecZone {
zone: string; zone: string;
algorithm: string; algorithm: 'ECDSA' | 'ED25519' | 'RSA';
keySize: number; keySize: number;
days: number; days: number;
} }
interface DnssecKeyPair { interface DnssecKeyPair {
private: string; privateKey: string;
public: string; publicKey: string;
} }
class DnsSec { export class DnsSec {
private zone: DnssecZone; private zone: DnssecZone;
private keyPair: DnssecKeyPair; 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) { constructor(zone: DnssecZone) {
this.zone = zone; 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(); this.keyPair = this.generateKeyPair();
} }
private generateKeyPair(): DnssecKeyPair { private generateKeyPair(): DnssecKeyPair {
const key = this.ec.genKeyPair(); let privateKey: string;
const privatePem = key.getPrivate().toString('hex'); // get private key in hex format let publicKey: string;
// @ts-ignore
const publicPem = key.getPublic().toString('hex'); // get public key in hex format
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) { switch (this.zone.algorithm) {
case 'ECDSA': 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': 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': case 'RSA':
return '1'; // RSA key generation would be implemented here
throw new Error('RSA key generation is not yet implemented.');
default: default:
throw new Error(`Unsupported algorithm: ${this.zone.algorithm}`); 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 { public getKeyPair(): DnssecKeyPair {
return this.keyPair; return this.keyPair;
} }
public getDsAndKeyPair(): [DnssecKeyPair, string] { public getDsAndKeyPair(): { keyPair: DnssecKeyPair; dsRecord: string; dnskeyRecord: string } {
const dsRecord = this.getDSRecord(); const dsRecord = this.getDSRecord();
return [this.keyPair, dsRecord]; const dnskeyRecord = this.getDNSKEYRecord();
return { keyPair: this.keyPair, dsRecord, dnskeyRecord };
} }
} }

View File

@ -1,10 +1,12 @@
// node native // node native
import crypto from 'crypto';
import fs from 'fs'; import fs from 'fs';
import http from 'http'; import http from 'http';
import https from 'https'; import https from 'https';
import dgram from 'dgram'; import dgram from 'dgram';
export { export {
crypto,
fs, fs,
http, http,
https, https,