101 lines
4.0 KiB
JavaScript
101 lines
4.0 KiB
JavaScript
"use strict";
|
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
};
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.createCert = exports.createCA = void 0;
|
|
const node_forge_1 = require("node-forge");
|
|
const node_net_1 = __importDefault(require("node:net"));
|
|
const node_util_1 = require("node:util");
|
|
async function generateCert(options) {
|
|
const { subject, issuer, extensions, validity } = options;
|
|
const generateKeyPair = (0, node_util_1.promisify)(node_forge_1.pki.rsa.generateKeyPair.bind(node_forge_1.pki.rsa));
|
|
// create random serial number between between 50000 and 99999
|
|
const serial = Math.floor(Math.random() * 95000 + 50000).toString();
|
|
const keyPair = await generateKeyPair({ bits: 2048, workers: 4 });
|
|
const cert = node_forge_1.pki.createCertificate();
|
|
// serial number must be hex encoded
|
|
cert.serialNumber = Buffer.from(serial).toString("hex");
|
|
cert.publicKey = keyPair.publicKey;
|
|
cert.setSubject(subject);
|
|
cert.setIssuer(issuer);
|
|
cert.setExtensions(extensions);
|
|
cert.validity.notBefore = new Date();
|
|
cert.validity.notAfter = new Date();
|
|
cert.validity.notAfter.setDate(cert.validity.notAfter.getDate() + validity);
|
|
// sign the certificate with it's own
|
|
// private key if no separate signing key is provided
|
|
const signWith = options.signWith ? node_forge_1.pki.privateKeyFromPem(options.signWith) : keyPair.privateKey;
|
|
cert.sign(signWith, node_forge_1.md.sha256.create());
|
|
return {
|
|
key: node_forge_1.pki.privateKeyToPem(keyPair.privateKey),
|
|
cert: node_forge_1.pki.certificateToPem(cert)
|
|
};
|
|
}
|
|
async function createCA(options) {
|
|
// certificate Attributes: https://git.io/fptna
|
|
const attributes = [
|
|
{ name: "commonName", value: options.organization },
|
|
{ name: "countryName", value: options.countryCode },
|
|
{ name: "stateOrProvinceName", value: options.state },
|
|
{ name: "localityName", value: options.locality },
|
|
{ name: "organizationName", value: options.organization }
|
|
];
|
|
// required certificate extensions for a certificate authority
|
|
const extensions = [
|
|
{ name: "basicConstraints", cA: true, critical: true },
|
|
{ name: "keyUsage", keyCertSign: true, critical: true }
|
|
];
|
|
return await generateCert({
|
|
subject: attributes,
|
|
issuer: attributes,
|
|
extensions: extensions,
|
|
validity: options.validity
|
|
});
|
|
}
|
|
exports.createCA = createCA;
|
|
async function createCert(options) {
|
|
// certificate Attributes: https://git.io/fptna
|
|
const attributes = [
|
|
{ name: "commonName", value: options.domains[0] } // use the first address as common name
|
|
];
|
|
if (options.organization) {
|
|
attributes.push({ name: "organizationName", value: options.organization });
|
|
}
|
|
if (options.email) {
|
|
attributes.push({ name: "emailAddress", value: options.email });
|
|
}
|
|
// required certificate extensions for a tls certificate
|
|
const extensions = [
|
|
{ name: "basicConstraints", cA: false, critical: true },
|
|
{
|
|
name: "keyUsage",
|
|
digitalSignature: true,
|
|
keyEncipherment: true,
|
|
critical: true
|
|
},
|
|
{ name: "extKeyUsage", serverAuth: true, clientAuth: true },
|
|
{
|
|
name: "subjectAltName",
|
|
altNames: options.domains.map((domain) => {
|
|
// types https://git.io/fptng
|
|
const TYPE_DOMAIN = 2;
|
|
const TYPE_IP = 7;
|
|
if (node_net_1.default.isIP(domain)) {
|
|
return { type: TYPE_IP, ip: domain };
|
|
}
|
|
return { type: TYPE_DOMAIN, value: domain };
|
|
})
|
|
}
|
|
];
|
|
const ca = node_forge_1.pki.certificateFromPem(options.ca.cert);
|
|
return await generateCert({
|
|
subject: attributes,
|
|
issuer: ca.subject.attributes,
|
|
extensions: extensions,
|
|
validity: options.validity,
|
|
signWith: options.ca.key
|
|
});
|
|
}
|
|
exports.createCert = createCert;
|