UNPKG

node-opcua-pki

Version:
1,133 lines (1,102 loc) 160 kB
"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