298 lines
No EOL
8.4 KiB
JavaScript
298 lines
No EOL
8.4 KiB
JavaScript
|
|
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'
|
|
}))); |