node-opcua-pki 6.14.0 → 6.16.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 +195 -20
- package/dist/bin/pki.mjs.map +1 -1
- package/dist/index.d.mts +152 -1
- package/dist/index.d.ts +152 -1
- package/dist/index.js +195 -20
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +195 -20
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/bin/pki.mjs
CHANGED
|
@@ -248,9 +248,17 @@ function adjustDate(params) {
|
|
|
248
248
|
assert4(params instanceof Object);
|
|
249
249
|
params.startDate = params.startDate || /* @__PURE__ */ new Date();
|
|
250
250
|
assert4(params.startDate instanceof Date);
|
|
251
|
-
params.
|
|
252
|
-
|
|
253
|
-
|
|
251
|
+
if (params.validityMs !== void 0) {
|
|
252
|
+
if (params.validityMs <= 0) {
|
|
253
|
+
throw new RangeError(`validityMs must be > 0 (got ${params.validityMs})`);
|
|
254
|
+
}
|
|
255
|
+
params.endDate = new Date(params.startDate.getTime() + params.validityMs);
|
|
256
|
+
params.validity = Math.ceil(params.validityMs / 864e5);
|
|
257
|
+
} else {
|
|
258
|
+
params.validity = params.validity || 365;
|
|
259
|
+
params.endDate = new Date(params.startDate.getTime());
|
|
260
|
+
params.endDate.setDate(params.startDate.getDate() + params.validity);
|
|
261
|
+
}
|
|
254
262
|
assert4(params.endDate instanceof Date);
|
|
255
263
|
assert4(params.startDate instanceof Date);
|
|
256
264
|
}
|
|
@@ -1944,9 +1952,15 @@ function setEnv(varName, value) {
|
|
|
1944
1952
|
process.env[varName] = value;
|
|
1945
1953
|
}
|
|
1946
1954
|
}
|
|
1955
|
+
function hasEnv(varName) {
|
|
1956
|
+
return Object.prototype.hasOwnProperty.call(exportedEnvVars, varName);
|
|
1957
|
+
}
|
|
1947
1958
|
function getEnv(varName) {
|
|
1948
1959
|
return exportedEnvVars[varName];
|
|
1949
1960
|
}
|
|
1961
|
+
function unsetEnv(varName) {
|
|
1962
|
+
delete exportedEnvVars[varName];
|
|
1963
|
+
}
|
|
1950
1964
|
function getEnvironmentVarNames() {
|
|
1951
1965
|
return Object.keys(exportedEnvVars).map((varName) => {
|
|
1952
1966
|
return { key: varName, pattern: `\\$ENV\\:\\:${varName}` };
|
|
@@ -2416,10 +2430,17 @@ function openssl_require2DigitYearInDate() {
|
|
|
2416
2430
|
}
|
|
2417
2431
|
return g_config.opensslVersion.match(/OpenSSL 0\.9/);
|
|
2418
2432
|
}
|
|
2433
|
+
function stripConditionalBlocks(template) {
|
|
2434
|
+
return template.replace(/\{\{#([A-Z_][A-Z0-9_]*)\}\}([\s\S]*?)\{\{\/\1\}\}\r?\n?/g, (_match, key, content) => {
|
|
2435
|
+
const keep = hasEnv(key) && getEnv(key) !== "";
|
|
2436
|
+
return keep ? content : "";
|
|
2437
|
+
});
|
|
2438
|
+
}
|
|
2419
2439
|
function generateStaticConfig(configPath, options) {
|
|
2420
2440
|
const prePath = options?.cwd || "";
|
|
2421
2441
|
const originalFilename = !path4.isAbsolute(configPath) ? path4.join(prePath, configPath) : configPath;
|
|
2422
2442
|
let staticConfig = fs7.readFileSync(originalFilename, { encoding: "utf8" });
|
|
2443
|
+
staticConfig = stripConditionalBlocks(staticConfig);
|
|
2423
2444
|
for (const envVar of getEnvironmentVarNames()) {
|
|
2424
2445
|
staticConfig = staticConfig.replace(new RegExp(envVar.pattern, "gi"), getEnv(envVar.key));
|
|
2425
2446
|
}
|
|
@@ -2672,7 +2693,9 @@ nsComment = ''OpenSSL Generated Certificate''
|
|
|
2672
2693
|
#nsSslServerName =
|
|
2673
2694
|
keyUsage = critical, digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment, keyAgreement
|
|
2674
2695
|
extendedKeyUsage = critical,serverAuth ,clientAuth
|
|
2675
|
-
|
|
2696
|
+
{{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
|
|
2697
|
+
{{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
|
|
2698
|
+
{{/AIA_VALUE}}
|
|
2676
2699
|
[ v3_req ]
|
|
2677
2700
|
basicConstraints = critical, CA:FALSE
|
|
2678
2701
|
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
|
|
@@ -2695,10 +2718,9 @@ nsComment = "CA Certificate generated by Node-OPCUA Certificate
|
|
|
2695
2718
|
#nsCertType = sslCA, emailCA
|
|
2696
2719
|
#issuerAltName = issuer:copy
|
|
2697
2720
|
#obj = DER:02:03
|
|
2698
|
-
crlDistributionPoints =
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
[ v3_selfsigned]
|
|
2721
|
+
{{#CDP_URL}}crlDistributionPoints = URI:$ENV::CDP_URL
|
|
2722
|
+
{{/CDP_URL}}{{#AIA_VALUE}}authorityInfoAccess = $ENV::AIA_VALUE
|
|
2723
|
+
{{/AIA_VALUE}}[ v3_selfsigned]
|
|
2702
2724
|
basicConstraints = critical, CA:FALSE
|
|
2703
2725
|
keyUsage = nonRepudiation, digitalSignature, keyEncipherment, dataEncipherment, keyAgreement
|
|
2704
2726
|
extendedKeyUsage = critical,serverAuth ,clientAuth
|
|
@@ -2790,6 +2812,7 @@ async function construct_CertificateAuthority(certificateAuthority) {
|
|
|
2790
2812
|
const subjectOpt = ` -subj "${subject.toString()}" `;
|
|
2791
2813
|
const caCommonName = subject.commonName || "NodeOPCUA-CA";
|
|
2792
2814
|
setEnv("ALTNAME", `URI:urn:${caCommonName}`);
|
|
2815
|
+
certificateAuthority._wireRevocationEnvVars();
|
|
2793
2816
|
const options = { cwd: caRootDir };
|
|
2794
2817
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
2795
2818
|
const configOption = ` -config ${q4(n5(configFile))}`;
|
|
@@ -2843,6 +2866,33 @@ function parseOpenSSLDate(dateStr) {
|
|
|
2843
2866
|
const sec = raw.substring(10, 12);
|
|
2844
2867
|
return `${year}-${month}-${day}T${hour}:${min}:${sec}Z`;
|
|
2845
2868
|
}
|
|
2869
|
+
function validateRevocationUrl(url2, fieldName) {
|
|
2870
|
+
if (url2 === void 0) {
|
|
2871
|
+
return void 0;
|
|
2872
|
+
}
|
|
2873
|
+
if (url2 === "") {
|
|
2874
|
+
throw new Error(`${fieldName} must not be empty \u2014 pass undefined to disable the extension`);
|
|
2875
|
+
}
|
|
2876
|
+
let parsed;
|
|
2877
|
+
try {
|
|
2878
|
+
parsed = new URL(url2);
|
|
2879
|
+
} catch {
|
|
2880
|
+
throw new Error(`${fieldName} is not a valid URL: ${url2}`);
|
|
2881
|
+
}
|
|
2882
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
2883
|
+
throw new Error(`${fieldName} must use http: or https: (got ${parsed.protocol} in ${url2})`);
|
|
2884
|
+
}
|
|
2885
|
+
if (!parsed.pathname || parsed.pathname === "/") {
|
|
2886
|
+
throw new Error(`${fieldName} must include a path component (got ${url2})`);
|
|
2887
|
+
}
|
|
2888
|
+
const isLoopback = parsed.hostname === "localhost" || parsed.hostname === "::1" || parsed.hostname.startsWith("127.");
|
|
2889
|
+
if (isLoopback) {
|
|
2890
|
+
console.warn(
|
|
2891
|
+
`[node-opcua-pki] ${fieldName} points at loopback (${url2}) \u2014 certificates issued with this URL will be unreachable from any other host.`
|
|
2892
|
+
);
|
|
2893
|
+
}
|
|
2894
|
+
return url2;
|
|
2895
|
+
}
|
|
2846
2896
|
var defaultSubject, configurationFileTemplate, configurationFileSimpleTemplate2, config3, n5, q4, CertificateAuthority;
|
|
2847
2897
|
var init_certificate_authority = __esm({
|
|
2848
2898
|
"packages/node-opcua-pki/lib/ca/certificate_authority.ts"() {
|
|
@@ -2873,6 +2923,10 @@ var init_certificate_authority = __esm({
|
|
|
2873
2923
|
subject;
|
|
2874
2924
|
/** @internal Parent CA (undefined for root CAs). */
|
|
2875
2925
|
_issuerCA;
|
|
2926
|
+
/** @internal Configured CDP / AIA URLs (US-202). */
|
|
2927
|
+
_crlDistributionUrl;
|
|
2928
|
+
_ocspResponderUrl;
|
|
2929
|
+
_caIssuersUrl;
|
|
2876
2930
|
constructor(options) {
|
|
2877
2931
|
assert10(Object.prototype.hasOwnProperty.call(options, "location"));
|
|
2878
2932
|
assert10(Object.prototype.hasOwnProperty.call(options, "keySize"));
|
|
@@ -2880,6 +2934,93 @@ var init_certificate_authority = __esm({
|
|
|
2880
2934
|
this.keySize = options.keySize || 2048;
|
|
2881
2935
|
this.subject = new Subject4(options.subject || defaultSubject);
|
|
2882
2936
|
this._issuerCA = options.issuerCA;
|
|
2937
|
+
if (options.crlDistributionUrl !== void 0) {
|
|
2938
|
+
this.setCrlDistributionUrl(options.crlDistributionUrl);
|
|
2939
|
+
}
|
|
2940
|
+
if (options.ocspResponderUrl !== void 0) {
|
|
2941
|
+
this.setOcspResponderUrl(options.ocspResponderUrl);
|
|
2942
|
+
}
|
|
2943
|
+
if (options.caIssuersUrl !== void 0) {
|
|
2944
|
+
this.setCaIssuersUrl(options.caIssuersUrl);
|
|
2945
|
+
}
|
|
2946
|
+
}
|
|
2947
|
+
/**
|
|
2948
|
+
* Public URL where the CRL produced by this CA is reachable, or
|
|
2949
|
+
* `undefined` if no CDP extension should be emitted on issued certs.
|
|
2950
|
+
*/
|
|
2951
|
+
get crlDistributionUrl() {
|
|
2952
|
+
return this._crlDistributionUrl;
|
|
2953
|
+
}
|
|
2954
|
+
/**
|
|
2955
|
+
* Public URL of the OCSP responder, or `undefined` if no AIA OCSP
|
|
2956
|
+
* leg should be emitted on issued certs.
|
|
2957
|
+
*/
|
|
2958
|
+
get ocspResponderUrl() {
|
|
2959
|
+
return this._ocspResponderUrl;
|
|
2960
|
+
}
|
|
2961
|
+
/**
|
|
2962
|
+
* Public URL where the issuer's certificate can be fetched, or
|
|
2963
|
+
* `undefined` if no AIA caIssuers leg should be emitted.
|
|
2964
|
+
*/
|
|
2965
|
+
get caIssuersUrl() {
|
|
2966
|
+
return this._caIssuersUrl;
|
|
2967
|
+
}
|
|
2968
|
+
/**
|
|
2969
|
+
* Configure the URL embedded as `crlDistributionPoints` in every
|
|
2970
|
+
* subsequently-issued certificate. Pass `undefined` to disable
|
|
2971
|
+
* the extension entirely. Validated synchronously — throws on
|
|
2972
|
+
* empty string, non-http(s) protocol, missing path. Warns (does
|
|
2973
|
+
* not throw) when the URL points at loopback.
|
|
2974
|
+
*
|
|
2975
|
+
* @see US-202
|
|
2976
|
+
*/
|
|
2977
|
+
setCrlDistributionUrl(url2) {
|
|
2978
|
+
this._crlDistributionUrl = validateRevocationUrl(url2, "crlDistributionUrl");
|
|
2979
|
+
}
|
|
2980
|
+
/**
|
|
2981
|
+
* Configure the OCSP responder URL embedded as the `OCSP` leg of
|
|
2982
|
+
* the `authorityInfoAccess` extension on every subsequently-issued
|
|
2983
|
+
* certificate. Pass `undefined` to disable.
|
|
2984
|
+
*
|
|
2985
|
+
* @see US-202
|
|
2986
|
+
*/
|
|
2987
|
+
setOcspResponderUrl(url2) {
|
|
2988
|
+
this._ocspResponderUrl = validateRevocationUrl(url2, "ocspResponderUrl");
|
|
2989
|
+
}
|
|
2990
|
+
/**
|
|
2991
|
+
* Configure the caIssuers URL embedded as the `caIssuers` leg of
|
|
2992
|
+
* the `authorityInfoAccess` extension on every subsequently-issued
|
|
2993
|
+
* certificate. Pass `undefined` to disable.
|
|
2994
|
+
*
|
|
2995
|
+
* @see US-202
|
|
2996
|
+
*/
|
|
2997
|
+
setCaIssuersUrl(url2) {
|
|
2998
|
+
this._caIssuersUrl = validateRevocationUrl(url2, "caIssuersUrl");
|
|
2999
|
+
}
|
|
3000
|
+
/**
|
|
3001
|
+
* @internal
|
|
3002
|
+
* Populate the OpenSSL config substitution env vars (`CDP_URL` and
|
|
3003
|
+
* `AIA_VALUE`) from the configured URLs, or unset them so the
|
|
3004
|
+
* matching `{{#KEY}}...{{/KEY}}` blocks in the templates are
|
|
3005
|
+
* stripped. MUST be called before every `generateStaticConfig`
|
|
3006
|
+
* invocation that signs a certificate.
|
|
3007
|
+
*/
|
|
3008
|
+
_wireRevocationEnvVars() {
|
|
3009
|
+
unsetEnv("CDP_URL");
|
|
3010
|
+
unsetEnv("AIA_VALUE");
|
|
3011
|
+
if (this._crlDistributionUrl) {
|
|
3012
|
+
setEnv("CDP_URL", this._crlDistributionUrl);
|
|
3013
|
+
}
|
|
3014
|
+
const aiaLegs = [];
|
|
3015
|
+
if (this._ocspResponderUrl) {
|
|
3016
|
+
aiaLegs.push(`OCSP;URI:${this._ocspResponderUrl}`);
|
|
3017
|
+
}
|
|
3018
|
+
if (this._caIssuersUrl) {
|
|
3019
|
+
aiaLegs.push(`caIssuers;URI:${this._caIssuersUrl}`);
|
|
3020
|
+
}
|
|
3021
|
+
if (aiaLegs.length > 0) {
|
|
3022
|
+
setEnv("AIA_VALUE", aiaLegs.join(","));
|
|
3023
|
+
}
|
|
2883
3024
|
}
|
|
2884
3025
|
/** Absolute path to the CA root directory (alias for {@link location}). */
|
|
2885
3026
|
get rootDir() {
|
|
@@ -3126,14 +3267,15 @@ var init_certificate_authority = __esm({
|
|
|
3126
3267
|
* @returns the signed certificate as a DER-encoded buffer
|
|
3127
3268
|
*/
|
|
3128
3269
|
async signCertificateRequestFromDER(csrDer, options) {
|
|
3129
|
-
const validity = options?.validity ?? 365;
|
|
3130
3270
|
const tmpDir = await fs10.promises.mkdtemp(path6.join(os4.tmpdir(), "pki-sign-"));
|
|
3131
3271
|
try {
|
|
3132
3272
|
const csrFile = path6.join(tmpDir, "request.csr");
|
|
3133
3273
|
const certFile = path6.join(tmpDir, "certificate.pem");
|
|
3134
3274
|
const csrPem = toPem2(csrDer, "CERTIFICATE REQUEST");
|
|
3135
3275
|
await fs10.promises.writeFile(csrFile, csrPem, "utf-8");
|
|
3136
|
-
const signingParams = {
|
|
3276
|
+
const signingParams = {};
|
|
3277
|
+
if (options?.validityMs !== void 0) signingParams.validityMs = options.validityMs;
|
|
3278
|
+
else signingParams.validity = options?.validity ?? 365;
|
|
3137
3279
|
if (options?.startDate) signingParams.startDate = options.startDate;
|
|
3138
3280
|
if (options?.dns) signingParams.dns = options.dns;
|
|
3139
3281
|
if (options?.ip) signingParams.ip = options.ip;
|
|
@@ -3149,6 +3291,35 @@ var init_certificate_authority = __esm({
|
|
|
3149
3291
|
});
|
|
3150
3292
|
}
|
|
3151
3293
|
}
|
|
3294
|
+
/**
|
|
3295
|
+
* Advertise the validity limits this CA can honor.
|
|
3296
|
+
*
|
|
3297
|
+
* Consumers (notably the GDS server in [`cert_auth.ts`](https://github.com/sterfive/node-opcua-gds))
|
|
3298
|
+
* clamp a requested validity against these bounds before calling
|
|
3299
|
+
* {@link signCertificateRequestFromDER}, so a misconfigured
|
|
3300
|
+
* `defaultCertValidity` cannot ask the CA for something it cannot
|
|
3301
|
+
* produce.
|
|
3302
|
+
*
|
|
3303
|
+
* Defaults match the OpenSSL-backed implementation:
|
|
3304
|
+
* - `minValidityMs = 60_000` (1 minute) — practical floor; the
|
|
3305
|
+
* X.509 spec floor is 1 second but very short certs are rarely
|
|
3306
|
+
* useful and pathological for any real deployment.
|
|
3307
|
+
* - `maxValidityMs = 10 * 365 * 86_400_000` (≈ 10 years) — long
|
|
3308
|
+
* enough for root CAs.
|
|
3309
|
+
* - `validityGranularityMs = 1_000` (1 second) — RFC 5280 §4.1.2.5
|
|
3310
|
+
* floor on `notBefore` / `notAfter`.
|
|
3311
|
+
* - `nativeUnit = "second"` — what `x509Date()` actually encodes.
|
|
3312
|
+
*
|
|
3313
|
+
* @see US-208 — the consumer-side capability story.
|
|
3314
|
+
*/
|
|
3315
|
+
getCapabilities() {
|
|
3316
|
+
return {
|
|
3317
|
+
minValidityMs: 6e4,
|
|
3318
|
+
maxValidityMs: 10 * 365 * 864e5,
|
|
3319
|
+
validityGranularityMs: 1e3,
|
|
3320
|
+
nativeUnit: "second"
|
|
3321
|
+
};
|
|
3322
|
+
}
|
|
3152
3323
|
/**
|
|
3153
3324
|
* Generate a new RSA key pair, create an internal CSR, sign it
|
|
3154
3325
|
* with this CA, and return both the certificate and private key
|
|
@@ -3166,7 +3337,6 @@ var init_certificate_authority = __esm({
|
|
|
3166
3337
|
*/
|
|
3167
3338
|
async generateKeyPairAndSignDER(options) {
|
|
3168
3339
|
const keySize = options.keySize ?? 2048;
|
|
3169
|
-
const validity = options.validity ?? 365;
|
|
3170
3340
|
const startDate = options.startDate ?? /* @__PURE__ */ new Date();
|
|
3171
3341
|
const tmpDir = await fs10.promises.mkdtemp(path6.join(os4.tmpdir(), "pki-keygen-"));
|
|
3172
3342
|
try {
|
|
@@ -3186,13 +3356,15 @@ var init_certificate_authority = __esm({
|
|
|
3186
3356
|
purpose: CertificatePurpose2.ForApplication
|
|
3187
3357
|
});
|
|
3188
3358
|
const certFile = path6.join(tmpDir, "certificate.pem");
|
|
3189
|
-
|
|
3359
|
+
const signingParams = {
|
|
3190
3360
|
applicationUri: options.applicationUri,
|
|
3191
3361
|
dns: options.dns,
|
|
3192
3362
|
ip: options.ip,
|
|
3193
|
-
startDate
|
|
3194
|
-
|
|
3195
|
-
|
|
3363
|
+
startDate
|
|
3364
|
+
};
|
|
3365
|
+
if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
|
|
3366
|
+
else signingParams.validity = options.validity ?? 365;
|
|
3367
|
+
await this.signCertificateRequest(certFile, csrFile, signingParams);
|
|
3196
3368
|
const certPem = readCertificatePEM(certFile);
|
|
3197
3369
|
const certificateDer = convertPEMtoDER(certPem);
|
|
3198
3370
|
const privateKey = readPrivateKey(privateKeyFile);
|
|
@@ -3217,7 +3389,6 @@ var init_certificate_authority = __esm({
|
|
|
3217
3389
|
*/
|
|
3218
3390
|
async generateKeyPairAndSignPFX(options) {
|
|
3219
3391
|
const keySize = options.keySize ?? 2048;
|
|
3220
|
-
const validity = options.validity ?? 365;
|
|
3221
3392
|
const startDate = options.startDate ?? /* @__PURE__ */ new Date();
|
|
3222
3393
|
const passphrase = options.passphrase ?? "";
|
|
3223
3394
|
const tmpDir = await fs10.promises.mkdtemp(path6.join(os4.tmpdir(), "pki-keygen-pfx-"));
|
|
@@ -3238,13 +3409,15 @@ var init_certificate_authority = __esm({
|
|
|
3238
3409
|
purpose: CertificatePurpose2.ForApplication
|
|
3239
3410
|
});
|
|
3240
3411
|
const certFile = path6.join(tmpDir, "certificate.pem");
|
|
3241
|
-
|
|
3412
|
+
const signingParams = {
|
|
3242
3413
|
applicationUri: options.applicationUri,
|
|
3243
3414
|
dns: options.dns,
|
|
3244
3415
|
ip: options.ip,
|
|
3245
|
-
startDate
|
|
3246
|
-
|
|
3247
|
-
|
|
3416
|
+
startDate
|
|
3417
|
+
};
|
|
3418
|
+
if (options.validityMs !== void 0) signingParams.validityMs = options.validityMs;
|
|
3419
|
+
else signingParams.validity = options.validity ?? 365;
|
|
3420
|
+
await this.signCertificateRequest(certFile, csrFile, signingParams);
|
|
3248
3421
|
const pfxFile = path6.join(tmpDir, "bundle.pfx");
|
|
3249
3422
|
await createPFX({
|
|
3250
3423
|
certificateFile: certFile,
|
|
@@ -3482,6 +3655,7 @@ var init_certificate_authority = __esm({
|
|
|
3482
3655
|
async signCACertificateRequest(certFile, csrFile, params) {
|
|
3483
3656
|
const caRootDir = path6.resolve(this.rootDir);
|
|
3484
3657
|
const options = { cwd: caRootDir };
|
|
3658
|
+
this._wireRevocationEnvVars();
|
|
3485
3659
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
3486
3660
|
const validity = params.validity ?? 3650;
|
|
3487
3661
|
await execute_openssl(
|
|
@@ -3648,6 +3822,7 @@ var init_certificate_authority = __esm({
|
|
|
3648
3822
|
ip
|
|
3649
3823
|
};
|
|
3650
3824
|
processAltNames(params);
|
|
3825
|
+
this._wireRevocationEnvVars();
|
|
3651
3826
|
const configFile = generateStaticConfig("conf/caconfig.cnf", options);
|
|
3652
3827
|
displaySubtitle("- then we ask the authority to sign the certificate signing request");
|
|
3653
3828
|
const configOption = ` -config ${configFile}`;
|