node-opcua-pki
Version:
PKI management for node-opcua
1,133 lines (1,102 loc) • 160 kB
JavaScript
"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// packages/node-opcua-pki/lib/index.ts
var lib_exports = {};
__export(lib_exports, {
CertificateAuthority: () => CertificateAuthority,
CertificateManager: () => CertificateManager,
CertificateManagerState: () => CertificateManagerState,
ChainCompletionStatus: () => ChainCompletionStatus,
Subject: () => import_node_opcua_crypto.Subject,
VerificationStatus: () => VerificationStatus,
adjustApplicationUri: () => adjustApplicationUri,
adjustDate: () => adjustDate,
coerceCertificateChain: () => coerceCertificateChain,
convertPFXtoPEM: () => convertPFXtoPEM,
createPFX: () => createPFX,
dumpPFX: () => dumpPFX,
extractAllFromPFX: () => extractAllFromPFX,
extractCACertificatesFromPFX: () => extractCACertificatesFromPFX,
extractCertificateFromPFX: () => extractCertificateFromPFX,
extractPrivateKeyFromPFX: () => extractPrivateKeyFromPFX,
findIssuerCertificateInChain: () => findIssuerCertificateInChain,
install_prerequisite: () => install_prerequisite,
isIntermediateIssuer: () => isIntermediateIssuer,
isIssuer: () => isIssuer,
isRootIssuer: () => isRootIssuer,
makeFingerprint: () => makeFingerprint,
quote: () => quote
});
module.exports = __toCommonJS(lib_exports);
// packages/node-opcua-pki/lib/ca/certificate_authority.ts
var import_node_assert7 = __toESM(require("assert"));
var import_node_fs7 = __toESM(require("fs"));
var import_node_os3 = __toESM(require("os"));
var import_node_path5 = __toESM(require("path"));
var import_chalk5 = __toESM(require("chalk"));
var import_node_opcua_crypto2 = require("node-opcua-crypto");
// packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
var import_node_assert4 = __toESM(require("assert"));
var import_node_fs4 = __toESM(require("fs"));
// packages/node-opcua-pki/lib/toolbox/common.ts
var import_node_assert = __toESM(require("assert"));
function quote(str) {
return `"${str || ""}"`;
}
function adjustDate(params) {
(0, import_node_assert.default)(params instanceof Object);
params.startDate = params.startDate || /* @__PURE__ */ new Date();
(0, import_node_assert.default)(params.startDate instanceof Date);
if (params.validityMs !== void 0) {
if (params.validityMs <= 0) {
throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
}
params.endDate = new Date(params.startDate.getTime() + params.validityMs);
params.validity = Math.ceil(params.validityMs / 864e5);
} else {
params.validity = params.validity || 365;
params.endDate = new Date(params.startDate.getTime());
params.endDate.setDate(params.startDate.getDate() + params.validity);
}
(0, import_node_assert.default)(params.endDate instanceof Date);
(0, import_node_assert.default)(params.startDate instanceof Date);
}
function adjustApplicationUri(params) {
const applicationUri = params.applicationUri || "";
if (applicationUri.length > 200) {
throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);
}
}
// packages/node-opcua-pki/lib/toolbox/common2.ts
var import_node_assert2 = __toESM(require("assert"));
var import_node_fs = __toESM(require("fs"));
var import_node_path = __toESM(require("path"));
var import_chalk = __toESM(require("chalk"));
// packages/node-opcua-pki/lib/toolbox/config.ts
var g_config = {
opensslVersion: "unset",
silent: process.env.VERBOSE ? !process.env.VERBOSE : true,
force: false
};
// packages/node-opcua-pki/lib/toolbox/debug.ts
var doDebug = process.env.NODEOPCUAPKIDEBUG || false;
var displayError = true;
var displayDebug = !!process.env.NODEOPCUAPKIDEBUG || false;
function debugLog(...args) {
if (displayDebug) {
console.log.apply(null, args);
}
}
function warningLog(...args) {
console.log.apply(null, args);
}
// packages/node-opcua-pki/lib/toolbox/common2.ts
function certificateFileExist(certificateFile) {
if (import_node_fs.default.existsSync(certificateFile) && !g_config.force) {
warningLog(
import_chalk.default.yellow(" certificate ") + import_chalk.default.cyan(certificateFile) + import_chalk.default.yellow(" already exists => do not overwrite")
);
return false;
}
return true;
}
function mkdirRecursiveSync(folder) {
if (!import_node_fs.default.existsSync(folder)) {
debugLog(import_chalk.default.white(" .. constructing "), folder);
import_node_fs.default.mkdirSync(folder, { recursive: true });
}
}
function makePath(folderName, filename) {
let s;
if (filename) {
s = import_node_path.default.join(import_node_path.default.normalize(folderName), filename);
} else {
(0, import_node_assert2.default)(folderName);
s = folderName;
}
s = s.replace(/\\/g, "/");
return s;
}
// packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
var import_node_assert3 = __toESM(require("assert"));
var import_node_child_process2 = __toESM(require("child_process"));
var import_node_fs3 = __toESM(require("fs"));
var import_node_os2 = __toESM(require("os"));
var import_byline2 = __toESM(require("byline"));
var import_chalk3 = __toESM(require("chalk"));
// packages/node-opcua-pki/lib/toolbox/with_openssl/_env.ts
var exportedEnvVars = {};
function setEnv(varName, value) {
if (!g_config.silent) {
warningLog(` set ${varName}=${value}`);
}
exportedEnvVars[varName] = value;
if (["OPENSSL_CONF"].indexOf(varName) >= 0) {
process.env[varName] = value;
}
if (["RANDFILE"].indexOf(varName) >= 0) {
process.env[varName] = value;
}
}
function hasEnv(varName) {
return Object.prototype.hasOwnProperty.call(exportedEnvVars, varName);
}
function getEnv(varName) {
return exportedEnvVars[varName];
}
function unsetEnv(varName) {
delete exportedEnvVars[varName];
}
function getEnvironmentVarNames() {
return Object.keys(exportedEnvVars).map((varName) => {
return { key: varName, pattern: `\\$ENV\\:\\:${varName}` };
});
}
function processAltNames(params) {
params.dns = params.dns || [];
params.ip = params.ip || [];
let subjectAltName = [];
subjectAltName.push(`URI:${params.applicationUri}`);
subjectAltName = [].concat(
subjectAltName,
params.dns.map((d) => `DNS:${d}`)
);
subjectAltName = [].concat(
subjectAltName,
params.ip.map((d) => `IP:${d}`)
);
const subjectAltNameString = subjectAltName.join(", ");
setEnv("ALTNAME", subjectAltNameString);
}
// packages/node-opcua-pki/lib/toolbox/with_openssl/install_prerequisite.ts
var import_node_child_process = __toESM(require("child_process"));
var import_node_fs2 = __toESM(require("fs"));
var import_node_os = __toESM(require("os"));
var import_node_path2 = __toESM(require("path"));
var import_node_url = __toESM(require("url"));
var import_byline = __toESM(require("byline"));
var import_chalk2 = __toESM(require("chalk"));
var import_progress = __toESM(require("progress"));
var import_wget_improved_2 = __toESM(require("wget-improved-2"));
var import_yauzl = __toESM(require("yauzl"));
var doDebug2 = process.env.NODEOPCUAPKIDEBUG || false;
function makeOptions() {
const proxy = process.env.HTTPS_PROXY || process.env.https_proxy || process.env.HTTP_PROXY || process.env.http_proxy || void 0;
if (proxy) {
const a = new import_node_url.default.URL(proxy);
const auth = a.username ? `${a.username}:${a.password}` : void 0;
const options = {
proxy: {
port: a.port ? parseInt(a.port, 10) : 80,
protocol: a.protocol.replace(":", ""),
host: a.hostname ?? "",
proxyAuth: auth
}
};
warningLog(import_chalk2.default.green("- using proxy "), proxy);
warningLog(options);
return options;
}
return {};
}
async function execute(cmd, cwd) {
let output = "";
const options = {
cwd,
windowsHide: true
};
return await new Promise((resolve, reject) => {
const child = import_node_child_process.default.exec(
cmd,
options,
(err) => {
const exitCode = err === null ? 0 : err.code || 1;
if (err) reject(err);
else {
resolve({ exitCode, output });
}
}
);
const stream1 = (0, import_byline.default)(child.stdout);
stream1.on("data", (line) => {
output += `${line}
`;
if (doDebug2) {
process.stdout.write(` stdout ${import_chalk2.default.yellow(line)}
`);
}
});
});
}
function quote2(str) {
return `"${str.replace(/\\/g, "/")}"`;
}
function is_expected_openssl_version(strVersion) {
return !!strVersion.match(/OpenSSL 1|3/);
}
async function getopensslExecPath() {
let result1;
try {
result1 = await execute("which openssl");
} catch (err) {
warningLog("warning: ", err.message);
throw new Error("Cannot find openssl");
}
const exitCode = result1?.exitCode;
const output = result1?.output;
if (exitCode !== 0) {
warningLog(import_chalk2.default.yellow(" it seems that ") + import_chalk2.default.cyan("openssl") + import_chalk2.default.yellow(" is not installed on your computer "));
warningLog(import_chalk2.default.yellow("Please install it before running this programs"));
throw new Error("Cannot find openssl");
}
const opensslExecPath = output.replace(/\n\r/g, "").trim();
return opensslExecPath;
}
async function check_system_openssl_version() {
const opensslExecPath = await getopensslExecPath();
const q_opensslExecPath = quote2(opensslExecPath);
if (doDebug2) {
warningLog(` OpenSSL found in : ${import_chalk2.default.yellow(opensslExecPath)}`);
}
const result = await execute(`${q_opensslExecPath} version`);
const exitCode = result?.exitCode;
const output = result?.output;
const version = output.trim();
const versionOK = exitCode === 0 && is_expected_openssl_version(version);
if (!versionOK) {
let message = import_chalk2.default.whiteBright("Warning !!!!!!!!!!!! ") + "\nyour version of openssl is " + version + ". It doesn't match the expected version";
if (process.platform === "darwin") {
message += import_chalk2.default.cyan("\nplease refer to :") + import_chalk2.default.yellow(" https://github.com/node-opcua/node-opcua/wiki/installing-node-opcua-or-node-red-on-MacOS");
}
console.log(message);
}
return output;
}
async function install_and_check_win32_openssl_version() {
const downloadFolder = import_node_path2.default.join(import_node_os.default.tmpdir(), ".");
function get_openssl_folder_win32() {
if (process.env.LOCALAPPDATA) {
const userProgramFolder = import_node_path2.default.join(process.env.LOCALAPPDATA, "Programs");
if (import_node_fs2.default.existsSync(userProgramFolder)) {
return import_node_path2.default.join(userProgramFolder, "openssl");
}
}
return import_node_path2.default.join(process.cwd(), "openssl");
}
function get_openssl_exec_path_win32() {
const opensslFolder2 = get_openssl_folder_win32();
return import_node_path2.default.join(opensslFolder2, "openssl.exe");
}
async function check_openssl_win32() {
const opensslExecPath2 = get_openssl_exec_path_win32();
const exists = import_node_fs2.default.existsSync(opensslExecPath2);
if (!exists) {
warningLog("checking presence of ", opensslExecPath2);
warningLog(import_chalk2.default.red(" cannot find file ") + opensslExecPath2);
return {
opensslOk: false,
version: `cannot find file ${opensslExecPath2}`
};
} else {
const q_openssl_exe_path = quote2(opensslExecPath2);
const cwd = ".";
const { exitCode, output } = await execute(`${q_openssl_exe_path} version`, cwd);
const version = output.trim();
if (doDebug2) {
warningLog(" Version = ", version);
}
return {
opensslOk: exitCode === 0 && is_expected_openssl_version(version),
version
};
}
}
function win32or64() {
if (process.env.PROCESSOR_ARCHITECTURE === "x86" && process.env.PROCESSOR_ARCHITEW6432) {
return 64;
}
if (process.env.PROCESSOR_ARCHITECTURE === "AMD64") {
return 64;
}
if (process.env.CURRENT_CPU === "x64") {
return 64;
}
return 32;
}
async function download_openssl() {
const url2 = win32or64() === 64 ? "https://github.com/node-opcua/node-opcua-pki/releases/download/2.14.2/openssl-1.0.2u-x64_86-win64.zip" : "https://github.com/node-opcua/node-opcua-pki/releases/download/2.14.2/openssl-1.0.2u-i386-win32.zip";
const outputFilename = import_node_path2.default.join(downloadFolder, import_node_path2.default.basename(url2));
warningLog(`downloading ${import_chalk2.default.yellow(url2)} to ${outputFilename}`);
if (import_node_fs2.default.existsSync(outputFilename)) {
return { downloadedFile: outputFilename };
}
const options = makeOptions();
const bar = new import_progress.default(import_chalk2.default.cyan("[:bar]") + import_chalk2.default.cyan(" :percent ") + import_chalk2.default.white(":etas"), {
complete: "=",
incomplete: " ",
total: 100,
width: 100
});
return await new Promise((resolve, reject) => {
const download = import_wget_improved_2.default.download(url2, outputFilename, options);
download.on("error", (err) => {
warningLog(err);
setImmediate(() => {
reject(err);
});
});
download.on("end", (output) => {
if (doDebug2) {
warningLog(output);
}
resolve({ downloadedFile: outputFilename });
});
download.on("progress", (progress) => {
bar.update(progress);
});
});
}
async function unzip_openssl(zipFilename) {
const opensslFolder2 = get_openssl_folder_win32();
const zipFile = await new Promise((resolve, reject) => {
import_yauzl.default.open(zipFilename, { lazyEntries: true }, (err, zipfile) => {
if (err) {
reject(err);
} else {
if (!zipfile) {
reject(new Error("zipfile is null"));
} else {
resolve(zipfile);
}
}
});
});
zipFile.readEntry();
await new Promise((resolve, reject) => {
zipFile.on("end", (err) => {
setImmediate(() => {
if (doDebug2) {
warningLog("unzip done");
}
if (err) {
reject(err);
} else {
resolve();
}
});
});
zipFile.on("entry", (entry) => {
zipFile.openReadStream(entry, (err, readStream) => {
if (err) {
return reject(err);
}
const file = import_node_path2.default.join(opensslFolder2, entry.fileName);
if (doDebug2) {
warningLog(" unzipping :", file);
}
const writeStream = import_node_fs2.default.createWriteStream(file, "binary");
readStream?.pipe(writeStream);
writeStream.on("close", () => {
zipFile.readEntry();
});
});
});
});
}
const opensslFolder = get_openssl_folder_win32();
const opensslExecPath = get_openssl_exec_path_win32();
if (!import_node_fs2.default.existsSync(opensslFolder)) {
if (doDebug2) {
warningLog("creating openssl_folder", opensslFolder);
}
import_node_fs2.default.mkdirSync(opensslFolder);
}
const { opensslOk, version: _version } = await check_openssl_win32();
if (!opensslOk) {
warningLog(import_chalk2.default.yellow("openssl seems to be missing and need to be installed"));
const { downloadedFile } = await download_openssl();
if (doDebug2) {
warningLog("deflating ", import_chalk2.default.yellow(downloadedFile));
}
await unzip_openssl(downloadedFile);
const opensslExists = !!import_node_fs2.default.existsSync(opensslExecPath);
if (doDebug2) {
warningLog("verifying ", opensslExists, opensslExists ? import_chalk2.default.green("OK ") : import_chalk2.default.red(" Error"), opensslExecPath);
}
const _opensslExecPath2 = await check_openssl_win32();
return opensslExecPath;
} else {
if (doDebug2) {
warningLog(import_chalk2.default.green("openssl is already installed and have the expected version."));
}
return opensslExecPath;
}
}
async function install_prerequisite() {
if (process.platform !== "win32") {
return await check_system_openssl_version();
} else {
return await install_and_check_win32_openssl_version();
}
}
async function get_openssl_exec_path() {
if (process.platform === "win32") {
const opensslExecPath = await install_prerequisite();
if (!import_node_fs2.default.existsSync(opensslExecPath)) {
throw new Error(`internal error cannot find ${opensslExecPath}`);
}
return opensslExecPath;
} else {
return "openssl";
}
}
// packages/node-opcua-pki/lib/toolbox/with_openssl/execute_openssl.ts
var opensslPath;
var n = makePath;
async function execute2(cmd, options) {
const from = new Error();
options.cwd = options.cwd || process.cwd();
if (!g_config.silent) {
warningLog(import_chalk3.default.cyan(" CWD "), options.cwd);
}
const outputs = [];
return await new Promise((resolve, reject) => {
const child = import_node_child_process2.default.exec(
cmd,
{
cwd: options.cwd,
windowsHide: true
},
(err) => {
if (err) {
if (!options.hideErrorMessage) {
const fence = "###########################################";
console.error(import_chalk3.default.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
console.error(import_chalk3.default.bgWhiteBright.redBright(`CWD = ${options.cwd}`));
console.error(import_chalk3.default.bgWhiteBright.redBright(err.message));
console.error(import_chalk3.default.bgWhiteBright.redBright(`${fence} OPENSSL ERROR ${fence}`));
console.error(from.stack);
}
reject(new Error(err.message));
return;
}
resolve(outputs.join(""));
}
);
if (child.stdout) {
const stream2 = (0, import_byline2.default)(child.stdout);
stream2.on("data", (line) => {
outputs.push(`${line}
`);
});
if (!g_config.silent) {
stream2.on("data", (line) => {
line = line.toString();
if (doDebug) {
process.stdout.write(`${import_chalk3.default.white(" stdout ") + import_chalk3.default.whiteBright(line)}
`);
}
});
}
}
if (!g_config.silent) {
if (child.stderr) {
const stream1 = (0, import_byline2.default)(child.stderr);
stream1.on("data", (line) => {
line = line.toString();
if (displayError) {
process.stdout.write(`${import_chalk3.default.white(" stderr ") + import_chalk3.default.red(line)}
`);
}
});
}
}
});
}
async function find_openssl() {
return await get_openssl_exec_path();
}
async function ensure_openssl_installed() {
if (!opensslPath) {
opensslPath = await find_openssl();
const outputs = await execute_openssl("version", { cwd: "." });
g_config.opensslVersion = outputs.trim();
if (doDebug) {
warningLog("OpenSSL version : ", g_config.opensslVersion);
}
}
}
async function execute_openssl_no_failure(cmd, options) {
options = options || {};
options.hideErrorMessage = true;
try {
return await execute_openssl(cmd, options);
} catch (err) {
debugLog(" (ignored error = ERROR : )", err.message);
}
}
function getTempFolder() {
return import_node_os2.default.tmpdir();
}
async function execute_openssl(cmd, options) {
debugLog("execute_openssl", cmd, options);
const empty_config_file = n(getTempFolder(), "empty_config.cnf");
if (!import_node_fs3.default.existsSync(empty_config_file)) {
await import_node_fs3.default.promises.writeFile(empty_config_file, "# empty config file");
}
options = options || {};
options.openssl_conf = options.openssl_conf || empty_config_file;
(0, import_node_assert3.default)(options.openssl_conf);
setEnv("OPENSSL_CONF", options.openssl_conf);
if (!g_config.silent) {
warningLog(import_chalk3.default.cyan(" OPENSSL_CONF"), process.env.OPENSSL_CONF);
warningLog(import_chalk3.default.cyan(" RANDFILE "), process.env.RANDFILE);
warningLog(import_chalk3.default.cyan(" CMD openssl "), import_chalk3.default.cyanBright(cmd));
}
await ensure_openssl_installed();
return await execute2(`${quote(opensslPath)} ${cmd}`, options);
}
// packages/node-opcua-pki/lib/pki/toolbox_pfx.ts
var q = quote;
var n2 = makePath;
async function createPFX(options) {
const { certificateFile, privateKeyFile, outputFile, passphrase = "", caCertificateFiles } = options;
(0, import_node_assert4.default)(import_node_fs4.default.existsSync(certificateFile), `Certificate file does not exist: ${certificateFile}`);
(0, import_node_assert4.default)(import_node_fs4.default.existsSync(privateKeyFile), `Private key file does not exist: ${privateKeyFile}`);
let cmd = `pkcs12 -export`;
cmd += ` -in ${q(n2(certificateFile))}`;
cmd += ` -inkey ${q(n2(privateKeyFile))}`;
if (caCertificateFiles) {
for (const caFile of caCertificateFiles) {
(0, import_node_assert4.default)(import_node_fs4.default.existsSync(caFile), `CA certificate file does not exist: ${caFile}`);
cmd += ` -certfile ${q(n2(caFile))}`;
}
}
cmd += ` -out ${q(n2(outputFile))}`;
cmd += ` -passout pass:${passphrase}`;
await execute_openssl(cmd, {});
}
async function extractCertificateFromPFX(options) {
const { pfxFile, passphrase = "" } = options;
(0, import_node_assert4.default)(import_node_fs4.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
const cmd = `pkcs12 -in ${q(n2(pfxFile))} -clcerts -nokeys -nodes -passin pass:${passphrase}`;
return await execute_openssl(cmd, {});
}
async function extractPrivateKeyFromPFX(options) {
const { pfxFile, passphrase = "" } = options;
(0, import_node_assert4.default)(import_node_fs4.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
const cmd = `pkcs12 -in ${q(n2(pfxFile))} -nocerts -nodes -passin pass:${passphrase}`;
return await execute_openssl(cmd, {});
}
async function extractCACertificatesFromPFX(options) {
const { pfxFile, passphrase = "" } = options;
(0, import_node_assert4.default)(import_node_fs4.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
const cmd = `pkcs12 -in ${q(n2(pfxFile))} -cacerts -nokeys -nodes -passin pass:${passphrase}`;
return await execute_openssl(cmd, {});
}
async function extractAllFromPFX(options) {
const [certificate, privateKey, caCertificates] = await Promise.all([
extractCertificateFromPFX(options),
extractPrivateKeyFromPFX(options),
extractCACertificatesFromPFX(options)
]);
return { certificate, privateKey, caCertificates };
}
async function convertPFXtoPEM(pfxFile, pemFile, passphrase = "") {
(0, import_node_assert4.default)(import_node_fs4.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
const cmd = `pkcs12 -in ${q(n2(pfxFile))} -out ${q(n2(pemFile))} -nodes -passin pass:${passphrase}`;
await execute_openssl(cmd, {});
}
async function dumpPFX(pfxFile, passphrase = "") {
(0, import_node_assert4.default)(import_node_fs4.default.existsSync(pfxFile), `PFX file does not exist: ${pfxFile}`);
const cmd = `pkcs12 -in ${q(n2(pfxFile))} -info -nodes -passin pass:${passphrase}`;
return await execute_openssl(cmd, {});
}
// packages/node-opcua-pki/lib/toolbox/display.ts
var import_chalk4 = __toESM(require("chalk"));
function displayTitle(str) {
if (!g_config.silent) {
warningLog("");
warningLog(import_chalk4.default.yellowBright(str));
warningLog(import_chalk4.default.yellow(new Array(str.length + 1).join("=")), "\n");
}
}
function displaySubtitle(str) {
if (!g_config.silent) {
warningLog("");
warningLog(` ${import_chalk4.default.yellowBright(str)}`);
warningLog(` ${import_chalk4.default.white(new Array(str.length + 1).join("-"))}`, "\n");
}
}
function display(str) {
if (!g_config.silent) {
warningLog(` ${str}`);
}
}
// packages/node-opcua-pki/lib/toolbox/with_openssl/index.ts
var import_node_constants = __toESM(require("constants"));
// packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
var import_node_assert6 = __toESM(require("assert"));
var import_node_fs6 = __toESM(require("fs"));
var import_node_path4 = __toESM(require("path"));
// packages/node-opcua-pki/lib/misc/subject.ts
var import_node_opcua_crypto = require("node-opcua-crypto");
// packages/node-opcua-pki/lib/toolbox/with_openssl/toolbox.ts
var import_node_assert5 = __toESM(require("assert"));
var import_node_fs5 = __toESM(require("fs"));
var import_node_path3 = __toESM(require("path"));
function openssl_require2DigitYearInDate() {
if (!g_config.opensslVersion) {
throw new Error(
"openssl_require2DigitYearInDate : openssl version is not known: please call ensure_openssl_installed()"
);
}
return g_config.opensslVersion.match(/OpenSSL 0\.9/);
}
g_config.opensslVersion = "";
var _counter = 0;
function stripConditionalBlocks(template) {
return template.replace(/\{\{#([A-Z_][A-Z0-9_]*)\}\}([\s\S]*?)\{\{\/\1\}\}\r?\n?/g, (_match, key, content) => {
const keep = hasEnv(key) && getEnv(key) !== "";
return keep ? content : "";
});
}
function generateStaticConfig(configPath, options) {
const prePath = options?.cwd || "";
const originalFilename = !import_node_path3.default.isAbsolute(configPath) ? import_node_path3.default.join(prePath, configPath) : configPath;
let staticConfig = import_node_fs5.default.readFileSync(originalFilename, { encoding: "utf8" });
staticConfig = stripConditionalBlocks(staticConfig);
for (const envVar of getEnvironmentVarNames()) {
staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), getEnv(envVar.key));
}
const staticConfigPath = `${configPath}.${process.pid}-${_counter++}.tmp`;
const temporaryConfigPath = !import_node_path3.default.isAbsolute(configPath) ? import_node_path3.default.join(prePath, staticConfigPath) : staticConfigPath;
import_node_fs5.default.writeFileSync(temporaryConfigPath, staticConfig);
if (options?.cwd) {
return import_node_path3.default.relative(options.cwd, temporaryConfigPath);
} else {
return temporaryConfigPath;
}
}
function x509Date(date) {
date = date || /* @__PURE__ */ new Date();
const Y = date.getUTCFullYear();
const M = date.getUTCMonth() + 1;
const D = date.getUTCDate();
const h = date.getUTCHours();
const m = date.getUTCMinutes();
const s = date.getUTCSeconds();
function w(s2, l) {
return `${s2}`.padStart(l, "0");
}
if (openssl_require2DigitYearInDate()) {
return `${w(Y, 2) + w(M, 2) + w(D, 2) + w(h, 2) + w(m, 2) + w(s, 2)}Z`;
} else {
return `${w(Y, 4) + w(M, 2) + w(D, 2) + w(h, 2) + w(m, 2) + w(s, 2)}Z`;
}
}
// packages/node-opcua-pki/lib/toolbox/with_openssl/create_certificate_signing_request.ts
var q2 = quote;
var n3 = makePath;
async function createCertificateSigningRequestWithOpenSSL(certificateSigningRequestFilename, params) {
(0, import_node_assert6.default)(params);
(0, import_node_assert6.default)(params.rootDir);
(0, import_node_assert6.default)(params.configFile);
(0, import_node_assert6.default)(params.privateKey);
(0, import_node_assert6.default)(typeof params.privateKey === "string");
(0, import_node_assert6.default)(import_node_fs6.default.existsSync(params.configFile), `config file must exist ${params.configFile}`);
(0, import_node_assert6.default)(import_node_fs6.default.existsSync(params.privateKey), `Private key must exist${params.privateKey}`);
(0, import_node_assert6.default)(import_node_fs6.default.existsSync(params.rootDir), "RootDir key must exist");
(0, import_node_assert6.default)(typeof certificateSigningRequestFilename === "string");
processAltNames(params);
const configFile = generateStaticConfig(params.configFile, { cwd: params.rootDir });
const options = { cwd: params.rootDir, openssl_conf: import_node_path4.default.relative(params.rootDir, configFile) };
const configOption = ` -config ${q2(n3(configFile))}`;
const subject = params.subject ? new import_node_opcua_crypto.Subject(params.subject).toString() : void 0;
const subjectOptions = subject ? ` -subj "${subject}"` : "";
displaySubtitle("- Creating a Certificate Signing Request with openssl");
await execute_openssl(
"req -new -sha256 -batch -text " + configOption + " -key " + q2(n3(params.privateKey)) + subjectOptions + " -out " + q2(n3(certificateSigningRequestFilename)),
options
);
}
// packages/node-opcua-pki/lib/pki/templates/simple_config_template.cnf.ts
var config = '##################################################################################################\n## SIMPLE OPENSSL CONFIG FILE FOR SELF-SIGNED CERTIFICATE GENERATION\n################################################################################################################\n\ndistinguished_name = req_distinguished_name\ndefault_md = sha1\n\ndefault_md = sha256 # The default digest algorithm\n\n[ v3_ca ]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid:always,issuer:always\n\n# authorityKeyIdentifier = keyid\nbasicConstraints = CA:TRUE\nkeyUsage = critical, cRLSign, keyCertSign\nnsComment = "Self-signed Certificate for CA generated by Node-OPCUA Certificate utility"\n#nsCertType = sslCA, emailCA\n#subjectAltName = email:copy\n#issuerAltName = issuer:copy\n#obj = DER:02:03\n# crlDistributionPoints = @crl_info\n# [ crl_info ]\n# URI.0 = http://localhost:8900/crl.pem\nsubjectAltName = $ENV::ALTNAME\n\n[ req ]\ndays = 390\nreq_extensions = v3_req\nx509_extensions = v3_ca\n\n[v3_req]\nbasicConstraints = CA:false\nkeyUsage = critical, nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nsubjectAltName = $ENV::ALTNAME\n\n[ v3_ca_signed]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "certificate generated by Node-OPCUA Certificate utility and signed by a CA"\nsubjectAltName = $ENV::ALTNAME\n[ v3_selfsigned]\nsubjectKeyIdentifier = hash\nauthorityKeyIdentifier = keyid,issuer\nbasicConstraints = critical, CA:FALSE\nkeyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment\nextendedKeyUsage = clientAuth,serverAuth \nnsComment = "Self-signed certificate generated by Node-OPCUA Certificate utility"\nsubjectAltName = $ENV::ALTNAME\n[ req_distinguished_name ]\ncountryName = Country Name (2 letter code)\ncountryName_default = FR\ncountryName_min = 2\ncountryName_max = 2\n# stateOrProvinceName = State or Province Name (full name)\n# stateOrProvinceName_default = Ile de France\n# localityName = Locality Name (city, district)\n# localityName_default = Paris\norganizationName = Organization Name (company)\norganizationName_default = NodeOPCUA\n# organizationalUnitName = Organizational Unit Name (department, division)\n# organizationalUnitName_default = R&D\ncommonName = Common Name (hostname, FQDN, IP, or your name)\ncommonName_max = 256\ncommonName_default = NodeOPCUA\n# emailAddress = Email Address\n# emailAddress_max = 40\n# emailAddress_default = node-opcua (at) node-opcua (dot) com\nsubjectAltName = $ENV::ALTNAME';
var simple_config_template_cnf_default = config;
// packages/node-opcua-pki/lib/ca/templates/ca_config_template.cnf.ts
var config2 = `#.........DO NOT MODIFY BY HAND .........................
[ ca ]
default_ca = CA_default
[ CA_default ]
dir = %%ROOT_FOLDER%% # the main CA folder
certs = $dir/certs # where to store certificates
new_certs_dir = $dir/certs #
database = $dir/index.txt # the certificate database
serial = $dir/serial # the serial number counter
certificate = $dir/public/cacert.pem # The root CA certificate
private_key = $dir/private/cakey.pem # the CA private key
x509_extensions = usr_cert #
default_days = 3650 # default validity : 10 years
# default_md = sha1
default_md = sha256 # The default digest algorithm
preserve = no
policy = policy_match
# randfile = $dir/random.rnd
# default_startdate = YYMMDDHHMMSSZ
# default_enddate = YYMMDDHHMMSSZ
crl_dir = $dir/crl
crl_extensions = crl_ext
crl = $dir/revocation_list.crl # the Revocation list
crlnumber = $dir/crlnumber # CRL number file
default_crl_days = 30
default_crl_hours = 24
#msie_hack
[ policy_match ]
countryName = optional
stateOrProvinceName = optional
localityName = optional
organizationName = optional
organizationalUnitName = optional
commonName = optional
emailAddress = optional
[ req ]
default_bits = 4096 # Size of keys
default_keyfile = key.pem # name of generated keys
distinguished_name = req_distinguished_name
attributes = req_attributes
x509_extensions = v3_ca
#input_password
#output_password
string_mask = nombstr # permitted characters
req_extensions = v3_req
[ req_distinguished_name ]
#0 countryName = Country Name (2 letter code)
# countryName_default = FR
# countryName_min = 2
# countryName_max = 2
# stateOrProvinceName = State or Province Name (full name)
# stateOrProvinceName_default = Ile de France
# localityName = Locality Name (city, district)
# localityName_default = Paris
organizationName = Organization Name (company)
organizationName_default = NodeOPCUA
# organizationalUnitName = Organizational Unit Name (department, division)
# organizationalUnitName_default = R&D
commonName = Common Name (hostname, FQDN, IP, or your name)
commonName_max = 256
commonName_default = NodeOPCUA
# emailAddress = Email Address
# emailAddress_max = 40
# emailAddress_default = node-opcua (at) node-opcua (dot) com
[ req_attributes ]
#challengePassword = A challenge password
#challengePassword_min = 4
#challengePassword_max = 20
#unstructuredName = An optional company name
[ usr_cert ]
basicConstraints = critical, CA:FALSE
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer:always
#authorityKeyIdentifier = keyid
subjectAltName = $ENV::ALTNAME
# issuerAltName = issuer:copy
nsComment = ''OpenSSL Generated Certificate''
#nsCertType = client, email, objsign for ''everything including object signing''
#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem
#nsBaseUrl =
#nsRenewalUrl =
#nsCaPolicyUrl =
#nsSslServerName =
keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
extendedKeyUsage = critical,serverAuth ,clientAuth
{{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
{{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
{{/AIA_VALUE}}
[ v3_req ]
basicConstraints = critical, CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
extendedKeyUsage = critical,serverAuth ,clientAuth
subjectAltName = $ENV::ALTNAME
nsComment = "CA Generated by Node-OPCUA Certificate utility using openssl"
[ v3_ca_req ]
subjectKeyIdentifier = hash
basicConstraints = CA:TRUE
keyUsage = critical, cRLSign, keyCertSign
subjectAltName = $ENV::ALTNAME
nsComment = "CA CSR generated by Node-OPCUA Certificate utility using openssl"
[ v3_ca ]
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid:always,issuer:always
basicConstraints = CA:TRUE
keyUsage = critical, cRLSign, keyCertSign
subjectAltName = $ENV::ALTNAME
nsComment = "CA Certificate generated by Node-OPCUA Certificate utility using openssl"
#nsCertType = sslCA, emailCA
#issuerAltName = issuer:copy
#obj = DER:02:03
{{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
{{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
{{/AIA_VALUE}}[ v3_selfsigned]
basicConstraints = critical, CA:FALSE
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
extendedKeyUsage = critical,serverAuth ,clientAuth
nsComment = "Self-signed certificate, generated by NodeOPCUA"
subjectAltName = $ENV::ALTNAME
[ crl_ext ]
#issuerAltName = issuer:copy
authorityKeyIdentifier = keyid:always,issuer:always
#authorityInfoAccess = @issuer_info`;
var ca_config_template_cnf_default = config2;
// packages/node-opcua-pki/lib/ca/certificate_authority.ts
var defaultSubject = "/C=FR/ST=IDF/L=Paris/O=Local NODE-OPCUA Certificate Authority/CN=NodeOPCUA-CA";
var configurationFileTemplate = ca_config_template_cnf_default;
var configurationFileSimpleTemplate = simple_config_template_cnf_default;
var config3 = {
certificateDir: "INVALID",
forceCA: false,
pkiDir: "INVALID"
};
var n4 = makePath;
var q3 = quote;
function octetStringToIpAddress(a) {
return parseInt(a.substring(0, 2), 16).toString() + "." + parseInt(a.substring(2, 4), 16).toString() + "." + parseInt(a.substring(4, 6), 16).toString() + "." + parseInt(a.substring(6, 8), 16).toString();
}
(0, import_node_assert7.default)(octetStringToIpAddress("c07b9179") === "192.123.145.121");
async function construct_CertificateAuthority(certificateAuthority) {
const subject = certificateAuthority.subject;
const caRootDir = import_node_path5.default.resolve(certificateAuthority.rootDir);
async function make_folders() {
mkdirRecursiveSync(caRootDir);
mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "private"));
mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "public"));
mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "certs"));
mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "crl"));
mkdirRecursiveSync(import_node_path5.default.join(caRootDir, "conf"));
}
await make_folders();
async function construct_default_files() {
const serial = import_node_path5.default.join(caRootDir, "serial");
if (!import_node_fs7.default.existsSync(serial)) {
await import_node_fs7.default.promises.writeFile(serial, "1000");
}
const crlNumber = import_node_path5.default.join(caRootDir, "crlnumber");
if (!import_node_fs7.default.existsSync(crlNumber)) {
await import_node_fs7.default.promises.writeFile(crlNumber, "1000");
}
const indexFile = import_node_path5.default.join(caRootDir, "index.txt");
if (!import_node_fs7.default.existsSync(indexFile)) {
await import_node_fs7.default.promises.writeFile(indexFile, "");
}
}
await construct_default_files();
const caKeyExists = import_node_fs7.default.existsSync(import_node_path5.default.join(caRootDir, "private/cakey.pem"));
const caCertExists = import_node_fs7.default.existsSync(import_node_path5.default.join(caRootDir, "public/cacert.pem"));
if (caKeyExists && caCertExists && !config3.forceCA) {
debugLog("CA private key and certificate already exist ... skipping");
return;
}
if (caKeyExists && !caCertExists) {
debugLog("CA private key exists but cacert.pem is missing \u2014 rebuilding CA");
import_node_fs7.default.unlinkSync(import_node_path5.default.join(caRootDir, "private/cakey.pem"));
const staleCsr = import_node_path5.default.join(caRootDir, "private/cakey.csr");
if (import_node_fs7.default.existsSync(staleCsr)) {
import_node_fs7.default.unlinkSync(staleCsr);
}
}
displayTitle("Create Certificate Authority (CA)");
const indexFileAttr = import_node_path5.default.join(caRootDir, "index.txt.attr");
if (!import_node_fs7.default.existsSync(indexFileAttr)) {
await import_node_fs7.default.promises.writeFile(indexFileAttr, "unique_subject = no");
}
const caConfigFile = certificateAuthority.configFile;
if (1) {
let data = configurationFileTemplate;
data = makePath(data.replace(/%%ROOT_FOLDER%%/, caRootDir));
await import_node_fs7.default.promises.writeFile(caConfigFile, data);
}
const subjectOpt = ` -subj "${subject.toString()}" `;
const caCommonName = subject.commonName || "NodeOPCUA-CA";
setEnv("ALTNAME", `URI:urn:${caCommonName}`);
certificateAuthority._wireRevocationEnvVars();
const options = { cwd: caRootDir };
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
const configOption = ` -config ${q3(n4(configFile))}`;
const keySize = certificateAuthority.keySize;
const privateKeyFilename = import_node_path5.default.join(caRootDir, "private/cakey.pem");
const csrFilename = import_node_path5.default.join(caRootDir, "private/cakey.csr");
displayTitle(`Generate the CA private Key - ${keySize}`);
await (0, import_node_opcua_crypto2.generatePrivateKeyFile)(privateKeyFilename, keySize);
displayTitle("Generate a certificate request for the CA key");
await execute_openssl(
"req -new -sha256 -text -extensions v3_ca_req" + configOption + " -key " + q3(n4(privateKeyFilename)) + " -out " + q3(n4(csrFilename)) + " " + subjectOpt,
options
);
const issuerCA = certificateAuthority._issuerCA;
if (issuerCA) {
displayTitle("Generate CA Certificate (signed by issuer CA)");
const issuerCert = import_node_path5.default.resolve(issuerCA.caCertificate);
const issuerKey = import_node_path5.default.resolve(issuerCA.rootDir, "private/cakey.pem");
const issuerSerial = import_node_path5.default.resolve(issuerCA.rootDir, "serial");
await execute_openssl(
" x509 -sha256 -req -days 3650 -text -extensions v3_ca -extfile " + q3(n4(configFile)) + " -in private/cakey.csr -CA " + q3(n4(issuerCert)) + " -CAkey " + q3(n4(issuerKey)) + " -CAserial " + q3(n4(issuerSerial)) + " -out public/cacert.pem",
options
);
} else {
displayTitle("Generate CA Certificate (self-signed)");
await execute_openssl(
" x509 -sha256 -req -days 3650 -text -extensions v3_ca -extfile " + q3(n4(configFile)) + " -in private/cakey.csr -signkey " + q3(n4(privateKeyFilename)) + " -out public/cacert.pem",
options
);
}
displaySubtitle("generate initial CRL (Certificate Revocation List)");
await regenerateCrl(certificateAuthority.revocationList, configOption, options);
displayTitle("Create Certificate Authority (CA) ---> DONE");
}
async function regenerateCrl(revocationList, configOption, options) {
displaySubtitle("regenerate CRL (Certificate Revocation List)");
await execute_openssl(`ca -gencrl ${configOption} -out crl/revocation_list.crl`, options);
await execute_openssl("crl -in crl/revocation_list.crl -out crl/revocation_list.der -outform der", options);
displaySubtitle("Display (Certificate Revocation List)");
await execute_openssl(`crl -in ${q3(n4(revocationList))} -text -noout`, options);
}
function parseOpenSSLDate(dateStr) {
const raw = dateStr?.split(",")[0] ?? "";
if (raw.length < 12) return "";
const yy = parseInt(raw.substring(0, 2), 10);
const year = yy >= 70 ? 1900 + yy : 2e3 + yy;
const month = raw.substring(2, 4);
const day = raw.substring(4, 6);
const hour = raw.substring(6, 8);
const min = raw.substring(8, 10);
const sec = raw.substring(10, 12);
return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
}
function validateRevocationUrl(url2, fieldName) {
if (url2 === void 0) {
return void 0;
}
if (url2 === "") {
throw new Error(`${fieldName} must not be empty \u2014 pass undefined to disable the extension`);
}
let parsed;
try {
parsed = new URL(url2);
} catch {
throw new Error(`${fieldName} is not a valid URL: ${url2}`);
}
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
throw new Error(`${fieldName} must use http: or https: (got ${parsed.protocol} in ${url2})`);
}
if (!parsed.pathname || parsed.pathname === "/") {
throw new Error(`${fieldName} must include a path component (got ${url2})`);
}
const isLoopback = parsed.hostname === "localhost" || parsed.hostname === "::1" || parsed.hostname.startsWith("127.");
if (isLoopback) {
console.warn(
`[node-opcua-pki] ${fieldName} points at loopback (${url2}) \u2014 certificates issued with this URL will be unreachable from any other host.`
);
}
return url2;
}
var CertificateAuthority = class {
/** RSA key size used when generating the CA private key. */
keySize;
/** Root filesystem path of the CA directory structure. */
location;
/** X.500 subject of the CA certificate. */
subject;
/** @internal Parent CA (undefined for root CAs). */
_issuerCA;
/** @internal Configured CDP / AIA URLs (US-202). */
_crlDistributionUrl;
_ocspResponderUrl;
_caIssuersUrl;
constructor(options) {
(0, import_node_assert7.default)(Object.prototype.hasOwnProperty.call(options, "location"));
(0, import_node_assert7.default)(Object.prototype.hasOwnProperty.call(options, "keySize"));
this.location = options.location;
this.keySize = options.keySize || 2048;
this.subject = new import_node_opcua_crypto2.Subject(options.subject || defaultSubject);
this._issuerCA = options.issuerCA;
if (options.crlDistributionUrl !== void 0) {
this.setCrlDistributionUrl(options.crlDistributionUrl);
}
if (options.ocspResponderUrl !== void 0) {
this.setOcspResponderUrl(options.ocspResponderUrl);
}
if (options.caIssuersUrl !== void 0) {
this.setCaIssuersUrl(options.caIssuersUrl);
}
}
/**
* Public URL where the CRL produced by this CA is reachable, or
* `undefined` if no CDP extension should be emitted on issued certs.
*/
get crlDistributionUrl() {
return this._crlDistributionUrl;
}
/**
* Public URL of the OCSP responder, or `undefined` if no AIA OCSP
* leg should be emitted on issued certs.
*/
get ocspResponderUrl() {
return this._ocspResponderUrl;
}
/**
* Public URL where the issuer's certificate can be fetched, or
* `undefined` if no AIA caIssuers leg should be emitted.
*/
get caIssuersUrl() {
return this._caIssuersUrl;
}
/**
* Configure the URL embedded as `crlDistributionPoints` in every
* subsequently-issued certificate. Pass `undefined` to disable
* the extension entirely. Validated synchronously — throws on
* empty string, non-http(s) protocol, missing path. Warns (does
* not throw) when the URL points at loopback.
*
* @see US-202
*/
setCrlDistributionUrl(url2) {
this._crlDistributionUrl = validateRevocationUrl(url2, "crlDistributionUrl");
}
/**
* Configure the OCSP responder URL embedded as the `OCSP` leg of
* the `authorityInfoAccess` extension on every subsequently-issued
* certificate. Pass `undefined` to disable.
*
* @see US-202
*/
setOcspResponderUrl(url2) {
this._ocspResponderUrl = validateRevocationUrl(url2, "ocspResponderUrl");
}
/**
* Configure the caIssuers URL embedded as the `caIssuers` leg of
* the `authorityInfoAccess` extension on every subseq