node-opcua-pki 6.20.0 → 6.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/pki.mjs +1627 -821
- package/dist/bin/pki.mjs.map +1 -1
- package/dist/chunk-EQ4DGI4M.mjs +1917 -0
- package/dist/chunk-EQ4DGI4M.mjs.map +1 -0
- package/dist/core-BwqSJPHc.d.mts +1041 -0
- package/dist/core-BwqSJPHc.d.ts +1041 -0
- package/dist/core.d.mts +2 -0
- package/dist/core.d.ts +2 -0
- package/dist/core.js +1902 -0
- package/dist/core.js.map +1 -0
- package/dist/core.mjs +21 -0
- package/dist/core.mjs.map +1 -0
- package/dist/index.d.mts +136 -833
- package/dist/index.d.ts +136 -833
- package/dist/index.js +1890 -1086
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +890 -1922
- package/dist/index.mjs.map +1 -1
- package/package.json +14 -3
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../lib/toolbox/common.ts","../lib/ca/core/ca_database.ts","../lib/ca/backends/native_ca_backend.ts","../lib/toolbox/common2.ts","../lib/toolbox/config.ts","../lib/toolbox/debug.ts","../lib/toolbox/display.ts","../lib/misc/subject.ts","../lib/ca/core/certificate_authority_core.ts","../lib/ca/templates/ca_config_template.cnf.ts"],"sourcesContent":["// ---------------------------------------------------------------------------------------------------------------------\n// node-opcua-pki\n// ---------------------------------------------------------------------------------------------------------------------\n// Copyright (c) 2014-2026 - Etienne Rossignon - etienne.rossignon (at) gadz.org\n// Copyright (c) 2022-2026 - Sterfive.com\n// ---------------------------------------------------------------------------------------------------------------------\n//\n// This project is licensed under the terms of the MIT license.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n// documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the\n// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the\n// Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n// ---------------------------------------------------------------------------------------------------------------------\nimport assert from \"node:assert\";\n\n/** RSA key size in bits. */\nexport type KeySize = 1024 | 2048 | 3072 | 4096;\n/** Hex-encoded SHA-1 certificate thumbprint. */\nexport type Thumbprint = string;\n/** A filesystem path to a file. */\nexport type Filename = string;\n/** Status of a certificate in the trust store. */\nexport type CertificateStatus = \"unknown\" | \"trusted\" | \"rejected\";\n\nimport type { CaSigner, CertificatePurpose, PrivateKey } from \"node-opcua-crypto\";\nimport type { SubjectOptions } from \"../misc/subject\";\n\n/**\n * @deprecated Use {@link KeySize} instead.\n */\nexport type KeyLength = 1024 | 2048 | 3072 | 4096;\n\nexport function quote(str?: string): string {\n return `\"${str || \"\"}\"`;\n}\n\n/**\n * Subject Alternative Name (SAN) parameters for certificate\n * generation.\n */\nexport interface ProcessAltNamesParam {\n /** DNS host names to include in the SAN extension. */\n dns?: string[];\n /** IP addresses to include in the SAN extension. */\n ip?: string[];\n /** OPC UA application URI for the SAN extension. */\n applicationUri?: string;\n}\n\n/**\n * Options for creating a Certificate Signing Request (CSR).\n */\nexport interface CreateCertificateSigningRequestOptions extends ProcessAltNamesParam {\n /** X.500 subject for the certificate. */\n subject?: SubjectOptions | string;\n}\n\n/**\n * Extended CSR options that include filesystem paths and\n * certificate purpose — used internally by the OpenSSL toolbox.\n */\nexport interface CreateCertificateSigningRequestWithConfigOptions extends CreateCertificateSigningRequestOptions {\n /** Root directory of the PKI store. */\n rootDir: Filename;\n /** Path to the OpenSSL configuration file. */\n configFile: Filename;\n /**\n * The private key: a filesystem path to a PEM file (unencrypted;\n * the historical behavior), an already-resolved in-memory\n * {@link PrivateKey}, or an opaque {@link CaSigner} (HSM/KMS-held key;\n * only the openssl-free toolbox supports it — openssl needs a key file)\n * — see {@link CreateSelfSignCertificateWithConfigParam.privateKey}.\n */\n privateKey: Filename | PrivateKey | CaSigner;\n /** Intended purpose of the certificate. */\n purpose: CertificatePurpose;\n}\n\n/**\n * Narrows the `privateKey` union of the toolbox options: a {@link CaSigner}\n * is the only member that carries a `sign` method — a path is a string and\n * a {@link PrivateKey} envelope has only `hidden`.\n */\nexport function isOpaqueSigner(privateKey: Filename | PrivateKey | CaSigner): privateKey is CaSigner {\n return typeof privateKey !== \"string\" && typeof (privateKey as CaSigner).sign === \"function\";\n}\n\n/**\n * Validity period parameters for certificate generation.\n */\nexport interface StartDateEndDateParam {\n /** Certificate \"Not Before\" date. Defaults to now. */\n startDate?: Date;\n /** Certificate \"Not After\" date (computed from validity). */\n endDate?: Date;\n /** Number of days the certificate is valid. @defaultValue 365 */\n validity?: number;\n /**\n * Certificate validity in milliseconds.\n *\n * When provided, takes precedence over {@link validity} and enables\n * sub-day validity (X.509 supports second precision per RFC 5280\n * §4.1.2.5; OpenSSL is invoked with `-startdate`/`-enddate` already).\n *\n * Typical use is short-lived certificates for demos or for renewal\n * cycle testing. Existing day-based callers are unaffected.\n */\n validityMs?: number;\n}\n\n/**\n * Parameters for creating a self-signed certificate.\n */\nexport interface CreateSelfSignCertificateParam extends ProcessAltNamesParam, StartDateEndDateParam {\n /** X.500 subject for the certificate. */\n subject?: SubjectOptions | string;\n}\n\n/**\n * Extended self-signed certificate options that include\n * filesystem paths and purpose — used internally.\n */\nexport interface CreateSelfSignCertificateWithConfigParam extends CreateSelfSignCertificateParam {\n /** Root directory of the PKI store. */\n rootDir: Filename;\n /** Path to the OpenSSL configuration file. */\n configFile: Filename;\n /**\n * The private key: a filesystem path to a PEM file (unencrypted;\n * the historical behavior), an already-resolved in-memory\n * {@link PrivateKey} — used when the key is passphrase-protected on\n * disk, so the decrypted key material is never written back out in\n * cleartext — or an opaque {@link CaSigner} (HSM/KMS-held key; only\n * the openssl-free toolbox supports it — openssl needs a key file).\n */\n privateKey: Filename | PrivateKey | CaSigner;\n /** Intended purpose of the certificate. */\n purpose: CertificatePurpose;\n}\n\n/**\n * A passphrase, or a function resolving one — called lazily, and at most\n * once per `CertificateManager` / `CertificateAuthority` instance (the\n * result is cached in memory). Never logged.\n */\nexport type PrivateKeyPassphrase = string | (() => Promise<string>);\n\n/**\n * Sources a {@link PrivateKey} from somewhere other than the local\n * filesystem (an HSM, a KMS, ...). When configured on `CertificateManager`,\n * it overrides disk entirely — the on-disk `own/private/private_key.pem` is\n * neither generated nor read. `CertificateAuthority` does not support a\n * provider (its signing paths are openssl reading a key file); it does\n * support `privateKeyPassphrase`.\n */\nexport interface PrivateKeyProvider {\n getPrivateKey(): Promise<PrivateKey>;\n}\n\n/** Resolve a {@link PrivateKeyPassphrase}, if configured. */\nexport async function resolvePrivateKeyPassphrase(passphrase: PrivateKeyPassphrase | undefined): Promise<string | undefined> {\n if (passphrase === undefined) {\n return undefined;\n }\n return typeof passphrase === \"function\" ? await passphrase() : passphrase;\n}\n\n/**\n * General-purpose parameters passed to CA operations such as\n * {@link CertificateAuthority.signCertificateRequest} and\n * {@link CertificateAuthority.revokeCertificate}.\n */\nexport interface Params extends ProcessAltNamesParam, StartDateEndDateParam {\n /** X.500 subject for the certificate. */\n subject?: SubjectOptions | string;\n\n /** Path to the private key file. */\n privateKey?: string;\n /** Path to the OpenSSL configuration file. */\n configFile?: string;\n /** Root directory of the PKI store. */\n rootDir?: string;\n\n /** Output filename for the generated certificate. */\n outputFile?: string;\n /** CRL revocation reason (e.g. `\"keyCompromise\"`). */\n reason?: string;\n}\n\nexport function adjustDate(params: StartDateEndDateParam) {\n assert(params instanceof Object);\n params.startDate = params.startDate || new Date();\n assert(params.startDate instanceof Date);\n\n // Precedence: validityMs > validity (days) > default 365 days.\n // When validityMs is set, compute endDate via millisecond arithmetic\n // (sub-day capable). Otherwise preserve the legacy calendar-day\n // arithmetic via setDate() so day-based callers remain\n // bit-for-bit identical (including DST boundary handling).\n if (params.validityMs !== undefined) {\n if (params.validityMs <= 0) {\n throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);\n }\n params.endDate = new Date(params.startDate.getTime() + params.validityMs);\n // Keep params.validity in sync as the ceil-rounded day count, so\n // downstream code paths that read params.validity (e.g. logging,\n // OpenSSL command echo) still see a sensible value.\n params.validity = Math.ceil(params.validityMs / 86_400_000);\n } else {\n params.validity = params.validity || 365; // one year\n params.endDate = new Date(params.startDate.getTime());\n params.endDate.setDate(params.startDate.getDate() + params.validity);\n }\n\n // params.endDate = x509Date(endDate);\n // params.startDate = x509Date(startDate);\n\n assert(params.endDate instanceof Date);\n assert(params.startDate instanceof Date);\n\n // // istanbul ignore next\n // if (!g_config.silent) {\n // warningLog(\" start Date \", params.startDate.toUTCString(), x509Date(params.startDate));\n // warningLog(\" end Date \", params.endDate.toUTCString(), x509Date(params.endDate));\n // }\n}\n\nexport function adjustApplicationUri(params: Params) {\n const applicationUri = params.applicationUri || \"\";\n if (applicationUri.length > 200) {\n throw new Error(`Openssl doesn't support urn with length greater than 200${applicationUri}`);\n }\n}\n","// ---------------------------------------------------------------------------------------------------------------------\n// node-opcua-pki\n// ---------------------------------------------------------------------------------------------------------------------\n// Copyright (c) 2014-2026 - Etienne Rossignon - etienne.rossignon (at) gadz.org\n// Copyright (c) 2022-2026 - Sterfive.com\n// ---------------------------------------------------------------------------------------------------------------------\n//\n// This project is licensed under the terms of the MIT license.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n// documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the\n// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the\n// Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n// ---------------------------------------------------------------------------------------------------------------------\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport { convertPEMtoDER, readCertificatePEM } from \"node-opcua-crypto\";\n\n/**\n * A record from the OpenSSL CA certificate database (`index.txt`).\n */\nexport interface IssuedCertificateRecord {\n /** Hex-encoded serial number (e.g. `\"1000\"`). */\n serial: string;\n /** Certificate status. */\n status: \"valid\" | \"revoked\" | \"expired\";\n /** X.500 subject string (slash-delimited). */\n subject: string;\n /** Certificate expiry date as ISO-8601 string. */\n expiryDate: string;\n /**\n * Revocation date as ISO-8601 string.\n * Only present when `status === \"revoked\"`.\n */\n revocationDate?: string;\n /**\n * CRL revocation reason (e.g. `\"keyCompromise\"`), as stored after the\n * comma in the revocation-date field. Only present when\n * `status === \"revoked\"` and a reason was recorded.\n */\n reason?: string;\n}\n\n/**\n * Parse an OpenSSL date string (`YYMMDDHHmmssZ`) into an ISO-8601 string.\n */\nfunction parseOpenSSLDate(dateStr: string): string {\n // Revocation dates may have a reason suffix: \"YYMMDDHHmmssZ,reason\"\n // Strip anything after the first comma.\n const raw = dateStr?.split(\",\")[0] ?? \"\";\n if (raw.length < 12) return \"\";\n // OpenSSL uses 2-digit year; 70+ is 19xx, <70 is 20xx\n const yy = parseInt(raw.substring(0, 2), 10);\n const year = yy >= 70 ? 1900 + yy : 2000 + yy;\n const month = raw.substring(2, 4);\n const day = raw.substring(4, 6);\n const hour = raw.substring(6, 8);\n const min = raw.substring(8, 10);\n const sec = raw.substring(10, 12);\n return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;\n}\n\n/** The inverse of {@link parseOpenSSLDate}: format a `Date` as `YYMMDDHHmmssZ` (UTC). */\nfunction formatOpenSSLDate(date: Date): string {\n const yy = String(date.getUTCFullYear() % 100).padStart(2, \"0\");\n const mm = String(date.getUTCMonth() + 1).padStart(2, \"0\");\n const dd = String(date.getUTCDate()).padStart(2, \"0\");\n const hh = String(date.getUTCHours()).padStart(2, \"0\");\n const mi = String(date.getUTCMinutes()).padStart(2, \"0\");\n const ss = String(date.getUTCSeconds()).padStart(2, \"0\");\n return `${yy}${mm}${dd}${hh}${mi}${ss}Z`;\n}\n\n/** Extract the reason suffix (after the first comma) from a revocation-date field, if any. */\nfunction parseRevocationReason(dateStr: string): string | undefined {\n const parts = dateStr?.split(\",\");\n return parts && parts.length > 1 ? parts[1] : undefined;\n}\n\n/** Pad a hex string to an even number of digits, matching OpenSSL's `serial`/`crlnumber` file convention. */\nfunction evenLengthHex(value: bigint): string {\n const hex = value.toString(16).toUpperCase();\n return hex.length % 2 === 0 ? hex : `0${hex}`;\n}\n\n/**\n * Escape every control character (and backslash) as `\\xHH`, the way\n * OpenSSL's `X509_NAME_oneline` does before writing a subject to\n * `index.txt`. `index.txt` is a tab/newline-delimited table: a subject\n * (which comes verbatim from the requester's CSR, so is attacker\n * controlled) containing a raw `\\n` or `\\t` would otherwise inject a\n * forged row — e.g. an `R` entry naming a victim's serial, which the next\n * CRL regeneration would then faithfully publish as revoked.\n */\nfunction escapeIndexField(value: string): string {\n // biome-ignore lint/suspicious/noControlCharactersInRegex: escaping control characters is the point\n return value.replace(/[\\x00-\\x1f\\x7f\\\\]/g, (c) => `\\\\x${c.charCodeAt(0).toString(16).toUpperCase().padStart(2, \"0\")}`);\n}\n\n/** A hex serial handed to a writer must be exactly that — never something that could carry a field or row separator. */\nfunction assertHexSerial(serial: string): string {\n if (!/^[0-9A-Fa-f]+$/.test(serial)) {\n throw new Error(`Invalid certificate serial number: ${JSON.stringify(serial)}`);\n }\n return serial.toUpperCase();\n}\n\n/**\n * Read and write access to a CA's OpenSSL-format certificate database\n * (`index.txt`, `serial`, `crlnumber`, `certs/<SERIAL>.pem`).\n *\n * This is the format `openssl ca` itself writes and reads; every mutating\n * method here (`nextSerial`, `nextCrlNumber`, `appendIssued`, `markRevoked`,\n * `storeCertificate`) exists so a native (non-openssl) signing backend can\n * write the exact same on-disk format `openssl ca` does, so an install can\n * move between backends and external `openssl` tooling keeps working on the\n * same directory. Callers are responsible for serializing access — every\n * mutating call must happen under the owning CA's directory lock\n * (`CertificateAuthority._withCaDirectoryLock`), the same guarantee\n * `OpenSslCaBackend` gets from the lock around each `openssl ca` /\n * `openssl x509 -CAserial` invocation.\n */\nexport class CaDatabase {\n readonly #rootDir: string;\n\n constructor(rootDir: string) {\n this.#rootDir = rootDir;\n }\n\n /** Path to the OpenSSL certificate database file (`index.txt`). */\n public get indexFile(): string {\n return path.join(this.#rootDir, \"index.txt\");\n }\n\n /**\n * Parse the OpenSSL `index.txt` certificate database.\n *\n * Each line has tab-separated fields:\n * ```\n * status expiry [revocationDate] serial unknown subject\n * ```\n *\n * - status: `V` (valid), `R` (revoked), `E` (expired)\n * - expiry: `YYMMDDHHmmssZ`\n * - revocationDate: present only for revoked certs\n * - serial: hex string\n * - unknown: always `\"unknown\"`\n * - subject: X.500 slash-delimited string\n */\n public readIndex(): IssuedCertificateRecord[] {\n const indexPath = this.indexFile;\n if (!fs.existsSync(indexPath)) {\n return [];\n }\n\n const content = fs.readFileSync(indexPath, \"utf-8\");\n const lines = content.split(\"\\n\").filter((l) => l.trim().length > 0);\n const records: IssuedCertificateRecord[] = [];\n\n for (const line of lines) {\n const fields = line.split(\"\\t\");\n if (fields.length < 4) continue;\n\n const statusChar = fields[0];\n const expiryStr = fields[1];\n\n let serial: string;\n let subject: string;\n let revocationDate: string | undefined;\n\n if (statusChar === \"R\") {\n // Revoked: status expiry revocationDate serial unknown subject\n revocationDate = fields[2];\n serial = fields[3];\n subject = fields.length >= 6 ? fields[5] : \"\";\n } else {\n // Valid/Expired: status expiry (empty) serial unknown subject\n serial = fields[3];\n subject = fields.length >= 6 ? fields[5] : \"\";\n }\n\n let status: \"valid\" | \"revoked\" | \"expired\";\n switch (statusChar) {\n case \"V\":\n status = \"valid\";\n break;\n case \"R\":\n status = \"revoked\";\n break;\n case \"E\":\n status = \"expired\";\n break;\n default:\n continue; // skip unknown status\n }\n\n records.push({\n serial,\n status,\n subject,\n expiryDate: parseOpenSSLDate(expiryStr),\n revocationDate: revocationDate ? parseOpenSSLDate(revocationDate) : undefined,\n reason: revocationDate ? parseRevocationReason(revocationDate) : undefined\n });\n }\n\n return records;\n }\n\n /** Look up one record by serial number (case-insensitive). */\n public findBySerial(serial: string): IssuedCertificateRecord | undefined {\n const upper = serial.toUpperCase();\n return this.readIndex().find((r) => r.serial.toUpperCase() === upper);\n }\n\n /**\n * Read a specific issued certificate by serial number.\n *\n * OpenSSL stores signed certificates in the `certs/` directory using\n * the naming convention `<SERIAL>.pem`.\n *\n * @param serial - hex-encoded serial number (e.g. `\"1000\"`)\n * @returns the DER buffer, or `undefined` if not found\n */\n public getCertificateBySerial(serial: string): Buffer | undefined {\n const upper = serial.toUpperCase();\n const certFile = path.join(this.#rootDir, \"certs\", `${upper}.pem`);\n if (!fs.existsSync(certFile)) {\n return undefined;\n }\n const pem = readCertificatePEM(certFile);\n return convertPEMtoDER(pem);\n }\n\n /**\n * Read-increment-write a hex counter file (`serial`/`crlnumber`),\n * returning the value to hand out — matching `openssl ca`'s own\n * behavior: the file holds the *next* value to assign, and is bumped\n * to the following one immediately after being read. A `.old` backup\n * of the pre-bump content is kept, as `openssl` itself does.\n */\n #nextHexCounter(fileName: string): string {\n const counterFile = path.join(this.#rootDir, fileName);\n const current = BigInt(`0x${fs.readFileSync(counterFile, \"utf-8\").trim()}`);\n fs.copyFileSync(counterFile, `${counterFile}.old`);\n fs.writeFileSync(counterFile, evenLengthHex(current + 1n));\n return evenLengthHex(current);\n }\n\n /** Hand out the next certificate serial number, bumping the `serial` file. */\n public nextSerial(): string {\n return this.#nextHexCounter(\"serial\");\n }\n\n /** Hand out the next CRL number, bumping the `crlnumber` file. */\n public nextCrlNumber(): string {\n return this.#nextHexCounter(\"crlnumber\");\n }\n\n /** Append a newly-issued certificate's `V` (valid) row to `index.txt`. */\n public appendIssued(record: { serial: string; expiryDate: Date; subject: string }): void {\n const serial = assertHexSerial(record.serial);\n const subject = escapeIndexField(record.subject);\n const line = `V\\t${formatOpenSSLDate(record.expiryDate)}\\t\\t${serial}\\tunknown\\t${subject}\\n`;\n fs.appendFileSync(this.indexFile, line);\n }\n\n /**\n * Rewrite a certificate's `index.txt` row from `V` to `R` (revoked).\n * Throws if the serial is not found, or is already revoked — the same\n * \"already revoked\" case `openssl ca -revoke` itself rejects.\n */\n public markRevoked(serial: string, revocationDate: Date, reason: string): void {\n const upper = assertHexSerial(serial);\n if (!/^[A-Za-z]+$/.test(reason)) {\n throw new Error(`Invalid CRL reason: ${JSON.stringify(reason)}`);\n }\n const lines = fs\n .readFileSync(this.indexFile, \"utf-8\")\n .split(\"\\n\")\n .filter((l) => l.trim().length > 0);\n\n let found = false;\n const rewritten = lines.map((line) => {\n const fields = line.split(\"\\t\");\n if (fields.length < 4 || fields[3]?.toUpperCase() !== upper) {\n return line;\n }\n if (fields[0] === \"R\") {\n throw new Error(`Certificate ${upper} is already revoked`);\n }\n found = true;\n const subject = fields.length >= 6 ? fields[5] : \"\";\n return `R\\t${fields[1]}\\t${formatOpenSSLDate(revocationDate)},${reason}\\t${upper}\\tunknown\\t${subject}`;\n });\n\n if (!found) {\n throw new Error(`Certificate ${upper} not found in the certificate database`);\n }\n fs.writeFileSync(this.indexFile, `${rewritten.join(\"\\n\")}\\n`);\n }\n\n /** Store a signed certificate's PEM under `certs/<SERIAL>.pem`, as `openssl ca` does. */\n public storeCertificate(serial: string, pem: string): void {\n const certFile = path.join(this.#rootDir, \"certs\", `${assertHexSerial(serial)}.pem`);\n fs.writeFileSync(certFile, pem);\n }\n}\n","// ---------------------------------------------------------------------------------------------------------------------\n// node-opcua-pki\n// ---------------------------------------------------------------------------------------------------------------------\n// Copyright (c) 2014-2026 - Etienne Rossignon - etienne.rossignon (at) gadz.org\n// Copyright (c) 2022-2026 - Sterfive.com\n// ---------------------------------------------------------------------------------------------------------------------\n//\n// This project is licensed under the terms of the MIT license.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n// documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the\n// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the\n// Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n// ---------------------------------------------------------------------------------------------------------------------\n\nimport fs from \"node:fs\";\nimport path from \"node:path\";\nimport {\n type CaSignAlgorithm,\n type CaSigner,\n CertificatePurpose,\n createCertificateFromCsr,\n createCertificateSigningRequest,\n createCrl,\n isCaSigner,\n privateKeyToCryptoKey,\n readPrivateKey,\n Subject,\n x509\n} from \"node-opcua-crypto\";\nimport { displaySubtitle, displayTitle, type Params, type ProcessAltNamesParam } from \"../../toolbox\";\nimport type { CaBackend } from \"../core/ca_backend\";\nimport { CaDatabase } from \"../core/ca_database\";\nimport type { CertificateAuthorityCore } from \"../core/certificate_authority_core\";\n\n/** The CA-certificate lifetime the openssl backend hard-codes (`x509 -req -days 3650`). */\nconst CA_CERT_VALIDITY_DAYS = 3650;\n\nconst RSA_SHA256: CaSignAlgorithm = { name: \"RSASSA-PKCS1-v1_5\", hash: { name: \"SHA-256\" } };\n\n/** A signer declares its own algorithm; a raw key is always the RSA/SHA-256 this CA generates. */\nfunction signingAlgorithmOf(key: CryptoKey | CaSigner): CaSignAlgorithm {\n return isCaSigner(key) ? key.algorithm : RSA_SHA256;\n}\n\nfunction daysFromNow(notBefore: Date, days: number): Date {\n return new Date(notBefore.getTime() + days * 24 * 60 * 60 * 1000);\n}\n\n/**\n * The SAN every CA certificate carries. RFC 5280 does not require one on a\n * CA certificate, but the OPC UA profile does, and validators (UaExpert,\n * the compliance tools) reject a CA without it - so mirror what the\n * openssl backend puts in `[v3_ca]`: `URI:urn:<commonName>`.\n */\nfunction caSanUri(ca: CertificateAuthorityCore): string {\n return `urn:${ca.subject.commonName || \"NodeOPCUA-CA\"}`;\n}\n\n/**\n * The subject alternative names a CSR asked for, in the shape the signing\n * primitive wants. `openssl ca` does not copy SANs across from the CSR\n * (openssl/openssl#10458), which is why the caller has to re-derive them;\n * peculiar decodes IP addresses to dotted form for us.\n */\nfunction sanFromCsr(csrPem: string): { dns: string[]; ip: string[]; applicationUri?: string } {\n const request = new x509.Pkcs10CertificateRequest(csrPem);\n const extension = request.getExtension(\"2.5.29.17\") as x509.SubjectAlternativeNameExtension | null;\n const dns: string[] = [];\n const ip: string[] = [];\n let applicationUri: string | undefined;\n for (const name of extension?.names.toJSON() ?? []) {\n if (name.type === \"dns\") {\n dns.push(name.value);\n } else if (name.type === \"ip\") {\n ip.push(name.value);\n } else if (name.type === \"url\" && applicationUri === undefined) {\n applicationUri = name.value;\n }\n }\n return { dns, ip, applicationUri };\n}\n\n/**\n * Maps this codebase's CRL revocation reason strings (see\n * `CertificateAuthority.revokeCertificate`'s `crlReasons` list) to\n * `@peculiar/x509`'s `X509CrlReason` enum keys — same reasons, but\n * `X509CrlReason.cACompromise` is lowercase-first where this codebase's\n * `\"CACompromise\"` is not.\n */\nconst REASON_TO_X509_CRL_REASON: Record<string, x509.X509CrlReason> = {\n unspecified: x509.X509CrlReason.unspecified,\n keyCompromise: x509.X509CrlReason.keyCompromise,\n CACompromise: x509.X509CrlReason.cACompromise,\n affiliationChanged: x509.X509CrlReason.affiliationChanged,\n superseded: x509.X509CrlReason.superseded,\n cessationOfOperation: x509.X509CrlReason.cessationOfOperation,\n certificateHold: x509.X509CrlReason.certificateHold,\n removeFromCRL: x509.X509CrlReason.removeFromCRL\n};\n\n/**\n * Builds OpenSSL's slash-delimited one-line subject (`\"/CN=Leaf/O=Acme\"`)\n * from the certificate's parsed subject attributes — NOT by re-splitting\n * `Name.toString()`. The subject comes verbatim from the requester's CSR,\n * so it is attacker-controlled; the string form escapes `,` (which a\n * `split(\",\")` would then mangle) and, because its control-character\n * `replace()` has no `g` flag, escapes only the FIRST `\\n`/`\\t` per value,\n * letting later ones through raw — which would let a crafted CN inject a\n * forged row into the tab/newline-delimited `index.txt`. Working from\n * `toJSON()` sidesteps that entirely; {@link CaDatabase} additionally\n * escapes every control character it writes, as `X509_NAME_oneline` does.\n */\nfunction toOpenSslSubjectString(subjectName: x509.Name): string {\n const parts: string[] = [];\n for (const rdn of subjectName.toJSON()) {\n for (const [type, values] of Object.entries(rdn)) {\n for (const value of values) {\n parts.push(`${type}=${value}`);\n }\n }\n }\n return `/${parts.join(\"/\")}`;\n}\n\n/**\n * A native (pure-JS, no `openssl` child process) {@link CaBackend}: it\n * bootstraps a CA, signs end-entity and subordinate-CA requests, revokes,\n * and builds CRLs entirely through `node-opcua-crypto`'s signing\n * primitives and {@link CaDatabase}'s write path, so the resulting\n * `index.txt`/`serial`/`crlnumber`/`certs/` are the same on-disk format\n * `openssl ca` produces and an install can move between backends.\n *\n * The signing key reaches every operation through\n * `CertificateAuthority._getSigningKey()`, which yields either the on-disk\n * key or an external `CaSigner`. Nothing below can tell the difference,\n * which is the point: a CA whose key lives in an HSM never has a\n * `private/cakey.pem` and never needs one.\n *\n * Two deliberate differences from the openssl backend, both improvements:\n * a subordinate CA's certificate is recorded in the issuer's `index.txt`\n * (`openssl x509 -CAserial` bumps the serial file but records nothing),\n * and a subordinate's SAN is taken from its own CSR rather than being\n * stamped with the issuer's.\n */\nexport class NativeCaBackend implements CaBackend {\n /** Nothing to check: this backend spawns no process and needs no tool on PATH. */\n /** Signing goes through `ca._getSigningKey()`, which may be an external signer. */\n public readonly supportsExternalSigner = true;\n\n public async preflight(): Promise<void> {\n // intentionally empty\n }\n\n /**\n * Write a CSR for the CA's own key, the `[v3_ca_req]` equivalent. The\n * key comes from the CA rather than from `privateKeyFile`: on a\n * signer-backed CA that file does not exist.\n */\n public async generateCaCsr(\n ca: CertificateAuthorityCore,\n _caRootDir: string,\n _privateKeyFile: string,\n csrFile: string\n ): Promise<void> {\n displayTitle(\"Generate a certificate request for the CA key\");\n const signingKey = await ca._getSigningKey();\n const { csr } = await createCertificateSigningRequest({\n privateKey: signingKey,\n subject: ca.subject.toString(),\n applicationUri: caSanUri(ca),\n purpose: CertificatePurpose.ForCertificateAuthority\n });\n await fs.promises.writeFile(csrFile, csr);\n }\n\n public async bootstrap(ca: CertificateAuthorityCore): Promise<void> {\n const caRootDir = path.resolve(ca.rootDir);\n const csrFile = path.join(caRootDir, \"private/cakey.csr\");\n await this.generateCaCsr(ca, caRootDir, path.join(caRootDir, \"private/cakey.pem\"), csrFile);\n\n const issuerCA = ca._issuerCA;\n if (issuerCA) {\n displayTitle(\"Generate CA Certificate (signed by issuer CA)\");\n // The issuer's serial file and database are mutated here, so the\n // issuer's own lock is required - the caller only holds this\n // (subordinate) CA's. Subordinate -> issuer is the only cross-CA\n // lock edge in this codebase and it is never reversed, so it\n // cannot deadlock; the exception is two CAs sharing one rootDir,\n // where the lock is already held and re-acquiring the\n // non-reentrant file lock would deadlock against itself.\n const signWithIssuer = () => this.signSubordinateCsr(issuerCA, csrFile, ca.caCertificate, CA_CERT_VALIDITY_DAYS);\n if (path.resolve(issuerCA.rootDir) === caRootDir) {\n await signWithIssuer();\n } else {\n await issuerCA._withCaDirectoryLock(signWithIssuer);\n }\n } else {\n displayTitle(\"Generate CA Certificate (self-signed)\");\n const csrPem = await fs.promises.readFile(csrFile, \"utf-8\");\n const signingKey = await ca._getSigningKey();\n const request = new x509.Pkcs10CertificateRequest(csrPem);\n const notBefore = new Date();\n const { cert } = await createCertificateFromCsr({\n csr: csrPem,\n // the CSR's own parsed subject, so issuer and subject are\n // byte-identical on a self-signed root: re-encoding a string\n // form could differ and break chain building\n issuerName: request.subjectName,\n issuerPublicKey: request.publicKey,\n signingKey,\n signingAlgorithm: signingAlgorithmOf(signingKey),\n notBefore,\n notAfter: daysFromNow(notBefore, CA_CERT_VALIDITY_DAYS),\n purpose: CertificatePurpose.ForCertificateAuthority,\n applicationUri: caSanUri(ca),\n revocation: {\n crlDistributionUrl: ca.crlDistributionUrl,\n ocspResponderUrl: ca.ocspResponderUrl,\n caIssuersUrl: ca.caIssuersUrl\n }\n });\n await fs.promises.writeFile(ca.caCertificate, cert);\n }\n\n displaySubtitle(\"generate initial CRL (Certificate Revocation List)\");\n await this.#regenerateCrlLocked(ca, new CaDatabase(ca.rootDir));\n displayTitle(\"Create Certificate Authority (CA) ---> DONE\");\n }\n\n /** Sign a subordinate CA's request with this CA's key, the `[v3_ca]` equivalent. */\n public async signSubordinateCsr(\n ca: CertificateAuthorityCore,\n csrFile: string,\n certFile: string,\n validityDays: number\n ): Promise<void> {\n const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);\n const csrPem = await fs.promises.readFile(csrFile, \"utf-8\");\n const db = new CaDatabase(ca.rootDir);\n const serialNumber = db.nextSerial();\n const notBefore = new Date();\n const notAfter = daysFromNow(notBefore, validityDays);\n\n const { cert } = await createCertificateFromCsr({\n csr: csrPem,\n issuerName,\n issuerPublicKey,\n signingKey,\n signingAlgorithm,\n serialNumber,\n notBefore,\n notAfter,\n purpose: CertificatePurpose.ForCertificateAuthority,\n // the subordinate's own SAN, not the issuer's\n ...sanFromCsr(csrPem),\n revocation: {\n crlDistributionUrl: ca.crlDistributionUrl,\n ocspResponderUrl: ca.ocspResponderUrl,\n caIssuersUrl: ca.caIssuersUrl\n }\n });\n\n await fs.promises.writeFile(certFile, cert);\n this.#record(db, serialNumber, cert, notAfter);\n }\n\n /**\n * Self-sign a certificate with a caller-supplied key file and record it\n * in this CA's database: the native equivalent of `openssl req -new`\n * followed by `openssl ca -selfsign`, which likewise applies the\n * end-entity profile and updates `index.txt`.\n */\n public async createSelfSignedCertificate(\n ca: CertificateAuthorityCore,\n certificateFile: string,\n privateKeyFile: string,\n params: Params\n ): Promise<void> {\n displaySubtitle(\"- the certificate signing request\");\n const key = await privateKeyToCryptoKey(readPrivateKey(privateKeyFile, await ca._privateKeyPassphrase()));\n const { csr } = await createCertificateSigningRequest({\n privateKey: key,\n subject: params.subject ? new Subject(params.subject).toString() : ca.subject.toString(),\n dns: params.dns,\n ip: params.ip,\n applicationUri: params.applicationUri,\n purpose: CertificatePurpose.ForApplication\n });\n\n displaySubtitle(\"- creating the self-signed certificate\");\n const request = new x509.Pkcs10CertificateRequest(csr);\n const db = new CaDatabase(ca.rootDir);\n const serialNumber = db.nextSerial();\n const notBefore = params.startDate ?? new Date();\n const notAfter = params.endDate ?? daysFromNow(notBefore, params.validity ?? 365);\n\n const { cert } = await createCertificateFromCsr({\n csr,\n issuerName: request.subjectName,\n issuerPublicKey: request.publicKey,\n signingKey: key,\n signingAlgorithm: RSA_SHA256,\n serialNumber,\n notBefore,\n notAfter,\n purpose: CertificatePurpose.ForApplication,\n ...sanFromCsr(csr)\n });\n\n await fs.promises.writeFile(certificateFile, cert);\n this.#record(db, serialNumber, cert, notAfter);\n }\n\n /** `certs/<SERIAL>.pem` plus the `V` row, exactly as `openssl ca` writes them. */\n #record(db: CaDatabase, serialNumber: string, certPem: string, notAfter: Date): void {\n db.storeCertificate(serialNumber, certPem);\n db.appendIssued({\n serial: serialNumber,\n expiryDate: notAfter,\n subject: toOpenSslSubjectString(new x509.X509Certificate(certPem).subjectName)\n });\n }\n\n /**\n * The CA's signing key and issuer identity, resolved fresh on every\n * call and never cached across operations. The identity is read back\n * from the CA certificate on disk so that the issuer field of anything\n * this CA signs is byte-identical to that certificate's subject.\n */\n async #issuerContext(ca: CertificateAuthorityCore): Promise<{\n signingKey: CryptoKey | CaSigner;\n signingAlgorithm: CaSignAlgorithm;\n issuerPublicKey: x509.PublicKey;\n issuerName: x509.Name;\n }> {\n const signingKey = await ca._getSigningKey();\n const caCertPem = await fs.promises.readFile(ca.caCertificate, \"utf-8\");\n const caCert = new x509.X509Certificate(caCertPem);\n return {\n signingKey,\n signingAlgorithm: signingAlgorithmOf(signingKey),\n issuerPublicKey: caCert.publicKey,\n issuerName: caCert.subjectName\n };\n }\n\n public async signEndEntityCsr(\n ca: CertificateAuthorityCore,\n certificate: string,\n csr: string,\n params: Params,\n sanOverride: Required<ProcessAltNamesParam>\n ): Promise<void> {\n const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);\n const db = new CaDatabase(ca.rootDir);\n const serialNumber = db.nextSerial();\n const csrPem = await fs.promises.readFile(csr, \"utf-8\");\n\n const notBefore = params.startDate ?? new Date();\n const notAfter = params.endDate ?? new Date(notBefore.getTime() + (params.validity ?? 365) * 24 * 60 * 60 * 1000);\n\n const { cert } = await createCertificateFromCsr({\n csr: csrPem,\n issuerName,\n issuerPublicKey,\n signingKey,\n signingAlgorithm,\n serialNumber,\n notBefore,\n notAfter,\n purpose: CertificatePurpose.ForApplication,\n dns: sanOverride.dns,\n ip: sanOverride.ip,\n applicationUri: sanOverride.applicationUri,\n revocation: {\n crlDistributionUrl: ca.crlDistributionUrl,\n ocspResponderUrl: ca.ocspResponderUrl,\n caIssuersUrl: ca.caIssuersUrl\n }\n });\n\n await fs.promises.writeFile(certificate, cert);\n this.#record(db, serialNumber, cert, notAfter);\n }\n\n async #regenerateCrlLocked(ca: CertificateAuthorityCore, db: CaDatabase): Promise<void> {\n const { signingKey, signingAlgorithm, issuerPublicKey, issuerName } = await this.#issuerContext(ca);\n const crlNumber = db.nextCrlNumber();\n const entries = db\n .readIndex()\n .filter((r) => r.status === \"revoked\")\n .map((r) => ({\n serialNumber: r.serial,\n revocationDate: r.revocationDate ? new Date(r.revocationDate) : new Date(),\n reason: r.reason ? REASON_TO_X509_CRL_REASON[r.reason] : undefined\n }));\n\n const { crl } = await createCrl({\n issuerName,\n issuerPublicKey,\n signingKey,\n signingAlgorithm,\n crlNumber: BigInt(`0x${crlNumber}`),\n entries\n });\n\n await fs.promises.writeFile(ca.revocationList, crl);\n const der = new x509.X509Crl(crl).rawData;\n await fs.promises.writeFile(ca.revocationListDER, Buffer.from(der));\n }\n\n public async regenerateCrl(ca: CertificateAuthorityCore): Promise<void> {\n const db = new CaDatabase(ca.rootDir);\n await this.#regenerateCrlLocked(ca, db);\n }\n\n public async revoke(ca: CertificateAuthorityCore, certificate: string, reason: string): Promise<void> {\n const certPem = await fs.promises.readFile(certificate, \"utf-8\");\n const cert = new x509.X509Certificate(certPem);\n const db = new CaDatabase(ca.rootDir);\n db.markRevoked(cert.serialNumber, new Date(), reason);\n await this.#regenerateCrlLocked(ca, db);\n }\n}\n","// ---------------------------------------------------------------------------------------------------------------------\n// node-opcua-pki\n// ---------------------------------------------------------------------------------------------------------------------\n// Copyright (c) 2014-2026 - Etienne Rossignon - etienne.rossignon (at) gadz.org\n// Copyright (c) 2022-2026 - Sterfive.com\n// ---------------------------------------------------------------------------------------------------------------------\n//\n// This project is licensed under the terms of the MIT license.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n// documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the\n// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the\n// Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n// ---------------------------------------------------------------------------------------------------------------------\n\nimport assert from \"node:assert\";\nimport fs from \"node:fs\";\nimport path from \"node:path\";\n\nimport chalk from \"chalk\";\n\nimport { g_config } from \"./config\";\n\nimport { debugLog, warningLog } from \"./debug\";\n\nexport function certificateFileExist(certificateFile: string): boolean {\n // istanbul ignore next\n if (fs.existsSync(certificateFile) && !g_config.force) {\n warningLog(\n chalk.yellow(\" certificate \") + chalk.cyan(certificateFile) + chalk.yellow(\" already exists => do not overwrite\")\n );\n return false;\n }\n return true;\n}\n\nexport function mkdirRecursiveSync(folder: string): void {\n if (!fs.existsSync(folder)) {\n // istanbul ignore next\n debugLog(chalk.white(\" .. constructing \"), folder);\n fs.mkdirSync(folder, { recursive: true });\n }\n}\n\n/**\n * Restrict a file or directory to owner-only access (mode 0600/0700).\n *\n * fs.chmod only toggles the read-only attribute on Windows and cannot express\n * POSIX-style owner-only permissions, so this is a no-op there rather than a\n * false sense of protection. Errors are logged, not thrown: a read-only mount\n * or an unexpected ownership mismatch should not prevent the server from\n * starting.\n */\nexport function restrictPrivateFilePermissions(target: string, mode: 0o600 | 0o700): void {\n // istanbul ignore if\n if (process.platform === \"win32\") {\n return;\n }\n try {\n fs.chmodSync(target, mode);\n } catch (err) {\n // istanbul ignore next\n warningLog(chalk.yellow(\" could not restrict permissions on \"), target, (err as Error).message);\n }\n}\n\n/**\n * Create (or repair) a directory intended to hold private key material,\n * restricted to owner-only access. Safe to call on an existing directory\n * to repair permissions left by an earlier, less restrictive version.\n */\nexport function ensurePrivateDirectory(dir: string): void {\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true, mode: 0o700 });\n }\n restrictPrivateFilePermissions(dir, 0o700);\n}\n\n/**\n * `true` if the PEM file at `filename` holds a passphrase-encrypted PKCS#8\n * key (`-----BEGIN ENCRYPTED PRIVATE KEY-----`). Header check only; it does\n * not validate or decrypt the key. Throws if the file cannot be read.\n */\nexport function isEncryptedPrivateKeyFile(filename: string): boolean {\n return fs.readFileSync(filename, \"utf-8\").includes(\"-----BEGIN ENCRYPTED PRIVATE KEY-----\");\n}\n\nexport function makePath(folderName: string, filename?: string): string {\n let s: string;\n if (filename) {\n s = path.join(path.normalize(folderName), filename);\n } else {\n assert(folderName);\n s = folderName;\n }\n s = s.replace(/\\\\/g, \"/\");\n return s;\n}\n","// ---------------------------------------------------------------------------------------------------------------------\n// node-opcua-pki\n// ---------------------------------------------------------------------------------------------------------------------\n// Copyright (c) 2014-2026 - Etienne Rossignon - etienne.rossignon (at) gadz.org\n// Copyright (c) 2022-2026 - Sterfive.com\n// ---------------------------------------------------------------------------------------------------------------------\n//\n// This project is licensed under the terms of the MIT license.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n// documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the\n// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the\n// Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n// ---------------------------------------------------------------------------------------------------------------------\nexport const g_config = {\n opensslVersion: \"unset\",\n silent: process.env.VERBOSE ? !process.env.VERBOSE : true,\n force: false\n};\n","// ---------------------------------------------------------------------------------------------------------------------\n// node-opcua-pki\n// ---------------------------------------------------------------------------------------------------------------------\n// Copyright (c) 2014-2026 - Etienne Rossignon - etienne.rossignon (at) gadz.org\n// Copyright (c) 2022-2026 - Sterfive.com\n// ---------------------------------------------------------------------------------------------------------------------\n//\n// This project is licensed under the terms of the MIT license.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n// documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the\n// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the\n// Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n// ---------------------------------------------------------------------------------------------------------------------\nexport const doDebug = process.env.NODEOPCUAPKIDEBUG || false;\nexport const displayError = true;\nexport const displayDebug = !!process.env.NODEOPCUAPKIDEBUG || false;\nexport function debugLog(...args: unknown[]) {\n // istanbul ignore next\n if (displayDebug) {\n console.log.apply(null, args);\n }\n}\nexport function warningLog(...args: unknown[]) {\n console.log.apply(null, args);\n}\n","// ---------------------------------------------------------------------------------------------------------------------\n// node-opcua-pki\n// ---------------------------------------------------------------------------------------------------------------------\n// Copyright (c) 2014-2026 - Etienne Rossignon - etienne.rossignon (at) gadz.org\n// Copyright (c) 2022-2026 - Sterfive.com\n// ---------------------------------------------------------------------------------------------------------------------\n//\n// This project is licensed under the terms of the MIT license.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n// documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the\n// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the\n// Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n// ---------------------------------------------------------------------------------------------------------------------\nimport chalk from \"chalk\";\nimport { g_config } from \"./config\";\nimport { warningLog } from \"./debug\";\n\n// istanbul ignore next\nexport function displayChapter(str: string) {\n const l = \" \";\n warningLog(`${chalk.bgWhite(l)} `);\n str = ` ${str}${l}`.substring(0, l.length);\n warningLog(chalk.bgWhite.cyan(str));\n warningLog(`${chalk.bgWhite(l)} `);\n}\n\nexport function displayTitle(str: string) {\n // istanbul ignore next\n if (!g_config.silent) {\n warningLog(\"\");\n warningLog(chalk.yellowBright(str));\n warningLog(chalk.yellow(new Array(str.length + 1).join(\"=\")), \"\\n\");\n }\n}\n\nexport function displaySubtitle(str: string) {\n // istanbul ignore next\n if (!g_config.silent) {\n warningLog(\"\");\n warningLog(` ${chalk.yellowBright(str)}`);\n warningLog(` ${chalk.white(new Array(str.length + 1).join(\"-\"))}`, \"\\n\");\n }\n}\nexport function display(str: string) {\n // istanbul ignore next\n if (!g_config.silent) {\n warningLog(` ${str}`);\n }\n}\n","export type { SubjectOptions } from \"node-opcua-crypto\";\nexport { Subject } from \"node-opcua-crypto\";\n","// ---------------------------------------------------------------------------------------------------------------------\n// node-opcua\n// ---------------------------------------------------------------------------------------------------------------------\n// Copyright (c) 2014-2026 - Etienne Rossignon - etienne.rossignon (at) gadz.org\n// Copyright (c) 2022-2026 - Sterfive.com\n// ---------------------------------------------------------------------------------------------------------------------\n//\n// This project is licensed under the terms of the MIT license.\n//\n// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated\n// documentation files (the \"Software\"), to deal in the Software without restriction, including without limitation the\n// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to\n// permit persons to whom the Software is furnished to do so, subject to the following conditions:\n//\n// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the\n// Software.\n//\n// THE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE\n// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR\n// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR\n// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n// ---------------------------------------------------------------------------------------------------------------------\nimport assert from \"node:assert\";\nimport fs from \"node:fs\";\nimport os from \"node:os\";\nimport path from \"node:path\";\nimport { withLock } from \"@ster5/global-mutex\";\nimport chalk from \"chalk\";\nimport {\n type CaSigner,\n CertificatePurpose,\n certificateMatchesPrivateKey,\n convertPEMtoDER,\n createCertificateSigningRequest,\n createPfx,\n exploreCertificate,\n exploreCertificateSigningRequest,\n generatePrivateKeyFile,\n type PrivateKey,\n privateKeyToCryptoKey,\n readCertificatePEM,\n readCertificateSigningRequest,\n readPrivateKey,\n Subject,\n type SubjectOptions,\n toPem,\n verifyCertificateSignature,\n writePrivateKeyFile,\n x509\n} from \"node-opcua-crypto\";\nimport {\n adjustApplicationUri,\n adjustDate,\n certificateFileExist,\n debugLog,\n displaySubtitle,\n displayTitle,\n ensurePrivateDirectory,\n type Filename,\n isEncryptedPrivateKeyFile,\n type KeySize,\n makePath,\n mkdirRecursiveSync,\n type Params,\n type PrivateKeyPassphrase,\n type ProcessAltNamesParam,\n resolvePrivateKeyPassphrase,\n restrictPrivateFilePermissions,\n warningLog\n} from \"../../toolbox\";\nimport type { CaBackend } from \"./ca_backend\";\nimport { CaDatabase, type IssuedCertificateRecord } from \"./ca_database\";\n\n/** Default X.500 subject used when no custom subject is provided. */\nexport const defaultSubject = \"/C=FR/ST=IDF/L=Paris/O=Local NODE-OPCUA Certificate Authority/CN=NodeOPCUA-CA\";\n\nimport _ca_config_template from \"../templates/ca_config_template.cnf\";\n\nexport const configurationFileTemplate: string = _ca_config_template;\n\n/**\n * Escape a value for use inside a double-quoted openssl config value.\n *\n * openssl's config parser (crypto/conf/conf_def.c) treats an unquoted value\n * specially: `$var` / `${var}` / `$(var)` are expanded, and `'`, `\"` and the\n * backtick are all quote characters (a backtick pair is silently removed).\n * Inside a double-quoted value none of that applies: characters are copied\n * verbatim except a backslash, which escapes the next character. So a path\n * containing `$`, backticks, `#` or spaces is safe once double-quoted with\n * `\\` and `\"` escaped.\n */\nexport function escapeOpensslConfDoubleQuoted(value: string): string {\n return value.replace(/\\\\/g, \"\\\\\\\\\").replace(/\"/g, '\\\\\"');\n}\n\n/**\n * Render the CA openssl configuration for `caRootDir`. The root folder is\n * emitted forward-slashed (openssl accepts that on every platform) and\n * double-quoted, so a CA `location` containing characters that openssl's\n * config syntax would otherwise interpret (`$`, backticks, `#`, quotes) is\n * taken literally.\n */\nexport function renderCaConfig(caRootDir: string): string {\n return configurationFileTemplate.replace(/%%ROOT_FOLDER%%/, escapeOpensslConfDoubleQuoted(makePath(caRootDir)));\n}\n\nconst config = {\n certificateDir: \"INVALID\",\n forceCA: false,\n pkiDir: \"INVALID\"\n};\n\n/**\n * Upper bound on how long a CA operation waits for `.ca.lock` before\n * throwing. CA operations themselves complete in seconds; a dead holder's\n * lock goes stale and is taken over after ~2 minutes, so 5 minutes covers\n * every legitimate wait with margin while still turning a stuck holder\n * into a loud error instead of a silent fleet-wide hang.\n */\nconst CA_LOCK_MAX_WAIT_MS = 5 * 60_000;\n\n/**\n * How long a waiter sleeps between attempts at the CA directory lock.\n * The lock library's default backoff doubles without an upper bound, so a\n * queue of contending operations ends up sleeping through 1s, 2s, 4s, 8s,\n * 16s ... while the lock is in fact free again after milliseconds: eight\n * queued signings measured over two minutes. Capping the interval keeps a\n * freed lock from going unnoticed; `CA_LOCK_MAX_WAIT_MS` still bounds the\n * total wait.\n */\nconst CA_LOCK_RETRY = { minTimeout: 20, maxTimeout: 250 };\n\n// convert 'c07b9179' to \"192.123.145.121\"\nfunction octetStringToIpAddress(a: string) {\n return (\n parseInt(a.substring(0, 2), 16).toString() +\n \".\" +\n parseInt(a.substring(2, 4), 16).toString() +\n \".\" +\n parseInt(a.substring(4, 6), 16).toString() +\n \".\" +\n parseInt(a.substring(6, 8), 16).toString()\n );\n}\nassert(octetStringToIpAddress(\"c07b9179\") === \"192.123.145.121\");\n\n/**\n * Result of {@link CertificateAuthority.initializeCSR}.\n *\n * - `\"ready\"` — the CA certificate already exists and is valid.\n * - `\"pending\"` — key + CSR exist but no cert; waiting for external signing.\n * - `\"created\"` — a fresh key + CSR were just generated.\n * - `\"expired\"` — the CA certificate has expired (or will expire within\n * the configured threshold). A new CSR has been generated for renewal\n * while preserving the existing private key.\n */\nexport type InitializeCSRResult =\n | { status: \"ready\" }\n | { status: \"pending\"; csrPath: string }\n | { status: \"created\"; csrPath: string }\n | { status: \"expired\"; csrPath: string; expiryDate: Date };\n\n/**\n * Result of {@link CertificateAuthority.installCACertificate}.\n *\n * - `\"success\"` — the certificate was installed and CRL generated.\n * - `\"error\"` — the certificate was rejected (see `reason`).\n */\nexport type InstallCACertificateResult = { status: \"success\" } | { status: \"error\"; reason: string; message: string };\n\n/**\n * Options for creating a {@link CertificateAuthority}.\n */\nexport interface CertificateAuthorityCoreOptions {\n /** RSA key size for the CA private key. */\n keySize: KeySize;\n /** Filesystem path where the CA directory structure is stored. */\n location: string;\n /**\n * X.500 subject for the CA certificate.\n * Accepts a slash-delimited string (e.g. `\"/CN=My CA/O=Acme\"`) or\n * a structured {@link SubjectOptions} object.\n *\n * @defaultValue {@link defaultSubject}\n */\n subject?: string | SubjectOptions;\n /**\n * Parent CA that will sign this CA's certificate.\n * If omitted, the CA is self-signed (root CA).\n * The parent CA must be initialized before this CA.\n */\n issuerCA?: CertificateAuthorityCore;\n /**\n * Public URL (http/https) where the CRL produced by this CA is\n * reachable. When set, every issued certificate carries an\n * X.509v3 `crlDistributionPoints` extension pointing at this URL.\n *\n * Leave undefined to omit the extension entirely (opt-in — see\n * US-202). Validated synchronously at construction / setter call.\n */\n crlDistributionUrl?: string;\n /**\n * Public URL of the OCSP responder. When set, every issued cert\n * carries an `authorityInfoAccess` extension with an `OCSP` leg\n * pointing at this URL. Leave undefined to omit (US-202).\n */\n ocspResponderUrl?: string;\n /**\n * Public URL where the issuer's certificate can be fetched.\n * When set, the `authorityInfoAccess` extension on every issued\n * cert carries a `caIssuers` leg pointing at this URL (chain\n * repair). Leave undefined to omit (US-202).\n */\n caIssuersUrl?: string;\n /**\n * Encrypt the CA private key (`private/cakey.pem`) at rest with this\n * passphrase (opt-in, default off). When set:\n * - a freshly generated CA key is written as encrypted PKCS#8;\n * - an existing *plaintext* CA key is encrypted in place by\n * {@link CertificateAuthority.initialize} / `initializeCSR()`;\n * - every `openssl` invocation that loads the CA key (CSR, self-sign,\n * sign, revoke, CRL generation) receives it via `-passin env:`, never\n * in argv; a missing or wrong passphrase fails `initialize()` closed.\n *\n * A function is called at most once per instance; the resolved\n * passphrase is kept in memory for the instance's lifetime because the\n * CA hands it to each openssl child. Never logged.\n *\n * A subordinate CA (`issuerCA` set) uses the *issuer's* passphrase for\n * the issuer's key: construct the parent with its own option.\n */\n privateKeyPassphrase?: PrivateKeyPassphrase;\n /**\n * Who actually signs, and writes the certificate database. Supplying it\n * is the whole point of this class: nothing here names a concrete\n * backend, so a program that only ever uses the native one never\n * references - and never has to ship - the openssl one.\n *\n * {@link CertificateAuthority} is the batteries-included subclass that\n * picks a backend for you from a `\"openssl\" | \"native\"` option.\n */\n backend: CaBackend;\n /**\n * Sign with an external key that never leaves its holder - an HSM or a\n * cloud KMS - instead of `private/cakey.pem`. The signer exposes only\n * `getPublicKey()` and `sign(tbs)`; this class builds the to-be-signed\n * bytes locally and asks the signer for the signature.\n *\n * Setting this implies `backend: \"native\"` (the openssl CLI can only\n * load a key from a file, so it cannot honour a signer); combining it\n * with an explicit `backend: \"openssl\"` is a constructor error. No\n * `private/cakey.pem` is generated, and the key-file operations that\n * cannot apply - {@link CertificateAuthority.getPrivateKey} and\n * {@link CertificateAuthority.reencryptPrivateKey} - throw.\n */\n signer?: CaSigner;\n}\n\n/**\n * An OpenSSL-based Certificate Authority (CA) that can create,\n * sign, and revoke X.509 certificates.\n *\n * The CA maintains a standard OpenSSL directory layout under\n * {@link CertificateAuthority.rootDir | rootDir}:\n *\n * ```\n * <location>/\n * ├── conf/ OpenSSL configuration\n * ├── private/ CA private key (cakey.pem)\n * ├── public/ CA certificate (cacert.pem)\n * ├── certs/ Signed certificates\n * ├── crl/ Revocation lists\n * ├── serial Next serial number\n * ├── crlnumber Next CRL number\n * └── index.txt Certificate database\n * ```\n *\n * @example\n * ```ts\n * const ca = new CertificateAuthority({\n * keySize: 2048,\n * location: \"/var/pki/CA\"\n * });\n * await ca.initialize();\n * ```\n */\n\n// ---------------------------------------------------------------\n// Certificate database types (US-057)\n// ---------------------------------------------------------------\n\nexport type { IssuedCertificateRecord };\n\n/**\n * Options for {@link CertificateAuthority.signCertificateRequestFromDER}.\n *\n * All fields are optional. When provided, they override the\n * corresponding values from the CSR.\n */\nexport interface SignCertificateOptions {\n /** Certificate validity in days (default: 365). */\n validity?: number;\n /**\n * Certificate validity in milliseconds.\n *\n * When provided, takes precedence over {@link validity} and enables\n * sub-day validity (e.g. 10-minute certificates for renewal demos).\n */\n validityMs?: number;\n /** Override the certificate start date. */\n startDate?: Date;\n /** Override DNS SANs. */\n dns?: string[];\n /** Override IP SANs. */\n ip?: string[];\n /** Override the application URI SAN. */\n applicationUri?: string;\n /** Override the X.500 subject. */\n subject?: SubjectOptions | string;\n}\n\n/**\n * Capabilities advertised by a PKI backend (or by this\n * {@link CertificateAuthority}) so consumers can clamp requested\n * validity to the limits the backend can actually honor.\n *\n * Useful for the GDS Pull / Push management flows, where the CA may\n * be supplied by an external service (step-ca, EJBCA, …) with its\n * own minimum / maximum / granularity constraints.\n *\n * @see CertificateAuthority.getCapabilities\n */\nexport interface PkiBackendCapabilities {\n /** Smallest validity this backend can issue, in milliseconds. */\n minValidityMs: number;\n /** Largest validity this backend will issue, in milliseconds. */\n maxValidityMs: number;\n /**\n * Validity is rounded up to the nearest multiple of this many\n * milliseconds. For `node-opcua-pki`'s OpenSSL-based CA this is\n * 1 000 ms (one second — the X.509 floor per RFC 5280 §4.1.2.5).\n */\n validityGranularityMs: number;\n /**\n * Native unit the backend works in. Diagnostic only — callers\n * always pass `validityMs` (US-208 / US-210).\n */\n nativeUnit: \"second\" | \"minute\" | \"hour\" | \"day\";\n}\n\n/**\n * Options for {@link CertificateAuthority.generateKeyPairAndSignDER}.\n */\nexport interface GenerateKeyPairAndSignOptions {\n /** OPC UA application URI (required). */\n applicationUri: string;\n /** X.500 subject for the certificate (e.g. \"CN=MyApp\"). */\n subject?: SubjectOptions | string;\n /** DNS host names for the SAN extension. */\n dns?: string[];\n /** IP addresses for the SAN extension. */\n ip?: string[];\n /** Certificate validity in days (default: 365). */\n validity?: number;\n /**\n * Certificate validity in milliseconds.\n *\n * When provided, takes precedence over {@link validity} and enables\n * sub-day validity (e.g. 10-minute certificates for renewal demos).\n */\n validityMs?: number;\n /** Certificate start date (default: now). */\n startDate?: Date;\n /** RSA key size in bits (default: 2048). */\n keySize?: KeySize;\n}\n\n/**\n * Options for {@link CertificateAuthority.generateKeyPairAndSignPFX}.\n *\n * Extends the DER options with an optional `passphrase` to protect\n * the PFX bundle.\n */\nexport interface GenerateKeyPairAndSignPFXOptions extends GenerateKeyPairAndSignOptions {\n /**\n * Passphrase to protect the PFX file.\n * If omitted, the PFX is created without a password.\n */\n passphrase?: string;\n}\n\n/**\n * Synchronously validate a revocation-related URL (CDP / OCSP /\n * caIssuers) before it is stored on a {@link CertificateAuthority}.\n *\n * Rules:\n * - `undefined` is a valid input — the matching extension is omitted.\n * - Empty string throws (almost always a config bug — pass `undefined`).\n * - Must parse via `new URL(s)`.\n * - Protocol must be `http:` or `https:`.\n * - Must include a non-trivial path (not `\"\"` or `\"/\"`).\n * - Loopback hostname produces a warning but does not throw — useful\n * for tests and local dev where pki-server and relying party share\n * a host.\n *\n * @see US-202\n */\nfunction validateRevocationUrl(url: string | undefined, fieldName: string): string | undefined {\n if (url === undefined) {\n return undefined;\n }\n if (url === \"\") {\n throw new Error(`${fieldName} must not be empty — pass undefined to disable the extension`);\n }\n let parsed: URL;\n try {\n parsed = new URL(url);\n } catch {\n throw new Error(`${fieldName} is not a valid URL: ${url}`);\n }\n if (parsed.protocol !== \"http:\" && parsed.protocol !== \"https:\") {\n throw new Error(`${fieldName} must use http: or https: (got ${parsed.protocol} in ${url})`);\n }\n if (!parsed.pathname || parsed.pathname === \"/\") {\n throw new Error(`${fieldName} must include a path component (got ${url})`);\n }\n const isLoopback = parsed.hostname === \"localhost\" || parsed.hostname === \"::1\" || parsed.hostname.startsWith(\"127.\");\n if (isLoopback) {\n console.warn(\n `[node-opcua-pki] ${fieldName} points at loopback (${url}) — ` +\n \"certificates issued with this URL will be unreachable from any other host.\"\n );\n }\n return url;\n}\n\nexport class CertificateAuthorityCore {\n /** RSA key size used when generating the CA private key. */\n public readonly keySize: KeySize;\n /** Root filesystem path of the CA directory structure. */\n public readonly location: string;\n /** X.500 subject of the CA certificate. */\n public readonly subject: Subject;\n\n /** @internal Parent CA (undefined for root CAs). */\n readonly _issuerCA?: CertificateAuthorityCore;\n\n /** @internal Configured CDP / AIA URLs (US-202). */\n private _crlDistributionUrl?: string;\n private _ocspResponderUrl?: string;\n private _caIssuersUrl?: string;\n\n readonly #privateKeyPassphrase?: PrivateKeyPassphrase;\n /** resolved once (see `privateKeyPassphrase`); `#passphraseResolved` distinguishes \"none\" from \"not yet\" */\n #resolvedPassphrase?: string;\n #passphraseResolved = false;\n\n /** Signing backend: `openssl` shells out to the CLI, `native` signs in-process. */\n readonly #backend: CaBackend;\n /** External signing key (HSM/KMS), when one was supplied instead of a key file. */\n readonly #signer?: CaSigner;\n /** Read access to `index.txt` / `certs/<SERIAL>.pem`. */\n readonly #db: CaDatabase;\n\n constructor(options: CertificateAuthorityCoreOptions) {\n assert(Object.prototype.hasOwnProperty.call(options, \"location\"));\n assert(Object.prototype.hasOwnProperty.call(options, \"keySize\"));\n this.location = options.location;\n this.keySize = options.keySize || 2048;\n this.subject = new Subject(options.subject || defaultSubject);\n this._issuerCA = options.issuerCA;\n this.#privateKeyPassphrase = options.privateKeyPassphrase;\n this.#signer = options.signer;\n if (options.signer) {\n // Refuse a signer nothing can use here, where the message can say\n // what is wrong with it, rather than letting it surface as a\n // normalize-algorithm error thrown from inside WebCrypto once the\n // CA directory already exists. The type system says as much for\n // TypeScript callers, but a signer is exactly the sort of object\n // that arrives from untyped glue around a KMS SDK.\n const algorithm = options.signer.algorithm;\n if (algorithm.name !== \"RSASSA-PKCS1-v1_5\" && algorithm.name !== \"ECDSA\") {\n throw new Error(\n `CertificateAuthority: signer algorithm ${(algorithm as { name: string }).name} is not supported - ` +\n \"use RSASSA-PKCS1-v1_5 or ECDSA.\"\n );\n }\n if (algorithm.name === \"ECDSA\" && !algorithm.namedCurve) {\n throw new Error(\n \"CertificateAuthority: an ECDSA signer must declare its namedCurve (P-256, P-384 or P-521) - \" +\n \"importing the signer's public key needs the curve, and an SPKI import cannot infer it.\"\n );\n }\n }\n if (options.signer && !options.backend.supportsExternalSigner) {\n // asked of the backend rather than hardcoded here, so this class\n // keeps working for backends it has never heard of\n throw new Error(\n \"CertificateAuthority: this backend cannot sign with an external signer - \" +\n \"it loads its key from a file. Use a backend that supports one, such as NativeCaBackend.\"\n );\n }\n this.#backend = options.backend;\n this.#db = new CaDatabase(this.location);\n if (options.crlDistributionUrl !== undefined) {\n this.setCrlDistributionUrl(options.crlDistributionUrl);\n }\n if (options.ocspResponderUrl !== undefined) {\n this.setOcspResponderUrl(options.ocspResponderUrl);\n }\n if (options.caIssuersUrl !== undefined) {\n this.setCaIssuersUrl(options.caIssuersUrl);\n }\n }\n\n /**\n * Public URL where the CRL produced by this CA is reachable, or\n * `undefined` if no CDP extension should be emitted on issued certs.\n */\n public get crlDistributionUrl(): string | undefined {\n return this._crlDistributionUrl;\n }\n\n /**\n * Public URL of the OCSP responder, or `undefined` if no AIA OCSP\n * leg should be emitted on issued certs.\n */\n public get ocspResponderUrl(): string | undefined {\n return this._ocspResponderUrl;\n }\n\n /**\n * Public URL where the issuer's certificate can be fetched, or\n * `undefined` if no AIA caIssuers leg should be emitted.\n */\n public get caIssuersUrl(): string | undefined {\n return this._caIssuersUrl;\n }\n\n /**\n * Configure the URL embedded as `crlDistributionPoints` in every\n * subsequently-issued certificate. Pass `undefined` to disable\n * the extension entirely. Validated synchronously — throws on\n * empty string, non-http(s) protocol, missing path. Warns (does\n * not throw) when the URL points at loopback.\n *\n * @see US-202\n */\n public setCrlDistributionUrl(url: string | undefined): void {\n this._crlDistributionUrl = validateRevocationUrl(url, \"crlDistributionUrl\");\n }\n\n /**\n * Configure the OCSP responder URL embedded as the `OCSP` leg of\n * the `authorityInfoAccess` extension on every subsequently-issued\n * certificate. Pass `undefined` to disable.\n *\n * @see US-202\n */\n public setOcspResponderUrl(url: string | undefined): void {\n this._ocspResponderUrl = validateRevocationUrl(url, \"ocspResponderUrl\");\n }\n\n /**\n * Configure the caIssuers URL embedded as the `caIssuers` leg of\n * the `authorityInfoAccess` extension on every subsequently-issued\n * certificate. Pass `undefined` to disable.\n *\n * @see US-202\n */\n public setCaIssuersUrl(url: string | undefined): void {\n this._caIssuersUrl = validateRevocationUrl(url, \"caIssuersUrl\");\n }\n\n /** Absolute path to the CA root directory (alias for {@link location}). */\n public get rootDir() {\n return this.location;\n }\n\n /** Path to the OpenSSL configuration file (`conf/caconfig.cnf`). */\n public get configFile() {\n return path.normalize(path.join(this.rootDir, \"./conf/caconfig.cnf\"));\n }\n\n /** Path to the CA private key (`private/cakey.pem`); may be passphrase-encrypted, see {@link getPrivateKey}. */\n public get privateKey() {\n return path.join(path.resolve(this.rootDir), \"private/cakey.pem\");\n }\n\n /**\n * The CA private key, decrypted with the configured `privateKeyPassphrase`\n * if it is encrypted. Fails closed (`PrivateKeyPassphraseRequiredError`)\n * on an encrypted key with no or the wrong passphrase.\n */\n public async getPrivateKey(): Promise<PrivateKey> {\n this.#assertKeyIsOnDisk(\"getPrivateKey\");\n return readPrivateKey(this.privateKey, await this._privateKeyPassphrase());\n }\n\n /**\n * True when this CA signs with an external {@link CaSigner} (HSM/KMS)\n * rather than with `private/cakey.pem`. There is then no private key\n * file on disk, and never was one.\n */\n public get hasExternalSigner(): boolean {\n return this.#signer !== undefined;\n }\n\n /**\n * Does `cert` certify the key this CA signs with? With the key on disk\n * that is a private-key match; with a signer there is no private key to\n * match, so compare the certified public key against the signer's own -\n * the same question, asked the only way an HSM allows.\n */\n async #certificateMatchesOurKey(certPem: string, certDer: Buffer): Promise<boolean> {\n if (this.#signer) {\n const ours = Buffer.from(await this.#signer.getPublicKey());\n const certified = Buffer.from(new x509.X509Certificate(certPem).publicKey.rawData);\n return ours.equals(certified);\n }\n return certificateMatchesPrivateKey(certDer, await this.getPrivateKey());\n }\n\n /** Reject the key-file-only operations up front, rather than failing on a missing file later. */\n #assertKeyIsOnDisk(what: string): void {\n if (this.#signer) {\n throw new Error(\n `CertificateAuthority.${what} is not available on a signer-backed CA: ` +\n \"the private key lives in the signer (HSM/KMS) and is never written to disk.\"\n );\n }\n }\n\n /**\n * @internal The key every signing operation goes through. An injected\n * {@link CaSigner} is returned as-is; otherwise the on-disk key is read\n * (and decrypted) and handed over as a plain `CryptoKey`. Both are\n * accepted by the `node-opcua-crypto` signing primitives, so callers\n * never branch on which one they got.\n */\n public async _getSigningKey(): Promise<CryptoKey | CaSigner> {\n if (this.#signer) {\n return this.#signer;\n }\n return privateKeyToCryptoKey(await this.getPrivateKey());\n }\n\n /**\n * Enable, disable, or rotate the passphrase protecting `private/cakey.pem`\n * (temp file + atomic rename, temp file removed on failure). Only\n * rewrites the file: construct a new `CertificateAuthority` with the new\n * passphrase to continue using it.\n */\n public async reencryptPrivateKey(oldPassphrase?: PrivateKeyPassphrase, newPassphrase?: PrivateKeyPassphrase): Promise<void> {\n this.#assertKeyIsOnDisk(\"reencryptPrivateKey\");\n const oldPass = await resolvePrivateKeyPassphrase(oldPassphrase);\n const newPass = await resolvePrivateKeyPassphrase(newPassphrase);\n const key = readPrivateKey(this.privateKey, oldPass);\n await this.#rewritePrivateKeyFile(key, newPass);\n }\n\n async #rewritePrivateKeyFile(privateKey: PrivateKey, passphrase: string | undefined): Promise<void> {\n const tmpFilename = `${this.privateKey}.${process.pid}-${Date.now()}.tmp`;\n try {\n await writePrivateKeyFile(tmpFilename, privateKey, { passphrase });\n await fs.promises.rename(tmpFilename, this.privateKey);\n } finally {\n await fs.promises.rm(tmpFilename, { force: true });\n }\n }\n\n /** @internal resolve the configured passphrase, at most once per instance */\n public async _privateKeyPassphrase(): Promise<string | undefined> {\n if (!this.#passphraseResolved) {\n this.#resolvedPassphrase = await resolvePrivateKeyPassphrase(this.#privateKeyPassphrase);\n this.#passphraseResolved = true;\n }\n return this.#resolvedPassphrase;\n }\n\n /**\n * @internal On an existing key: encrypt it in place if a passphrase is\n * configured and it is still plaintext (secure by default: the option\n * means \"protect this key\", not \"ignore me\"), then read it back so a\n * wrong or missing passphrase fails initialize() closed rather than the\n * first signing operation.\n */\n public async _ensurePrivateKeyProtection(): Promise<void> {\n if (this.#signer || !fs.existsSync(this.privateKey)) {\n return;\n }\n if (this.#privateKeyPassphrase !== undefined && !isEncryptedPrivateKeyFile(this.privateKey)) {\n warningLog(\"CertificateAuthority: private key is plaintext but a passphrase is configured; encrypting it in place\");\n const plaintextKey = readPrivateKey(this.privateKey);\n await this.#rewritePrivateKeyFile(plaintextKey, await this._privateKeyPassphrase());\n }\n await this.getPrivateKey();\n }\n\n /**\n * Acquire a file-based lock on this CA's directory for the duration of\n * `action` — serializes every operation that mutates the certificate\n * database (`index.txt`, `serial`, `crlnumber`, `certs/`,\n * `crl/revocation_list.*`) or the CA's own key/certificate, so\n * concurrent calls on one `CertificateAuthority` (or two instances\n * pointed at the same directory, in this or another process) cannot\n * interleave. Mirrors `CertificateManager.withLock2`.\n *\n * Only ever call this from a top-level public operation — nested calls\n * on the same instance would deadlock, since the underlying file lock\n * is not reentrant.\n *\n * The wait is bounded: a waiter gives up (throws) after\n * `CA_LOCK_MAX_WAIT_MS`. Without a bound, a holder whose openssl child\n * hangs would block every CA operation in every process forever and\n * silently — the lock library's keepalive keeps refreshing the lock\n * file's mtime as long as the holder process is alive, so stale-lock\n * recovery never fires for a live-but-stuck holder. A dead holder's\n * lock goes stale (default 2 minutes) and is taken over well within\n * this bound.\n */\n async #withCaLock<T>(action: () => Promise<T>): Promise<T> {\n const lockFileName = path.join(this.rootDir, \".ca.lock\");\n return withLock<T>(\n {\n fileToLock: lockFileName,\n retries: { forever: true, maxRetryTime: CA_LOCK_MAX_WAIT_MS, ...CA_LOCK_RETRY }\n },\n action\n );\n }\n\n /**\n * @internal Run `action` under this CA's directory lock. For the one\n * legitimate cross-instance use: a subordinate CA's bootstrap signs its\n * certificate with `openssl x509 -CAserial`, which read-increment-writes\n * THIS issuer's `serial` file, and must therefore hold this issuer's\n * lock (ordering is always subordinate -> issuer, so no cycle).\n */\n public async _withCaDirectoryLock<T>(action: () => Promise<T>): Promise<T> {\n return this.#withCaLock(action);\n }\n\n /** Path to the CA certificate in PEM format (`public/cacert.pem`). */\n public get caCertificate() {\n // the Certificate Authority Certificate\n return makePath(this.rootDir, \"./public/cacert.pem\");\n }\n\n /**\n * Path to the issuer certificate chain (`public/issuer_chain.pem`).\n *\n * This file is created by {@link installCACertificate} when the\n * provided cert file contains additional issuer certificates\n * (e.g. intermediate + root). It is appended to signed certs\n * by {@link constructCertificateChain} to produce a full chain\n * per OPC UA Part 6 §6.2.6.\n */\n public get issuerCertificateChain() {\n return makePath(this.rootDir, \"./public/issuer_chain.pem\");\n }\n\n /**\n * Path to the current Certificate Revocation List in DER format.\n * (`crl/revocation_list.der`)\n */\n public get revocationListDER() {\n return makePath(this.rootDir, \"./crl/revocation_list.der\");\n }\n\n /**\n * Path to the current Certificate Revocation List in PEM format.\n * (`crl/revocation_list.crl`)\n */\n public get revocationList() {\n return makePath(this.rootDir, \"./crl/revocation_list.crl\");\n }\n\n /**\n * Path to the concatenated CA certificate + CRL file.\n * Used by OpenSSL for CRL-based verification.\n */\n public get caCertificateWithCrl() {\n return makePath(this.rootDir, \"./public/cacertificate_with_crl.pem\");\n }\n\n // ---------------------------------------------------------------\n // Buffer-based accessors (US-059)\n // ---------------------------------------------------------------\n\n /**\n * Return the CA certificate as a DER-encoded buffer.\n *\n * @throws if the CA certificate file does not exist\n * (call {@link initialize} first).\n */\n public getCACertificateDER(): Buffer {\n const pem = readCertificatePEM(this.caCertificate);\n return convertPEMtoDER(pem);\n }\n\n /**\n * Return the CA certificate as a PEM-encoded string.\n *\n * @throws if the CA certificate file does not exist\n * (call {@link initialize} first).\n */\n public getCACertificatePEM(): string {\n const raw = readCertificatePEM(this.caCertificate);\n // OpenSSL CA cert files may include a human-readable text\n // dump before the PEM block — strip it.\n const beginMarker = \"-----BEGIN CERTIFICATE-----\";\n const idx = raw.indexOf(beginMarker);\n if (idx > 0) {\n return raw.substring(idx);\n }\n return raw;\n }\n\n /**\n * Return the current Certificate Revocation List as a\n * DER-encoded buffer.\n *\n * Returns an empty buffer if no CRL has been generated yet.\n */\n public getCRLDER(): Buffer {\n const crlPath = this.revocationListDER;\n if (!fs.existsSync(crlPath)) {\n return Buffer.alloc(0);\n }\n return fs.readFileSync(crlPath);\n }\n\n /**\n * Return the current Certificate Revocation List as a\n * PEM-encoded string.\n *\n * Returns an empty string if no CRL has been generated yet.\n */\n public getCRLPEM(): string {\n const crlPath = this.revocationList;\n if (!fs.existsSync(crlPath)) {\n return \"\";\n }\n const raw = fs.readFileSync(crlPath, \"utf-8\");\n // OpenSSL CRL files may include a human-readable text\n // dump before the PEM block — strip it.\n const beginMarker = \"-----BEGIN X509 CRL-----\";\n const idx = raw.indexOf(beginMarker);\n if (idx > 0) {\n return raw.substring(idx);\n }\n return raw;\n }\n\n // ---------------------------------------------------------------\n // Certificate database API (US-057)\n // ---------------------------------------------------------------\n\n /**\n * Return a list of all issued certificates recorded in the\n * OpenSSL `index.txt` database.\n *\n * Each entry includes the serial number, subject, status,\n * expiry date, and (for revoked certs) the revocation date.\n */\n public getIssuedCertificates(): IssuedCertificateRecord[] {\n return this.#db.readIndex();\n }\n\n /**\n * Return the total number of certificates recorded in\n * `index.txt`.\n */\n public getIssuedCertificateCount(): number {\n return this.#db.readIndex().length;\n }\n\n /**\n * Return the status of a certificate by its serial number.\n *\n * @param serial - hex-encoded serial number (e.g. `\"1000\"`)\n * @returns `\"valid\"`, `\"revoked\"`, `\"expired\"`, or\n * `undefined` if not found\n */\n public getCertificateStatus(serial: string): \"valid\" | \"revoked\" | \"expired\" | undefined {\n return this.#db.findBySerial(serial)?.status;\n }\n\n /**\n * Read a specific issued certificate by serial number and\n * return its content as a DER-encoded buffer.\n *\n * OpenSSL stores signed certificates in the `certs/`\n * directory using the naming convention `<SERIAL>.pem`.\n *\n * @param serial - hex-encoded serial number (e.g. `\"1000\"`)\n * @returns the DER buffer, or `undefined` if not found\n */\n public getCertificateBySerial(serial: string): Buffer | undefined {\n return this.#db.getCertificateBySerial(serial);\n }\n\n /**\n * Path to the OpenSSL certificate database file.\n */\n public get indexFile(): string {\n return this.#db.indexFile;\n }\n\n // ---------------------------------------------------------------\n // Buffer-based CA operations (US-058)\n // ---------------------------------------------------------------\n\n /**\n * Sign a DER-encoded Certificate Signing Request and return\n * the signed certificate as a DER buffer.\n *\n * This method handles temp-file creation and cleanup\n * internally so that callers can work with in-memory\n * buffers only.\n *\n * The CA can override fields from the CSR by passing\n * `options.dns`, `options.ip`, `options.applicationUri`,\n * `options.startDate`, or `options.subject`.\n *\n * @param csrDer - the CSR as a DER-encoded buffer\n * @param options - signing options and CA overrides\n * @returns the signed certificate as a DER-encoded buffer\n */\n public async signCertificateRequestFromDER(csrDer: Buffer, options?: SignCertificateOptions): Promise<Buffer> {\n const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), \"pki-sign-\"));\n\n try {\n const csrFile = path.join(tmpDir, \"request.csr\");\n const certFile = path.join(tmpDir, \"certificate.pem\");\n\n // Write CSR as PEM\n const csrPem = toPem(csrDer, \"CERTIFICATE REQUEST\");\n await fs.promises.writeFile(csrFile, csrPem, \"utf-8\");\n\n // Build signing parameters — CA overrides take precedence.\n // validityMs (sub-day capable) overrides validity (days) when\n // both are provided; adjustDate() handles precedence.\n const signingParams: Params = {};\n if (options?.validityMs !== undefined) signingParams.validityMs = options.validityMs;\n else signingParams.validity = options?.validity ?? 365;\n if (options?.startDate) signingParams.startDate = options.startDate;\n if (options?.dns) signingParams.dns = options.dns;\n if (options?.ip) signingParams.ip = options.ip;\n if (options?.applicationUri) signingParams.applicationUri = options.applicationUri;\n if (options?.subject) signingParams.subject = options.subject;\n\n // Delegate to the existing file-based method\n await this.signCertificateRequest(certFile, csrFile, signingParams);\n\n // Read the signed certificate and convert to DER\n const certPem = readCertificatePEM(certFile);\n return convertPEMtoDER(certPem);\n } finally {\n await fs.promises.rm(tmpDir, {\n recursive: true,\n force: true\n });\n }\n }\n\n /**\n * Advertise the validity limits this CA can honor.\n *\n * Consumers (notably the GDS server in [`cert_auth.ts`](https://github.com/sterfive/node-opcua-gds))\n * clamp a requested validity against these bounds before calling\n * {@link signCertificateRequestFromDER}, so a misconfigured\n * `defaultCertValidity` cannot ask the CA for something it cannot\n * produce.\n *\n * Defaults match the OpenSSL-backed implementation:\n * - `minValidityMs = 60_000` (1 minute) — practical floor; the\n * X.509 spec floor is 1 second but very short certs are rarely\n * useful and pathological for any real deployment.\n * - `maxValidityMs = 10 * 365 * 86_400_000` (≈ 10 years) — long\n * enough for root CAs.\n * - `validityGranularityMs = 1_000` (1 second) — RFC 5280 §4.1.2.5\n * floor on `notBefore` / `notAfter`.\n * - `nativeUnit = \"second\"` — what `x509Date()` actually encodes.\n *\n * @see US-208 — the consumer-side capability story.\n */\n public getCapabilities(): PkiBackendCapabilities {\n return {\n minValidityMs: 60_000,\n maxValidityMs: 10 * 365 * 86_400_000,\n validityGranularityMs: 1_000,\n nativeUnit: \"second\"\n };\n }\n\n /**\n * Generate a new RSA key pair, create an internal CSR, sign it\n * with this CA, and return both the certificate and private key\n * as DER-encoded buffers.\n *\n * The private key is **never stored** by the CA — it exists only\n * in a temporary directory that is cleaned up after the operation.\n *\n * This is used by `StartNewKeyPairRequest` (OPC UA Part 12) for\n * constrained devices that cannot generate their own keys.\n *\n * @param options - key generation and certificate parameters\n * @returns `{ certificateDer, privateKey }` — certificate as DER,\n * private key as a branded `PrivateKey` buffer\n */\n /**\n * An ephemeral key and a CSR for it, written into `tmpDir`. Both\n * `generateKeyPairAndSign*` methods need exactly this, and neither\n * needs a subprocess for it: the key comes from node's crypto and the\n * request is built and self-signed in process, so no `openssl.cnf` has\n * to be rendered either.\n */\n async #createEphemeralKeyAndCsr(\n tmpDir: string,\n keySize: KeySize,\n options: { applicationUri: string; subject?: SubjectOptions | string; dns?: string[]; ip?: string[] }\n ): Promise<{ privateKeyFile: string; csrFile: string }> {\n const privateKeyFile = path.join(tmpDir, \"private_key.pem\");\n await generatePrivateKeyFile(privateKeyFile, keySize);\n\n const { csr } = await createCertificateSigningRequest({\n privateKey: await privateKeyToCryptoKey(readPrivateKey(privateKeyFile)),\n subject: options.subject ? new Subject(options.subject).toString() : undefined,\n applicationUri: options.applicationUri,\n dns: options.dns ?? [],\n ip: options.ip ?? [],\n purpose: CertificatePurpose.ForApplication\n });\n const csrFile = path.join(tmpDir, \"request.csr\");\n await fs.promises.writeFile(csrFile, csr);\n return { privateKeyFile, csrFile };\n }\n\n public async generateKeyPairAndSignDER(options: GenerateKeyPairAndSignOptions): Promise<{\n certificateDer: Buffer;\n privateKey: PrivateKey;\n }> {\n const keySize = options.keySize ?? 2048;\n const startDate = options.startDate ?? new Date();\n const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), \"pki-keygen-\"));\n\n try {\n // 1. Ephemeral key + CSR, both in process\n const { privateKeyFile, csrFile } = await this.#createEphemeralKeyAndCsr(tmpDir, keySize, options);\n\n // 4. Sign the CSR with this CA — validityMs takes precedence\n // over validity when both are provided (adjustDate handles it).\n const certFile = path.join(tmpDir, \"certificate.pem\");\n const signingParams: Params = {\n applicationUri: options.applicationUri,\n dns: options.dns,\n ip: options.ip,\n startDate\n };\n if (options.validityMs !== undefined) signingParams.validityMs = options.validityMs;\n else signingParams.validity = options.validity ?? 365;\n await this.signCertificateRequest(certFile, csrFile, signingParams);\n\n // 5. Read results\n const certPem = readCertificatePEM(certFile);\n const certificateDer = convertPEMtoDER(certPem);\n const privateKey = readPrivateKey(privateKeyFile);\n\n return { certificateDer, privateKey };\n } finally {\n // 6. Securely clean up — private key is never persisted\n await fs.promises.rm(tmpDir, {\n recursive: true,\n force: true\n });\n }\n }\n\n /**\n * Generate a new RSA key pair, create an internal CSR, sign it\n * with this CA, and return the result as a PKCS#12 (PFX)\n * buffer bundling the certificate, private key, and CA chain.\n *\n * The private key is **never stored** by the CA — it exists only\n * in a temporary directory that is cleaned up after the operation.\n *\n * @param options - key generation, certificate, and PFX options\n * @returns the PFX as a `Buffer`\n */\n public async generateKeyPairAndSignPFX(options: GenerateKeyPairAndSignPFXOptions): Promise<Buffer> {\n const keySize = options.keySize ?? 2048;\n const startDate = options.startDate ?? new Date();\n const passphrase = options.passphrase ?? \"\";\n const tmpDir = await fs.promises.mkdtemp(path.join(os.tmpdir(), \"pki-keygen-pfx-\"));\n\n try {\n // 1. Ephemeral key + CSR, both in process\n const { privateKeyFile, csrFile } = await this.#createEphemeralKeyAndCsr(tmpDir, keySize, options);\n\n // 4. Sign the CSR with this CA — validityMs takes precedence\n // over validity when both are provided (adjustDate handles it).\n const certFile = path.join(tmpDir, \"certificate.pem\");\n const signingParams: Params = {\n applicationUri: options.applicationUri,\n dns: options.dns,\n ip: options.ip,\n startDate\n };\n if (options.validityMs !== undefined) signingParams.validityMs = options.validityMs;\n else signingParams.validity = options.validity ?? 365;\n await this.signCertificateRequest(certFile, csrFile, signingParams);\n\n // 5. Bundle into PFX. signCertificateRequest leaves the chain in\n // certFile, so the leading block is the certificate and the rest\n // is already this CA's own - no need to read it back separately.\n const blocks = (await fs.promises.readFile(certFile, \"utf-8\")).match(\n /-----BEGIN CERTIFICATE-----[\\s\\S]*?-----END CERTIFICATE-----/g\n );\n if (!blocks || blocks.length === 0) {\n throw new Error(`generateKeyPairAndSignPFX: no certificate was produced in ${certFile}`);\n }\n return await createPfx({\n certificate: convertPEMtoDER(blocks[0]),\n certificateChain: blocks.slice(1).map(convertPEMtoDER),\n privateKey: readPrivateKey(privateKeyFile),\n password: passphrase\n });\n } finally {\n // 7. Securely clean up — private key is never persisted\n await fs.promises.rm(tmpDir, {\n recursive: true,\n force: true\n });\n }\n }\n\n /**\n * Revoke a DER-encoded certificate and regenerate the CRL.\n *\n * Extracts the serial number from the certificate, then\n * uses the stored cert file at `certs/<serial>.pem` for\n * revocation — avoiding temp-file PEM format mismatches.\n *\n * @param certDer - the certificate as a DER-encoded buffer\n * @param reason - CRL reason code\n * (default: `\"keyCompromise\"`)\n * @throws if the certificate's serial is not found in the CA\n */\n public async revokeCertificateDER(certDer: Buffer, reason?: string): Promise<void> {\n // 1. Extract serial from the DER certificate\n const info = exploreCertificate(certDer);\n // exploreCertificate returns serial as \"10:00\" (colon-hex)\n // openssl stores cert files as \"1000.pem\" (plain hex upper)\n const serial = info.tbsCertificate.serialNumber.replace(/:/g, \"\").toUpperCase();\n\n // 2. Use the cert file that openssl ca already stored\n const storedCertFile = path.join(this.rootDir, \"certs\", `${serial}.pem`);\n if (!fs.existsSync(storedCertFile)) {\n throw new Error(`Cannot revoke: no stored certificate found for serial ${serial} at ${storedCertFile}`);\n }\n\n // 3. Delegate to the existing file-based method\n await this.revokeCertificate(storedCertFile, {\n reason: reason ?? \"keyCompromise\"\n });\n }\n\n /**\n * Initialize the CA directory structure, generate the CA\n * private key and self-signed certificate if they do not\n * already exist.\n */\n public async initialize(): Promise<void> {\n // the lock file lives inside rootDir, so rootDir must exist before\n // #withCaLock can even acquire it — on a brand-new CA it doesn't yet\n mkdirRecursiveSync(path.resolve(this.rootDir));\n await this.#withCaLock(() => this.#bootstrap());\n }\n\n /**\n * @internal Shared (backend-agnostic) part of `initialize()`: directory\n * layout, default database files, the \"already initialized\" / \"partial\n * init\" checks, and the openssl config file — then delegates CSR\n * generation, CA-certificate signing, and the initial CRL to\n * {@link CaBackend.bootstrap}. Must be called under {@link #withCaLock}.\n */\n async #bootstrap(): Promise<void> {\n const caRootDir = path.resolve(this.rootDir);\n\n mkdirRecursiveSync(caRootDir);\n ensurePrivateDirectory(path.join(caRootDir, \"private\"));\n mkdirRecursiveSync(path.join(caRootDir, \"public\"));\n mkdirRecursiveSync(path.join(caRootDir, \"certs\"));\n mkdirRecursiveSync(path.join(caRootDir, \"crl\"));\n mkdirRecursiveSync(path.join(caRootDir, \"conf\"));\n\n const serial = path.join(caRootDir, \"serial\");\n if (!fs.existsSync(serial)) {\n await fs.promises.writeFile(serial, \"1000\");\n }\n const crlNumber = path.join(caRootDir, \"crlnumber\");\n if (!fs.existsSync(crlNumber)) {\n await fs.promises.writeFile(crlNumber, \"1000\");\n }\n const indexFile = path.join(caRootDir, \"index.txt\");\n if (!fs.existsSync(indexFile)) {\n await fs.promises.writeFile(indexFile, \"\");\n }\n\n // A signer-backed CA has no key file and never will: the key is held\n // by the HSM/KMS, so it always \"exists\" and is never generated here.\n const signerBacked = this.hasExternalSigner;\n const caKeyExists = signerBacked || fs.existsSync(path.join(caRootDir, \"private/cakey.pem\"));\n const caCertExists = fs.existsSync(path.join(caRootDir, \"public/cacert.pem\"));\n if (caKeyExists && caCertExists && !config.forceCA) {\n // CA is fully initialized => do not overwrite\n if (!signerBacked) {\n // repair permissions on installs created before this hardening\n restrictPrivateFilePermissions(path.join(caRootDir, \"private/cakey.pem\"), 0o600);\n // encrypt a plaintext key in place if a passphrase is configured, and\n // fail closed now if the key cannot be read with the configured one\n await this._ensurePrivateKeyProtection();\n }\n debugLog(\"CA private key and certificate already exist ... skipping\");\n return;\n }\n if (!signerBacked && caKeyExists && !caCertExists) {\n // Partial init: key exists but certificate does not.\n // This can happen when a previous CA creation failed\n // (e.g. OpenSSL 3.5 authorityKeyIdentifier error).\n // Remove the stale key so the CA is rebuilt from scratch.\n debugLog(\"CA private key exists but cacert.pem is missing — rebuilding CA\");\n fs.unlinkSync(path.join(caRootDir, \"private/cakey.pem\"));\n // Also remove the stale CSR if present\n const staleCsr = path.join(caRootDir, \"private/cakey.csr\");\n if (fs.existsSync(staleCsr)) {\n fs.unlinkSync(staleCsr);\n }\n }\n\n displayTitle(\"Create Certificate Authority (CA)\");\n\n const indexFileAttr = path.join(caRootDir, \"index.txt.attr\");\n if (!fs.existsSync(indexFileAttr)) {\n await fs.promises.writeFile(indexFileAttr, \"unique_subject = no\");\n }\n\n const caConfigFile = this.configFile;\n await fs.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));\n\n if (!signerBacked) {\n const privateKeyFilename = path.join(caRootDir, \"private/cakey.pem\");\n\n displayTitle(`Generate the CA private Key - ${this.keySize}`);\n // The first step is to create your RSA Private Key.\n // This key is a 1025,2048,3072 or 2038 bit RSA key which is encrypted using\n // Triple-DES and stored in a PEM format so that it is readable as ASCII text.\n await generatePrivateKeyFile(privateKeyFilename, this.keySize, { passphrase: await this._privateKeyPassphrase() });\n restrictPrivateFilePermissions(privateKeyFilename, 0o600);\n }\n\n // Once the private key is generated a Certificate Signing Request can be generated.\n // The CSR is then used in one of two ways. Ideally, the CSR will be sent to a Certificate Authority, such as\n // Thawte or Verisign who will verify the identity of the requestor and issue a signed certificate.\n // The second option is to self-sign the CSR, which will be demonstrated in the next section\n await this.#backend.bootstrap(this);\n }\n\n /**\n * Initialize the CA directory structure and generate the\n * private key + CSR **without signing**.\n *\n * Use this when the CA certificate will be signed by an\n * external (third-party) root CA. After receiving the signed\n * certificate, call {@link installCACertificate} to complete\n * the setup.\n *\n * **Idempotent / restart-safe:**\n * - If the CA certificate exists and is valid → `{ status: \"ready\" }`\n * - If the CA certificate has expired → `{ status: \"expired\", csrPath, expiryDate }`\n * (a new CSR is generated, preserving the existing private key)\n * - If key + CSR exist but no cert (restart before install) →\n * `{ status: \"pending\", csrPath }` without regenerating\n * - Otherwise → generates key + CSR → `{ status: \"created\", csrPath }`\n *\n * @returns an {@link InitializeCSRResult} describing the CA state\n */\n public async initializeCSR(): Promise<InitializeCSRResult> {\n const caRootDir = path.resolve(this.rootDir);\n\n // the lock file lives inside rootDir, so rootDir must exist before\n // #withCaLock can even acquire it — on a brand-new CA it doesn't yet\n mkdirRecursiveSync(caRootDir);\n // Everything below mutates the CA directory (key generation guarded\n // only by an existsSync check, default database files, the openssl\n // config, the CSR) and must run under the same lock initialize()\n // uses, or two concurrent initializeCSR() calls can both pass the\n // key-existence check and overwrite each other's key after one of\n // them has already derived a CSR from it.\n return this.#withCaLock(() => this.#initializeCSRLocked(caRootDir));\n }\n\n async #initializeCSRLocked(caRootDir: string): Promise<InitializeCSRResult> {\n // Ensure directory structure always exists\n for (const dir of [\"public\", \"certs\", \"crl\", \"conf\"]) {\n mkdirRecursiveSync(path.join(caRootDir, dir));\n }\n ensurePrivateDirectory(path.join(caRootDir, \"private\"));\n\n const caCertFile = this.caCertificate;\n const privateKeyFile = path.join(caRootDir, \"private/cakey.pem\");\n const csrFile = path.join(caRootDir, \"private/cakey.csr\");\n\n // a signer-backed CA holds its key externally: it is always\n // \"available\" and there is no file to generate, protect or permission\n const keyAvailable = this.hasExternalSigner || fs.existsSync(privateKeyFile);\n\n // repair permissions on installs created before this hardening,\n // encrypt in place if a passphrase is configured, fail closed if unreadable\n if (!this.hasExternalSigner && fs.existsSync(privateKeyFile)) {\n restrictPrivateFilePermissions(privateKeyFile, 0o600);\n await this._ensurePrivateKeyProtection();\n }\n\n // ── Case 1: cert already exists ──\n if (fs.existsSync(caCertFile)) {\n // Check if the certificate has expired\n const certDer = convertPEMtoDER(readCertificatePEM(caCertFile));\n const certInfo = exploreCertificate(certDer);\n const notAfter = certInfo.tbsCertificate.validity.notAfter;\n if (notAfter.getTime() < Date.now()) {\n // Certificate has expired — regenerate CSR for renewal\n debugLog(\"CA certificate has expired — generating renewal CSR\");\n await this._generateCSR(caRootDir, privateKeyFile, csrFile);\n return { status: \"expired\", csrPath: csrFile, expiryDate: notAfter };\n }\n debugLog(\"CA certificate already exists and is valid — ready\");\n return { status: \"ready\" };\n }\n\n // ── Case 2: key + CSR exist but no cert → pending state ──\n // (restart between initializeCSR and installCACertificate)\n if (keyAvailable && fs.existsSync(csrFile)) {\n debugLog(\"CA key + CSR already exist — pending external signing\");\n return { status: \"pending\", csrPath: csrFile };\n }\n\n // ── Case 3: fresh setup — generate key + CSR ──\n // Create default files (serial, crlnumber, index.txt)\n const serial = path.join(caRootDir, \"serial\");\n if (!fs.existsSync(serial)) {\n await fs.promises.writeFile(serial, \"1000\");\n }\n const crlNumber = path.join(caRootDir, \"crlnumber\");\n if (!fs.existsSync(crlNumber)) {\n await fs.promises.writeFile(crlNumber, \"1000\");\n }\n const indexFile = path.join(caRootDir, \"index.txt\");\n if (!fs.existsSync(indexFile)) {\n await fs.promises.writeFile(indexFile, \"\");\n }\n const indexFileAttr = path.join(caRootDir, \"index.txt.attr\");\n if (!fs.existsSync(indexFileAttr)) {\n await fs.promises.writeFile(indexFileAttr, \"unique_subject = no\");\n }\n\n // Write OpenSSL config\n const caConfigFile = this.configFile;\n await fs.promises.writeFile(caConfigFile, renderCaConfig(caRootDir));\n\n // Generate private key\n if (!keyAvailable) {\n await generatePrivateKeyFile(privateKeyFile, this.keySize, { passphrase: await this._privateKeyPassphrase() });\n restrictPrivateFilePermissions(privateKeyFile, 0o600);\n }\n\n // Generate CSR\n await this._generateCSR(caRootDir, privateKeyFile, csrFile);\n return { status: \"created\", csrPath: csrFile };\n }\n\n /**\n * Check whether the CA certificate needs renewal and, if so,\n * generate a new CSR for re-signing by the external root CA.\n *\n * Use this while the CA is running to detect upcoming expiry\n * **before** it actually expires. The existing private key is\n * preserved so previously issued certs remain valid.\n *\n * @param thresholdDays - number of days before expiry at which\n * to trigger renewal (default: 30)\n * @returns an {@link InitializeCSRResult} — `\"expired\"` if\n * renewal is needed, `\"ready\"` if the cert is still valid\n */\n public async renewCSR(thresholdDays = 30): Promise<InitializeCSRResult> {\n const caRootDir = path.resolve(this.rootDir);\n const caCertFile = this.caCertificate;\n const privateKeyFile = path.join(caRootDir, \"private/cakey.pem\");\n const csrFile = path.join(caRootDir, \"private/cakey.csr\");\n\n if (!fs.existsSync(caCertFile)) {\n // No cert at all — delegate to initializeCSR\n return this.initializeCSR();\n }\n\n const certDer = convertPEMtoDER(readCertificatePEM(caCertFile));\n const certInfo = exploreCertificate(certDer);\n const notAfter = certInfo.tbsCertificate.validity.notAfter;\n\n const thresholdMs = thresholdDays * 24 * 60 * 60 * 1000;\n if (notAfter.getTime() - Date.now() < thresholdMs) {\n debugLog(`CA certificate expires within ${thresholdDays} days — generating renewal CSR`);\n await this.#withCaLock(() => this._generateCSR(caRootDir, privateKeyFile, csrFile));\n return { status: \"expired\", csrPath: csrFile, expiryDate: notAfter };\n }\n\n return { status: \"ready\" };\n }\n\n /**\n * Generate a CSR using the existing private key.\n * Must be called under {@link #withCaLock} — the lock is taken by the\n * public callers (`initializeCSR`, `renewCSR`), not here, so that\n * `initializeCSR` can hold one lock across key generation AND CSR\n * generation without the non-reentrant file lock deadlocking.\n * @internal\n */\n private async _generateCSR(caRootDir: string, privateKeyFile: string, csrFile: string): Promise<void> {\n await this.#backend.generateCaCsr(this, caRootDir, privateKeyFile, csrFile);\n }\n\n /**\n * Install an externally-signed CA certificate and generate\n * the initial CRL.\n *\n * Call this after {@link initializeCSR} once the external\n * root CA has signed the CSR.\n *\n * **Safety checks:**\n * - Verifies that the certificate's public key matches the\n * CA private key before installing.\n *\n * @param signedCertFile - path to the PEM-encoded signed\n * CA certificate (issued by the external root CA)\n * @returns an {@link InstallCACertificateResult} with\n * `status: \"success\"` or `status: \"error\"` and a `reason`\n */\n public async installCACertificate(signedCertFile: string): Promise<InstallCACertificateResult> {\n return this.#withCaLock(async () => {\n const caCertFile = this.caCertificate;\n\n // Read the full content once — may contain a chain\n const fullPem = await fs.promises.readFile(signedCertFile, \"utf8\");\n\n // Split PEM blocks: first cert → cacert.pem, rest → issuer_chain.pem\n const pemBlocks = fullPem.match(/-----BEGIN CERTIFICATE-----[\\s\\S]*?-----END CERTIFICATE-----/g);\n if (!pemBlocks || pemBlocks.length === 0) {\n return {\n status: \"error\",\n reason: \"no_certificate_found\",\n message: \"The provided file does not contain any PEM-encoded certificate.\"\n };\n }\n\n // Verify the first certificate really belongs to this CA's key\n const certDer = convertPEMtoDER(pemBlocks[0]);\n if (!(await this.#certificateMatchesOurKey(pemBlocks[0], certDer))) {\n return {\n status: \"error\",\n reason: \"certificate_key_mismatch\",\n message:\n \"The provided certificate does not match the CA \" +\n \"private key. Ensure the certificate was signed \" +\n \"from the CSR generated by initializeCSR().\"\n };\n }\n\n // Write the first cert (the CA cert itself)\n await fs.promises.writeFile(caCertFile, `${pemBlocks[0]}\\n`);\n\n // Write any additional issuer certs to the chain file\n const issuerChainFile = this.issuerCertificateChain;\n if (pemBlocks.length > 1) {\n const issuerPem = `${pemBlocks.slice(1).join(\"\\n\")}\\n`;\n await fs.promises.writeFile(issuerChainFile, issuerPem);\n debugLog(`Stored ${pemBlocks.length - 1} issuer certificate(s) in issuer_chain.pem`);\n } else {\n // No issuer chain — remove stale file if present\n if (fs.existsSync(issuerChainFile)) {\n await fs.promises.unlink(issuerChainFile);\n }\n }\n\n // Generate initial CRL\n await this.#backend.regenerateCrl(this);\n\n return { status: \"success\" };\n });\n }\n\n /**\n * Sign a CSR with CA extensions (`v3_ca`), producing a\n * subordinate CA certificate.\n *\n * Unlike {@link signCertificateRequest} which signs with\n * end-entity extensions (SANs, etc.), this method signs\n * with `basicConstraints = CA:TRUE` and `keyUsage =\n * keyCertSign, cRLSign`.\n *\n * @param certFile - output path for the signed CA cert (PEM)\n * @param csrFile - path to the subordinate CA's CSR\n * @param params - signing parameters\n */\n public async signCACertificateRequest(certFile: string, csrFile: string, params: { validity?: number }): Promise<void> {\n await this.#withCaLock(async () => {\n const validity = params.validity ?? 3650;\n\n await this.#backend.signSubordinateCsr(this, csrFile, certFile, validity);\n\n // Append this CA's cert chain to the output so the caller\n // receives a complete chain file ready for installCACertificate.\n // Chain format: [signedSubordinateCert, thisCA, thisCA's issuers...]\n await this.constructCertificateChain(certFile);\n });\n }\n\n /**\n * Rebuild the combined CA certificate + CRL file.\n *\n * This concatenates the CA certificate with the current\n * revocation list so that OpenSSL can verify certificates\n * with CRL checking enabled.\n */\n public async constructCACertificateWithCRL(): Promise<void> {\n const cacertWithCRL = this.caCertificateWithCrl;\n\n // note : in order to check if the certificate is revoked,\n // you need to specify -crl_check and have both the CA cert and the (applicable) CRL in your trust store.\n // There are two ways to do that:\n // 1. concatenate cacert.pem and crl.pem into one file and use that for -CAfile.\n // 2. use some linked\n // ( from http://security.stackexchange.com/a/58305/59982)\n\n if (fs.existsSync(this.revocationList)) {\n await fs.promises.writeFile(\n cacertWithCRL,\n fs.readFileSync(this.caCertificate, \"utf8\") + fs.readFileSync(this.revocationList, \"utf8\")\n );\n } else {\n // there is no revocation list yet\n await fs.promises.writeFile(cacertWithCRL, fs.readFileSync(this.caCertificate));\n }\n }\n\n /**\n * Append the CA certificate to a signed certificate file,\n * creating a PEM certificate chain.\n *\n * @param certificate - path to the certificate file to extend\n */\n public async constructCertificateChain(certificate: Filename): Promise<void> {\n assert(fs.existsSync(certificate));\n assert(fs.existsSync(this.caCertificate));\n\n debugLog(chalk.yellow(\" certificate file :\"), chalk.cyan(certificate));\n\n // Build chain: cert + this CA cert + issuer chain (if available)\n let chain = await fs.promises.readFile(certificate, \"utf8\");\n chain += await fs.promises.readFile(this.caCertificate, \"utf8\");\n\n // Append the issuer certificate chain (e.g. root CA cert)\n // to produce a complete chain per OPC UA Part 6 §6.2.6\n if (fs.existsSync(this.issuerCertificateChain)) {\n chain += await fs.promises.readFile(this.issuerCertificateChain, \"utf8\");\n }\n\n await fs.promises.writeFile(certificate, chain);\n }\n\n /**\n * Create a self-signed certificate using OpenSSL.\n *\n * @param certificateFile - output path for the signed certificate\n * @param privateKey - path to the private key file\n * @param params - certificate parameters (subject, validity, SANs)\n */\n public async createSelfSignedCertificate(certificateFile: Filename, privateKey: Filename, params: Params): Promise<void> {\n assert(typeof privateKey === \"string\");\n assert(fs.existsSync(privateKey));\n\n if (!certificateFileExist(certificateFile)) {\n return;\n }\n\n adjustDate(params);\n adjustApplicationUri(params);\n params.dns = params.dns || [];\n params.ip = params.ip || [];\n\n await this.#withCaLock(async () => {\n await this.#backend.createSelfSignedCertificate(this, certificateFile, privateKey, params);\n });\n }\n\n /**\n * Regenerate `crl/revocation_list.{crl,der}` from the current database\n * state, without changing any certificate's status. Normally\n * unnecessary — `revokeCertificate` already regenerates the CRL as\n * part of revoking — but useful to force a refresh (e.g. after\n * switching a CA's `backend` between `\"openssl\"` and `\"native\"`).\n */\n public async regenerateCrl(): Promise<void> {\n await this.#withCaLock(async () => {\n await this.#backend.regenerateCrl(this);\n });\n }\n\n /**\n * Revoke a certificate and regenerate the CRL.\n *\n * @param certificate - path to the certificate file to revoke\n * @param params - revocation parameters\n * @param params.reason - CRL reason code\n * (default `\"keyCompromise\"`)\n */\n public async revokeCertificate(certificate: Filename, params: Params): Promise<void> {\n const crlReasons = [\n \"unspecified\",\n \"keyCompromise\",\n \"CACompromise\",\n \"affiliationChanged\",\n \"superseded\",\n \"cessationOfOperation\",\n \"certificateHold\",\n \"removeFromCRL\"\n ];\n\n const reason = params.reason || \"keyCompromise\";\n assert(crlReasons.indexOf(reason) >= 0);\n\n displayTitle(`Revoking certificate ${certificate}`);\n\n await this.#withCaLock(async () => {\n await this.#backend.revoke(this, certificate, reason);\n });\n }\n\n /**\n * Sign a Certificate Signing Request (CSR) with this CA.\n *\n * The signed certificate is written to `certificate`, and the\n * CA certificate chain plus CRL are appended to form a\n * complete certificate chain.\n *\n * @param certificate - output path for the signed certificate\n * @param certificateSigningRequestFilename - path to the CSR\n * @param params1 - signing parameters (validity, dates, SANs)\n * @returns the path to the signed certificate\n */\n public async signCertificateRequest(\n certificate: Filename,\n certificateSigningRequestFilename: Filename,\n params1: Params\n ): Promise<Filename> {\n // ask the backend, do not assume: a native CA needs no openssl at all\n await this.#backend.preflight();\n assert(fs.existsSync(certificateSigningRequestFilename));\n if (!certificateFileExist(certificate)) {\n return \"\";\n }\n adjustDate(params1);\n adjustApplicationUri(params1);\n\n // note :\n // subjectAltName is not copied across\n // see https://github.com/openssl/openssl/issues/10458\n const csr = await readCertificateSigningRequest(certificateSigningRequestFilename);\n const csrInfo = exploreCertificateSigningRequest(csr);\n\n const applicationUri = csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier\n ? csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier[0]\n : undefined;\n if (typeof applicationUri !== \"string\") {\n throw new Error(\"Cannot find applicationUri in CSR\");\n }\n\n const dns = csrInfo.extensionRequest.subjectAltName.dNSName || [];\n let ip = csrInfo.extensionRequest.subjectAltName.iPAddress || [];\n ip = ip.map(octetStringToIpAddress);\n\n const sanOverride: Required<ProcessAltNamesParam> = { applicationUri, dns, ip };\n\n return this.#withCaLock(async () => {\n await this.#backend.signEndEntityCsr(this, certificate, certificateSigningRequestFilename, params1, sanOverride);\n\n displaySubtitle(\"- construct CA certificate with CRL\");\n await this.constructCACertificateWithCRL();\n\n // construct certificate chain\n // concatenate certificate with CA Certificate and revocation list\n displaySubtitle(\"- construct certificate chain\");\n await this.constructCertificateChain(certificate);\n // todo\n displaySubtitle(\"- verify certificate against the root CA\");\n await this.verifyCertificate(certificate);\n\n return certificate;\n });\n }\n\n /**\n * Check that `certificate` really was signed by this CA, and throw if it\n * was not.\n *\n * This used to do nothing: `openssl verify` crashes on Windows, so the\n * check was left as a placeholder. It no longer needs a subprocess -\n * `verifyCertificateSignature` does it in pure JS, the same way\n * {@link CertificateManager} already validates a chain - so the check\n * that `signCertificateRequest` always claimed to perform now actually\n * happens on every issuance, whichever backend did the signing.\n *\n * Only the leading certificate is examined: the file may be a chain,\n * and the rest of it is this CA's own certificate and its issuers.\n *\n * @param certificate - path to the certificate (or chain) to verify\n */\n public async verifyCertificate(certificate: Filename): Promise<void> {\n const pem = await fs.promises.readFile(certificate, \"utf-8\");\n const blocks = pem.match(/-----BEGIN CERTIFICATE-----[\\s\\S]*?-----END CERTIFICATE-----/g);\n if (!blocks || blocks.length === 0) {\n throw new Error(`verifyCertificate: ${certificate} contains no PEM-encoded certificate`);\n }\n const caCertificateDer = convertPEMtoDER(readCertificatePEM(this.caCertificate));\n if (!verifyCertificateSignature(convertPEMtoDER(blocks[0]), caCertificateDer)) {\n throw new Error(\n `verifyCertificate: ${certificate} was not signed by this certificate authority (${this.caCertificate})`\n );\n }\n }\n}\n","const config =\n \"#.........DO NOT MODIFY BY HAND .........................\\n\" +\n \"[ ca ]\\n\" +\n \"default_ca = CA_default\\n\" +\n \"[ CA_default ]\\n\" +\n 'dir = \"%%ROOT_FOLDER%%\" # the main CA folder (quoted: see renderCaConfig)\\n' +\n \"certs = $dir/certs # where to store certificates\\n\" +\n \"new_certs_dir = $dir/certs #\\n\" +\n \"database = $dir/index.txt # the certificate database\\n\" +\n \"serial = $dir/serial # the serial number counter\\n\" +\n \"certificate = $dir/public/cacert.pem # The root CA certificate\\n\" +\n \"private_key = $dir/private/cakey.pem # the CA private key\\n\" +\n \"x509_extensions = usr_cert #\\n\" +\n \"default_days = 3650 # default validity : 10 years\\n\" +\n \"\\n\" +\n \"# default_md = sha1\\n\" +\n \"\\n\" +\n \"default_md = sha256 # The default digest algorithm\\n\" +\n \"\\n\" +\n \"preserve = no\\n\" +\n \"policy = policy_match\\n\" +\n \"# randfile = $dir/random.rnd\\n\" +\n \"# default_startdate = YYMMDDHHMMSSZ\\n\" +\n \"# default_enddate = YYMMDDHHMMSSZ\\n\" +\n \"crl_dir = $dir/crl\\n\" +\n \"crl_extensions = crl_ext\\n\" +\n \"crl = $dir/crl/revocation_list.crl # the Revocation list\\n\" +\n \"crlnumber = $dir/crlnumber # CRL number file\\n\" +\n \"default_crl_days = 30\\n\" +\n \"default_crl_hours = 24\\n\" +\n \"#msie_hack\\n\" +\n \"\\n\" +\n \"[ policy_match ]\\n\" +\n \"countryName = optional\\n\" +\n \"stateOrProvinceName = optional\\n\" +\n \"localityName = optional\\n\" +\n \"organizationName = optional\\n\" +\n \"organizationalUnitName = optional\\n\" +\n \"commonName = optional\\n\" +\n \"emailAddress = optional\\n\" +\n \"\\n\" +\n \"[ req ]\\n\" +\n \"default_bits = 4096 # Size of keys\\n\" +\n \"default_keyfile = key.pem # name of generated keys\\n\" +\n \"distinguished_name = req_distinguished_name\\n\" +\n \"attributes = req_attributes\\n\" +\n \"x509_extensions = v3_ca\\n\" +\n \"#input_password\\n\" +\n \"#output_password\\n\" +\n \"string_mask = nombstr # permitted characters\\n\" +\n \"req_extensions = v3_req\\n\" +\n \"\\n\" +\n \"[ req_distinguished_name ]\\n\" +\n \"\\n\" +\n \"#0 countryName = Country Name (2 letter code)\\n\" +\n \"# countryName_default = FR\\n\" +\n \"# countryName_min = 2\\n\" +\n \"# countryName_max = 2\\n\" +\n \"# stateOrProvinceName = State or Province Name (full name)\\n\" +\n \"# stateOrProvinceName_default = Ile de France\\n\" +\n \"# localityName = Locality Name (city, district)\\n\" +\n \"# localityName_default = Paris\\n\" +\n \"organizationName = Organization Name (company)\\n\" +\n \"organizationName_default = NodeOPCUA\\n\" +\n \"# organizationalUnitName = Organizational Unit Name (department, division)\\n\" +\n \"# organizationalUnitName_default = R&D\\n\" +\n \"commonName = Common Name (hostname, FQDN, IP, or your name)\\n\" +\n \"commonName_max = 256\\n\" +\n \"commonName_default = NodeOPCUA\\n\" +\n \"# emailAddress = Email Address\\n\" +\n \"# emailAddress_max = 40\\n\" +\n \"# emailAddress_default = node-opcua (at) node-opcua (dot) com\\n\" +\n \"\\n\" +\n \"[ req_attributes ]\\n\" +\n \"#challengePassword = A challenge password\\n\" +\n \"#challengePassword_min = 4\\n\" +\n \"#challengePassword_max = 20\\n\" +\n \"#unstructuredName = An optional company name\\n\" +\n \"[ usr_cert ]\\n\" +\n \"basicConstraints = critical, CA:FALSE\\n\" +\n \"subjectKeyIdentifier = hash\\n\" +\n \"authorityKeyIdentifier = keyid,issuer:always\\n\" +\n \"#authorityKeyIdentifier = keyid\\n\" +\n \"subjectAltName = $ENV::ALTNAME\\n\" +\n \"# issuerAltName = issuer:copy\\n\" +\n \"nsComment = ''OpenSSL Generated Certificate''\\n\" +\n \"#nsCertType = client, email, objsign for ''everything including object signing''\\n\" +\n \"#nsCaRevocationUrl = http://www.domain.dom/ca-crl.pem\\n\" +\n \"#nsBaseUrl =\\n\" +\n \"#nsRenewalUrl =\\n\" +\n \"#nsCaPolicyUrl =\\n\" +\n \"#nsSslServerName =\\n\" +\n \"keyUsage = critical, digitalSignature, nonRepudiation,\" +\n \" keyEncipherment, dataEncipherment, keyAgreement\\n\" +\n \"extendedKeyUsage = critical,serverAuth ,clientAuth\\n\" +\n // Opt-in CRL Distribution Points and Authority Information Access.\n // Emitted only when CertificateAuthority.setCrlDistributionUrl /\n // setOcspResponderUrl / setCaIssuersUrl have been called — the\n // conditional blocks are stripped by generateStaticConfig() when\n // the matching env var is unset. See US-202.\n \"{{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL\\n{{/CDP_URL}}\" +\n \"{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE\\n{{/AIA_VALUE}}\" +\n \"\\n\" +\n \"[ v3_req ]\\n\" +\n \"basicConstraints = critical, CA:FALSE\\n\" +\n \"keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement\\n\" +\n \"extendedKeyUsage = critical,serverAuth ,clientAuth\\n\" +\n \"subjectAltName = $ENV::ALTNAME\\n\" +\n 'nsComment = \"CA Generated by Node-OPCUA Certificate utility using openssl\"\\n' +\n \"[ v3_ca_req ]\\n\" +\n // -----------------------------------------------------------------------\n // v3_ca_req is used ONLY during the CSR step (`openssl req -new\n // -extensions v3_ca_req`). At that stage no issuer certificate\n // exists yet (we are bootstrapping the root CA), so we must NOT\n // include `authorityKeyIdentifier` here.\n //\n // OpenSSL 3.5.x (Alpine) rejects *any* authorityKeyIdentifier\n // value during CSR generation — even plain `keyid` — because\n // v3_akid.c unconditionally looks for an issuer certificate:\n //\n // v3_akid.c:156: no issuer certificate\n //\n // The authorityKeyIdentifier is properly added during the\n // self-signing step (`openssl x509 -req -extensions v3_ca`)\n // where the issuer key is available.\n //\n // Tested with:\n // - OpenSSL 3.0.x (Ubuntu) — works\n // - OpenSSL 3.4.1 (Windows) — works\n // - OpenSSL 3.5.5 (Alpine) — works (only with this fix)\n //\n // References:\n // - https://github.com/openssl/openssl/issues/21519\n // - OpenSSL man x509v3_config(5) – authorityKeyIdentifier\n // -----------------------------------------------------------------------\n \"subjectKeyIdentifier = hash\\n\" +\n \"basicConstraints = CA:TRUE\\n\" +\n \"keyUsage = critical, cRLSign, keyCertSign\\n\" +\n \"subjectAltName = $ENV::ALTNAME\\n\" +\n 'nsComment = \"CA CSR generated by Node-OPCUA Certificate utility using openssl\"\\n' +\n \"[ v3_ca ]\\n\" +\n \"subjectKeyIdentifier = hash\\n\" +\n \"authorityKeyIdentifier = keyid:always,issuer:always\\n\" +\n \"basicConstraints = CA:TRUE\\n\" +\n \"keyUsage = critical, cRLSign, keyCertSign\\n\" +\n // OPC UA validators (UaExpert, ANSI compliance tools) flag a missing\n // SubjectAltName on the CA cert. Pull it from the ALTNAME env var\n // populated by construct_CertificateAuthority.\n \"subjectAltName = $ENV::ALTNAME\\n\" +\n 'nsComment = \"CA Certificate generated by Node-OPCUA Certificate utility using openssl\"\\n' +\n \"#nsCertType = sslCA, emailCA\\n\" +\n \"#issuerAltName = issuer:copy\\n\" +\n \"#obj = DER:02:03\\n\" +\n // Opt-in CDP / AIA on the CA cert itself (US-202). Previously a\n // hardcoded http://localhost:8900/crl.pem was emitted unconditionally\n // — which produced broken certs in any deployment outside dev.\n \"{{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL\\n{{/CDP_URL}}\" +\n \"{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE\\n{{/AIA_VALUE}}\" +\n \"[ v3_selfsigned]\\n\" +\n \"basicConstraints = critical, CA:FALSE\\n\" +\n \"keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement\\n\" +\n \"extendedKeyUsage = critical,serverAuth ,clientAuth\\n\" +\n 'nsComment = \"Self-signed certificate, generated by NodeOPCUA\"\\n' +\n \"subjectAltName = $ENV::ALTNAME\\n\" +\n \"\\n\" +\n \"[ crl_ext ]\\n\" +\n \"#issuerAltName = issuer:copy\\n\" +\n \"authorityKeyIdentifier = keyid:always,issuer:always\\n\" +\n \"#authorityInfoAccess = @issuer_info\";\n\nexport default config;\n"],"mappings":"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","names":["fs","path","assert","fs","path","fs","path","assert","chalk","chalk","fs","path","Subject","assert","fs","path","chalk","CertificatePurpose","convertPEMtoDER","createCertificateSigningRequest","privateKeyToCryptoKey","readCertificatePEM","readPrivateKey","Subject","x509","config","assert","Subject","path","readPrivateKey","x509","privateKeyToCryptoKey","fs","readCertificatePEM","convertPEMtoDER","createCertificateSigningRequest","CertificatePurpose","chalk"]}
|