initial upload

This commit is contained in:
Kai Waggeling 2025-05-17 16:10:17 +02:00
parent b2a512f7fe
commit 48ae5e89aa
30 changed files with 1293 additions and 0 deletions

View file

@ -0,0 +1,298 @@
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'
})));