import { default as forge } from "node-forge"; import { loadCertificate, loadPrivateKey, saveCertificate, savePrivateKey } from "./pki.utils.mjs"; import { saveCertificate as saveCertificateToDatabase } from "./db.certificates.mjs"; import { default as ValidateSchema } from 'validate' import { default as parseDuration } from "parse-duration"; import { randomBytes } from "crypto"; import { error } from "console"; const CertificateSchema = new ValidateSchema({ certType: { type: String, required: true, enum: [ 'rootCA', 'intermediateCA', 'identity' ], message: { type: '[certType] must be a string.', required: '[certType] is required.', enum: '[certType] must be one of [authority, identity]' } }, parent: { type: String, required: false, message: { type: '[parent] must be a string.' } }, duration: { type: String, required: true, message: { type: '[duration] must be a string.', required: '[duration] is required.' } }, commonName: { type: String, required: true, match: /^[a-zA-Z0-9\ \-\_\.]+$/, length: { min: 3, max: 32 }, message: { type: '[commonName] must be a string.', required: '[commonName] is required.', match: '[commonName] is invalid.', length: '[commonName] is invalid.' } }, emailAddress: { type: String, required: true, match: /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/, message: { type: '[emailAddress] must be a string.', required: '[emailAddress] is required.', match: '[emailAddress] is invalid (e.g. admin@example.com).' } }, countryName: { type: String, required: true, match: /^[A-Z]{2}$/, message: { type: '[countryName] must be a string.', required: '[countryName] is required.', match: '[countryName] needs to be 2 letter code (e.g. US).' } }, stateOrProvinceName: { type: String, required: true, match: /^[A-Za-z ]{3,64}$/, message: { type: '[stateOrProvinceName] must be a string.', required: '[stateOrProvinceName] is required.', match: '[stateOrProvinceName] is invalid: (A-Z|a-z| ), 3-64 characters' } }, localityName: { type: String, required: true, match: /^[A-Za-z ]{3,64}$/, message: { type: '[localityName] must be a string.', required: '[localityName] is required.', match: '[localityName] is invalid: (A-Z,a-z, ), 3-64 characters' } }, organizationName: { type: String, required: true, match: /^[A-Za-z\ \-\_\.]{3,64}$/, message: { type: '[organizationName] must be a string.', required: '[organizationName] is required.', match: '[organizationName] is invalid: (A-Z,a-z, ), 3-64 characters' } }, organizationalUnitName: { type: String, required: true, match: /^[A-Za-z\ \-\_\.]{3,64}$/, message: { type: '[organizationalUnitName] must be a string.', required: '[organizationalUnitName] is required.', match: '[organizationalUnitName] is invalid: (A-Z,a-z, ,.,-,_), 3-64 characters' } } }) function generateCertificate(Params) { let configErrorList = CertificateSchema.validate(Params); configErrorList = configErrorList.map((configError) => { return configError.message; }) if (configErrorList.length > 0) { return { status: "error", errors: configErrorList } } // generate certificate id const certificateId = randomBytes(4).toString("hex"); // generate new keypair const { privateKey, publicKey } = forge.pki.rsa.generateKeyPair(4096); // initialize new certificate const certificate = forge.pki.createCertificate(); certificate.publicKey = publicKey; // cert.serialNumber = '01'; certificate.validity.notBefore = new Date(Date.now()); certificate.validity.notAfter = new Date(Date.now() + parseDuration(Params.duration)); const attrs = [{ name: 'commonName', value: Params.commonName }, { name: 'countryName', value: Params.countryName }, { name: 'stateOrProvinceName', value: Params.stateOrProvinceName }, { name: 'localityName', value: Params.localityName }, { name: 'organizationName', value: Params.organizationName }, { name: 'organizationalUnitName', value: Params.organizationalUnitName }]; certificate.setSubject(attrs); certificate.setIssuer(attrs); // Erweiterungen hinzufügen certificate.setExtensions([{ name: 'basicConstraints', cA: true }, { name: 'keyUsage', keyCertSign: true, cRLSign: true }, { name: 'subjectKeyIdentifier' }]); console.log(`new certificate with uid ${certificateId} generated`); return { status: "ok", certificateId, certificate, privateKey, publicKey } } export async function createRootCertificate(certificateParameters) { let oGeneratedData = generateCertificate({ certType: 'rootCA', parent: null, duration: certificateParameters.duration, emailAddress: certificateParameters.emailAddress, commonName: certificateParameters.commonName, countryName: certificateParameters.countryName, stateOrProvinceName: certificateParameters.stateOrProvinceName, localityName: certificateParameters.localityName, organizationName: certificateParameters.organizationName, organizationalUnitName: certificateParameters.organizationalUnitName }); if (oGeneratedData.status != "ok") { return { status: "error", errors: oGeneratedData.errors } } // sign the new certificate oGeneratedData.certificate.sign(oGeneratedData.privateKey, forge.md.sha256.create()); // save certificate and keys saveCertificate(oGeneratedData.certificate, oGeneratedData.certificateId, 'rootCA'); savePrivateKey(oGeneratedData.privateKey, oGeneratedData.certificateId, 'rootCA'); saveCertificateToDatabase(oGeneratedData.certificateId, { displayName: certificateParameters.commonName, certificateType: 'rootCA' }) console.log(`new rootCA certificate with uid ${oGeneratedData.certificateId} saved`); return { status: "ok", certificateId: oGeneratedData.certificateId } } export async function createIntermediateCertificate(oCertificateParameters, sRootCertificateId) { let { certificateId: newCertificateId, certificate: newCertificate, privateKey: newPrivateKey } = generateCertificate(oCertificateParameters); // load certificate authority certificate and privateKey const rootCACertificate = loadCertificate(sRootCertificateId); const rootCAPrivateKey = loadPrivateKey(sRootCertificateId); // set rootCA as issuer newCertificate.setIssuer(rootCACertificate.subject.attributes); // sign certificate with root certificate newCertificate.sign(rootCAPrivateKey, forge.md.sha256.create()); // save certificate and keys saveCertificate(newCertificate, newCertificateId, 'intermediateCA'); savePrivateKey(newPrivateKey, newCertificateId, 'intermediateCA'); saveCertificateToDatabase(newCertificateId, { displayName: Params.commonName, certificateType: 'intermediateCA' }) console.log(`new intermediateCA certificate with uid ${newCertificateId} saved`); } export async function createIdentityCertificate(Params) { let { certificateId, certificate, privateKey, publicKey } = generateCertificate(Params) } // Funktion aufrufen, um das Root-CA-Zertifikat zu generieren console.log((await createRootCertificate({ duration: '10y', emailAddress: 'kai@waggeling.net', commonName: 'waggeling.net rootCA', countryName: 'DE', stateOrProvinceName: 'Sachsen', localityName: 'Leipzig', organizationName: 'WCloud', organizationalUnitName: 'TEst' })));