node-opcua-pki 3.0.2 → 3.1.1

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.
Files changed (53) hide show
  1. package/.ignore +6 -6
  2. package/.prettierrc +5 -5
  3. package/LICENSE +22 -22
  4. package/bin/crypto_create_CA.js +0 -0
  5. package/bin/crypto_create_CA_config.example.js +18 -18
  6. package/bin/install_prerequisite.js +9 -9
  7. package/dist/crypto_create_CA.d.ts +2 -2
  8. package/dist/crypto_create_CA.js +897 -897
  9. package/dist/index.d.ts +6 -6
  10. package/dist/index.js +44 -44
  11. package/dist/misc/applicationurn.d.ts +1 -1
  12. package/dist/misc/applicationurn.js +46 -46
  13. package/dist/misc/hostname.d.ts +8 -8
  14. package/dist/misc/hostname.js +102 -102
  15. package/dist/misc/install_prerequisite.d.ts +9 -9
  16. package/dist/misc/install_prerequisite.js +363 -360
  17. package/dist/misc/install_prerequisite.js.map +1 -1
  18. package/dist/misc/subject.d.ts +26 -26
  19. package/dist/misc/subject.js +121 -121
  20. package/dist/pki/certificate_authority.d.ts +61 -61
  21. package/dist/pki/certificate_authority.js +481 -481
  22. package/dist/pki/certificate_manager.d.ts +144 -144
  23. package/dist/pki/certificate_manager.js +883 -883
  24. package/dist/pki/certificate_manager.js.map +1 -1
  25. package/dist/pki/common.d.ts +5 -5
  26. package/dist/pki/common.js +2 -2
  27. package/dist/pki/templates/ca_config_template.cnf.d.ts +2 -2
  28. package/dist/pki/templates/ca_config_template.cnf.js +129 -129
  29. package/dist/pki/templates/simple_config_template.cnf.d.ts +2 -2
  30. package/dist/pki/templates/simple_config_template.cnf.js +75 -75
  31. package/dist/pki/toolbox.d.ts +160 -160
  32. package/dist/pki/toolbox.js +699 -699
  33. package/dist/pki/toolbox_pfx.js +18 -18
  34. package/lib/crypto_create_CA.ts +1135 -1135
  35. package/lib/index.ts +28 -28
  36. package/lib/misc/applicationurn.ts +45 -45
  37. package/lib/misc/hostname.ts +89 -89
  38. package/lib/misc/install_prerequisite.ts +454 -454
  39. package/lib/misc/subject.ts +141 -141
  40. package/lib/pki/certificate_manager.ts +1 -1
  41. package/lib/pki/common.ts +5 -5
  42. package/lib/pki/templates/ca_config_template.cnf.ts +129 -129
  43. package/lib/pki/templates/simple_config_template.cnf.ts +75 -75
  44. package/lib/pki/toolbox_pfx.ts +19 -19
  45. package/package.json +89 -89
  46. package/readme.md +214 -214
  47. package/tsconfig.json +20 -20
  48. package/dist/misc/fs.d.ts +0 -24
  49. package/dist/misc/fs.js +0 -21
  50. package/dist/misc/fs.js.map +0 -1
  51. package/dist/misc/get_default_filesystem.d.ts +0 -2
  52. package/dist/misc/get_default_filesystem.js +0 -9
  53. package/dist/misc/get_default_filesystem.js.map +0 -1
@@ -1,884 +1,884 @@
1
- "use strict";
2
- // ---------------------------------------------------------------------------------------------------------------------
3
- // node-opcua
4
- // ---------------------------------------------------------------------------------------------------------------------
5
- // Copyright (c) 2014-2022 - Etienne Rossignon - etienne.rossignon (at) gadz.org
6
- // Copyright (c) 2022 - Sterfive.com
7
- // ---------------------------------------------------------------------------------------------------------------------
8
- //
9
- // This project is licensed under the terms of the MIT license.
10
- //
11
- // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
12
- // documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
13
- // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
14
- // permit persons to whom the Software is furnished to do so, subject to the following conditions:
15
- //
16
- // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
17
- // Software.
18
- //
19
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
20
- // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
21
- // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22
- // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
- // ---------------------------------------------------------------------------------------------------------------------
24
- // tslint:disable:no-shadowed-variable
25
- // tslint:disable:member-ordering
26
- var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
27
- function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
28
- return new (P || (P = Promise))(function (resolve, reject) {
29
- function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
30
- function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
31
- function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
32
- step((generator = generator.apply(thisArg, _arguments || [])).next());
33
- });
34
- };
35
- Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.CertificateManager = exports.CertificateManagerState = exports.VerificationStatus = void 0;
37
- const assert = require("assert");
38
- const async = require("async");
39
- const chalk = require("chalk");
40
- const chokidar = require("chokidar");
41
- const fs = require("fs");
42
- const path = require("path");
43
- const util_1 = require("util");
44
- const node_opcua_crypto_1 = require("node-opcua-crypto");
45
- const toolbox_1 = require("./toolbox");
46
- const global_mutex_1 = require("@ster5/global-mutex");
47
- // tslint:disable:max-line-length
48
- // tslint:disable:no-var-requires
49
- // eslint-disable-next-line @typescript-eslint/no-var-requires
50
- const thenify = require("thenify");
51
- const fsFileExists = (0, util_1.promisify)(fs.exists);
52
- const fsWriteFile = (0, util_1.promisify)(fs.writeFile);
53
- const fsReadFile = (0, util_1.promisify)(fs.readFile);
54
- const fsRemoveFile = (0, util_1.promisify)(fs.unlink);
55
- var VerificationStatus;
56
- (function (VerificationStatus) {
57
- /** The certificate provided as a parameter is not valid. */
58
- VerificationStatus["BadCertificateInvalid"] = "BadCertificateInvalid";
59
- /** An error occurred verifying security. */
60
- VerificationStatus["BadSecurityChecksFailed"] = "BadSecurityChecksFailed";
61
- /** The certificate does not meet the requirements of the security policy. */
62
- VerificationStatus["BadCertificatePolicyCheckFailed"] = "BadCertificatePolicyCheckFailed";
63
- /** The certificate has expired or is not yet valid. */
64
- VerificationStatus["BadCertificateTimeInvalid"] = "BadCertificateTimeInvalid";
65
- /** An issuer certificate has expired or is not yet valid. */
66
- VerificationStatus["BadCertificateIssuerTimeInvalid"] = "BadCertificateIssuerTimeInvalid";
67
- /** The HostName used to connect to a server does not match a HostName in the certificate. */
68
- VerificationStatus["BadCertificateHostNameInvalid"] = "BadCertificateHostNameInvalid";
69
- /** The URI specified in the ApplicationDescription does not match the URI in the certificate. */
70
- VerificationStatus["BadCertificateUriInvalid"] = "BadCertificateUriInvalid";
71
- /** The certificate may not be used for the requested operation. */
72
- VerificationStatus["BadCertificateUseNotAllowed"] = "BadCertificateUseNotAllowed";
73
- /** The issuer certificate may not be used for the requested operation. */
74
- VerificationStatus["BadCertificateIssuerUseNotAllowed"] = "BadCertificateIssuerUseNotAllowed";
75
- /** The certificate is not trusted. */
76
- VerificationStatus["BadCertificateUntrusted"] = "BadCertificateUntrusted";
77
- /** It was not possible to determine if the certificate has been revoked. */
78
- VerificationStatus["BadCertificateRevocationUnknown"] = "BadCertificateRevocationUnknown";
79
- /** It was not possible to determine if the issuer certificate has been revoked. */
80
- VerificationStatus["BadCertificateIssuerRevocationUnknown"] = "BadCertificateIssuerRevocationUnknown";
81
- /** The certificate has been revoked. */
82
- VerificationStatus["BadCertificateRevoked"] = "BadCertificateRevoked";
83
- /** The issuer certificate has been revoked. */
84
- VerificationStatus["BadCertificateIssuerRevoked"] = "BadCertificateIssuerRevoked";
85
- /** The certificate chain is incomplete. */
86
- VerificationStatus["BadCertificateChainIncomplete"] = "BadCertificateChainIncomplete";
87
- /** Validation OK. */
88
- VerificationStatus["Good"] = "Good";
89
- })(VerificationStatus = exports.VerificationStatus || (exports.VerificationStatus = {}));
90
- function makeFingerprint(certificate) {
91
- return (0, node_opcua_crypto_1.makeSHA1Thumbprint)(certificate).toString("hex");
92
- }
93
- function short(stringToShorten) {
94
- return stringToShorten.substr(0, 10);
95
- }
96
- function buildIdealCertificateName(certificate) {
97
- const fingerprint = makeFingerprint(certificate);
98
- try {
99
- const commonName = (0, node_opcua_crypto_1.exploreCertificate)(certificate).tbsCertificate.subject.commonName || "";
100
- return commonName + "[" + fingerprint + "]";
101
- }
102
- catch (err) {
103
- // make be certificate is incorrect !
104
- return "invalid_certificate_[" + fingerprint + "]";
105
- }
106
- }
107
- function findMatchingIssuerKey(entries, wantedIssuerKey) {
108
- const selected = entries.filter(({ certificate }) => {
109
- const info = (0, node_opcua_crypto_1.exploreCertificate)(certificate);
110
- return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
111
- });
112
- return selected;
113
- }
114
- function isSelfSigned2(info) {
115
- var _a, _b, _c;
116
- return (((_a = info.tbsCertificate.extensions) === null || _a === void 0 ? void 0 : _a.subjectKeyIdentifier) ===
117
- ((_c = (_b = info.tbsCertificate.extensions) === null || _b === void 0 ? void 0 : _b.authorityKeyIdentifier) === null || _c === void 0 ? void 0 : _c.keyIdentifier));
118
- }
119
- function isSelfSigned3(certificate) {
120
- const info = (0, node_opcua_crypto_1.exploreCertificate)(certificate);
121
- return isSelfSigned2(info);
122
- }
123
- var CertificateManagerState;
124
- (function (CertificateManagerState) {
125
- CertificateManagerState[CertificateManagerState["Uninitialized"] = 0] = "Uninitialized";
126
- CertificateManagerState[CertificateManagerState["Initializing"] = 1] = "Initializing";
127
- CertificateManagerState[CertificateManagerState["Initialized"] = 2] = "Initialized";
128
- CertificateManagerState[CertificateManagerState["Disposing"] = 3] = "Disposing";
129
- CertificateManagerState[CertificateManagerState["Disposed"] = 4] = "Disposed";
130
- })(CertificateManagerState = exports.CertificateManagerState || (exports.CertificateManagerState = {}));
131
- class CertificateManager {
132
- constructor(options) {
133
- this.untrustUnknownCertificate = true;
134
- this.state = CertificateManagerState.Uninitialized;
135
- this.folderPoolingInterval = 5000;
136
- this._watchers = [];
137
- this._readCertificatesCalled = false;
138
- this._filenameToHash = {};
139
- this._thumbs = {
140
- rejected: {},
141
- trusted: {},
142
- issuers: {
143
- certs: {},
144
- },
145
- crl: {},
146
- issuersCrl: {},
147
- };
148
- this._pending_crl_to_process = 0;
149
- this.queue = [];
150
- options.keySize = options.keySize || 2048;
151
- assert(Object.prototype.hasOwnProperty.call(options, "location"));
152
- assert(Object.prototype.hasOwnProperty.call(options, "keySize"));
153
- assert(this.state === CertificateManagerState.Uninitialized);
154
- this.location = (0, toolbox_1.make_path)(options.location, "");
155
- this.keySize = options.keySize;
156
- (0, toolbox_1.mkdir)(options.location);
157
- // istanbul ignore next
158
- if (!fs.existsSync(this.location)) {
159
- throw new Error("CertificateManager cannot access location " + this.location);
160
- }
161
- }
162
- get configFile() {
163
- return path.join(this.rootDir, "own/openssl.cnf");
164
- }
165
- get rootDir() {
166
- return this.location;
167
- }
168
- get privateKey() {
169
- return path.join(this.rootDir, "own/private/private_key.pem");
170
- }
171
- get randomFile() {
172
- return path.join(this.rootDir, "./random.rnd");
173
- }
174
- getCertificateStatus(certificate, ...args) {
175
- const callback = args[0];
176
- this.initialize(() => {
177
- this._checkRejectedOrTrusted(certificate, (err, status) => {
178
- if (err) {
179
- return callback(err);
180
- }
181
- if (status === "unknown") {
182
- assert(certificate instanceof Buffer);
183
- const pem = (0, node_opcua_crypto_1.toPem)(certificate, "CERTIFICATE");
184
- const fingerprint = makeFingerprint(certificate);
185
- const filename = path.join(this.rejectedFolder, buildIdealCertificateName(certificate) + ".pem");
186
- fs.writeFile(filename, pem, (err) => {
187
- this._thumbs.rejected[fingerprint] = {
188
- certificate,
189
- filename,
190
- };
191
- if (err) {
192
- return callback(err);
193
- }
194
- status = "rejected";
195
- return callback(null, status);
196
- });
197
- return;
198
- }
199
- else {
200
- return callback(null, status);
201
- }
202
- });
203
- });
204
- }
205
- rejectCertificate(certificate, ...args) {
206
- const callback = args[0];
207
- assert(callback && callback instanceof Function, "expecting callback");
208
- this._moveCertificate(certificate, "rejected", callback);
209
- }
210
- trustCertificate(certificate, ...args) {
211
- const callback = args[0];
212
- assert(callback && callback instanceof Function, "expecting callback");
213
- this._moveCertificate(certificate, "trusted", callback);
214
- }
215
- get rejectedFolder() {
216
- return path.join(this.rootDir, "rejected");
217
- }
218
- get trustedFolder() {
219
- return path.join(this.rootDir, "trusted/certs");
220
- }
221
- get crlFolder() {
222
- return path.join(this.rootDir, "trusted/crl");
223
- }
224
- get issuersCertFolder() {
225
- return path.join(this.rootDir, "issuers/certs");
226
- }
227
- get issuersCrlFolder() {
228
- return path.join(this.rootDir, "issuers/crl");
229
- }
230
- isCertificateTrusted(certificate) {
231
- var _a;
232
- return __awaiter(this, void 0, void 0, function* () {
233
- const fingerprint = makeFingerprint(certificate);
234
- const certificateInTrust = (_a = this._thumbs.trusted[fingerprint]) === null || _a === void 0 ? void 0 : _a.certificate;
235
- if (certificateInTrust) {
236
- return "Good";
237
- }
238
- else {
239
- const certificateInRejected = this._thumbs.rejected[fingerprint];
240
- if (!certificateInRejected) {
241
- const certificateFilenameInRejected = path.join(this.rejectedFolder, buildIdealCertificateName(certificate) + ".pem");
242
- if (!this.untrustUnknownCertificate) {
243
- return "Good";
244
- }
245
- // Certificate should be mark as untrusted
246
- // let's first verify that certificate is valid ,as we don't want to write invalid data
247
- try {
248
- const certificateInfo = (0, node_opcua_crypto_1.exploreCertificateInfo)(certificate);
249
- }
250
- catch (err) {
251
- return "BadCertificateInvalid";
252
- }
253
- (0, toolbox_1.debugLog)("certificate has never been seen before and is now rejected (untrusted) ", certificateFilenameInRejected);
254
- yield fsWriteFile(certificateFilenameInRejected, (0, node_opcua_crypto_1.toPem)(certificate, "CERTIFICATE"));
255
- }
256
- return "BadCertificateUntrusted";
257
- }
258
- });
259
- }
260
- _innerVerifyCertificateAsync(certificate, isIssuer, level) {
261
- var _a, _b;
262
- return __awaiter(this, void 0, void 0, function* () {
263
- if (level >= 5) {
264
- // maximum level of certificate in chain reached !
265
- return VerificationStatus.BadSecurityChecksFailed;
266
- }
267
- const chain = (0, node_opcua_crypto_1.split_der)(certificate);
268
- (0, toolbox_1.debugLog)("NB CERTIFICATE IN CHAIN = ", chain.length);
269
- const info = (0, node_opcua_crypto_1.exploreCertificate)(chain[0]);
270
- let hasValidIssuer = false;
271
- let hasTrustedIssuer = false;
272
- // check if certificate is attached to a issuer
273
- const hasIssuerKey = (_b = (_a = info.tbsCertificate.extensions) === null || _a === void 0 ? void 0 : _a.authorityKeyIdentifier) === null || _b === void 0 ? void 0 : _b.keyIdentifier;
274
- (0, toolbox_1.debugLog)("Certificate as an Issuer Key", hasIssuerKey);
275
- // console.log(inspect(info, { depth: 100 }));
276
- if (hasIssuerKey) {
277
- const isSelfSigned = isSelfSigned2(info);
278
- (0, toolbox_1.debugLog)("Is the Certificate self-signed ?", isSelfSigned);
279
- if (!isSelfSigned) {
280
- const issuerCertificate = yield this.findIssuerCertificate(chain[0]);
281
- // console.log("issuer is found", !!issuerCertificate, info.tbsCertificate.extensions.subjectKeyIdentifier, info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier);
282
- if (!issuerCertificate) {
283
- // if the certificate is not self-signed we ust have a issuer.
284
- // but no Issuer is not found
285
- return VerificationStatus.BadSecurityChecksFailed;
286
- }
287
- const issuerStatus = yield this._innerVerifyCertificateAsync(issuerCertificate, true, level + 1);
288
- if (issuerStatus === VerificationStatus.BadCertificateRevocationUnknown) {
289
- // the issuer must have a CRL available .... !
290
- return VerificationStatus.BadCertificateIssuerRevocationUnknown;
291
- }
292
- if (issuerStatus === VerificationStatus.BadCertificateIssuerRevocationUnknown) {
293
- // the issuer must have a CRL available .... !
294
- return VerificationStatus.BadCertificateIssuerRevocationUnknown;
295
- }
296
- if (issuerStatus === VerificationStatus.BadCertificateTimeInvalid) {
297
- // the issuer must have valid dates ....
298
- return VerificationStatus.BadCertificateIssuerTimeInvalid;
299
- }
300
- if (issuerStatus !== VerificationStatus.Good && issuerStatus !== VerificationStatus.BadCertificateUntrusted) {
301
- // if the issuer has other issue => let's drop!
302
- return VerificationStatus.BadSecurityChecksFailed;
303
- }
304
- // verify that certificate was signed by issuer
305
- const isCertificateSignatureOK = (0, node_opcua_crypto_1.verifyCertificateSignature)(certificate, issuerCertificate);
306
- if (!isCertificateSignatureOK) {
307
- // the certificate was not signed by the issuer as it claim to be ! Danger
308
- return VerificationStatus.BadSecurityChecksFailed;
309
- }
310
- hasValidIssuer = true;
311
- // let detected if our certificate is in the revocation list
312
- const revokedStatus = yield this.isCertificateRevoked(certificate);
313
- if (revokedStatus === VerificationStatus.BadCertificateRevocationUnknown) {
314
- return VerificationStatus.BadCertificateRevocationUnknown;
315
- }
316
- if (revokedStatus !== VerificationStatus.Good) {
317
- // certificate is revoked !!!
318
- (0, toolbox_1.debugLog)("revokedStatus", revokedStatus);
319
- return revokedStatus;
320
- }
321
- // let check if the issuer is explicitly trusted
322
- const issuerTrustedStatus = yield this._checkRejectedOrTrusted(issuerCertificate);
323
- (0, toolbox_1.debugLog)("issuerTrustedStatus", issuerTrustedStatus);
324
- if (issuerTrustedStatus === "unknown") {
325
- hasTrustedIssuer = false;
326
- }
327
- else if (issuerTrustedStatus === "trusted") {
328
- hasTrustedIssuer = true;
329
- }
330
- else if (issuerTrustedStatus === "rejected") {
331
- // we should never get there: this should have been detected before !!!
332
- return VerificationStatus.BadSecurityChecksFailed;
333
- }
334
- }
335
- else {
336
- // verify that certificate was signed by issuer (self in this case)
337
- const isCertificateSignatureOK = (0, node_opcua_crypto_1.verifyCertificateSignature)(certificate, certificate);
338
- if (!isCertificateSignatureOK) {
339
- (0, toolbox_1.debugLog)("Self-signed Certificate signature is not valid");
340
- return VerificationStatus.BadSecurityChecksFailed;
341
- }
342
- const revokedStatus = yield this.isCertificateRevoked(certificate);
343
- (0, toolbox_1.debugLog)("revokedStatus of self signed certificate:", revokedStatus);
344
- }
345
- }
346
- const status = yield this._checkRejectedOrTrusted(certificate);
347
- if (status === "rejected") {
348
- return VerificationStatus.BadCertificateUntrusted;
349
- }
350
- const c2 = chain[1] ? (0, node_opcua_crypto_1.exploreCertificateInfo)(chain[1]) : "non";
351
- // Has SoftwareCertificate passed its issue date and has it not expired ?
352
- // check dates
353
- const certificateInfo = (0, node_opcua_crypto_1.exploreCertificateInfo)(certificate);
354
- const now = new Date();
355
- let isTimeInvalid = false;
356
- // check that certificate is active
357
- if (certificateInfo.notBefore.getTime() > now.getTime()) {
358
- // certificate is not active yet
359
- (0, toolbox_1.debugLog)(chalk.red("certificate is invalid : certificate is not active yet !") +
360
- " not before date =" +
361
- certificateInfo.notBefore);
362
- isTimeInvalid = true;
363
- }
364
- // check that certificate has not expired
365
- if (certificateInfo.notAfter.getTime() <= now.getTime()) {
366
- // certificate is obsolete
367
- (0, toolbox_1.debugLog)(chalk.red("certificate is invalid : certificate has expired !") + " not after date =" + certificateInfo.notAfter);
368
- isTimeInvalid = true;
369
- }
370
- if (status === "trusted") {
371
- return isTimeInvalid ? VerificationStatus.BadCertificateTimeInvalid : VerificationStatus.Good;
372
- }
373
- assert(status === "unknown");
374
- if (hasIssuerKey) {
375
- if (!hasTrustedIssuer) {
376
- return VerificationStatus.BadCertificateUntrusted;
377
- }
378
- if (!hasValidIssuer) {
379
- return VerificationStatus.BadCertificateUntrusted;
380
- }
381
- return isTimeInvalid ? VerificationStatus.BadCertificateTimeInvalid : VerificationStatus.Good;
382
- }
383
- else {
384
- return VerificationStatus.BadCertificateUntrusted;
385
- }
386
- });
387
- }
388
- verifyCertificateAsync(certificate) {
389
- return __awaiter(this, void 0, void 0, function* () {
390
- const status1 = yield this._innerVerifyCertificateAsync(certificate, false, 0);
391
- return status1;
392
- });
393
- }
394
- verifyCertificate(certificate, callback) {
395
- // Is the signature on the SoftwareCertificate valid .?
396
- if (!certificate) {
397
- // missing certificate
398
- return callback(null, VerificationStatus.BadSecurityChecksFailed);
399
- }
400
- (0, util_1.callbackify)(this.verifyCertificateAsync).call(this, certificate, callback);
401
- }
402
- initialize(...args) {
403
- const callback = args[0];
404
- assert(callback && callback instanceof Function);
405
- if (this.state !== CertificateManagerState.Uninitialized) {
406
- return callback();
407
- }
408
- this.state = CertificateManagerState.Initializing;
409
- return this._initialize((err) => {
410
- this.state = CertificateManagerState.Initialized;
411
- return callback(err);
412
- });
413
- }
414
- _initialize(callback) {
415
- assert((this.state = CertificateManagerState.Initializing));
416
- const pkiDir = this.location;
417
- (0, toolbox_1.mkdir)(pkiDir);
418
- (0, toolbox_1.mkdir)(path.join(pkiDir, "own"));
419
- (0, toolbox_1.mkdir)(path.join(pkiDir, "own/certs"));
420
- (0, toolbox_1.mkdir)(path.join(pkiDir, "own/private"));
421
- (0, toolbox_1.mkdir)(path.join(pkiDir, "rejected"));
422
- (0, toolbox_1.mkdir)(path.join(pkiDir, "trusted"));
423
- (0, toolbox_1.mkdir)(path.join(pkiDir, "trusted/certs"));
424
- (0, toolbox_1.mkdir)(path.join(pkiDir, "trusted/crl"));
425
- (0, toolbox_1.mkdir)(path.join(pkiDir, "issuers"));
426
- (0, toolbox_1.mkdir)(path.join(pkiDir, "issuers/certs")); // contains Trusted CA certificates
427
- (0, toolbox_1.mkdir)(path.join(pkiDir, "issuers/crl")); // contains CRL of revoked CA certificates
428
- this.withLock((callback) => {
429
- assert(this.state !== CertificateManagerState.Disposing);
430
- if (this.state === CertificateManagerState.Disposed) {
431
- return callback();
432
- }
433
- assert(this.state === CertificateManagerState.Initializing);
434
- fs.writeFileSync(this.configFile, toolbox_1.configurationFileSimpleTemplate);
435
- // note : openssl 1.1.1 has a bug that causes a failure if
436
- // random file cannot be found. (should be fixed in 1.1.1.a)
437
- // if this issue become important we may have to consider checking that rndFile exists and recreate
438
- // it if not . this could be achieved with the command :
439
- // "openssl rand -writerand ${this.randomFile}"
440
- //
441
- // cf: https://github.com/node-opcua/node-opcua/issues/554
442
- if (!fs.existsSync(this.privateKey)) {
443
- (0, toolbox_1.debugLog)("generating private key ...");
444
- (0, toolbox_1.setEnv)("RANDFILE", this.randomFile);
445
- (0, toolbox_1.createPrivateKey)(this.privateKey, this.keySize, (err) => {
446
- if (err) {
447
- return callback(err);
448
- }
449
- this._readCertificates(() => callback());
450
- });
451
- }
452
- else {
453
- // debugLog(" initialize : private key already exists ... skipping");
454
- this._readCertificates(() => callback());
455
- }
456
- }, callback);
457
- }
458
- dispose() {
459
- return __awaiter(this, void 0, void 0, function* () {
460
- if (this.state === CertificateManagerState.Disposing) {
461
- throw new Error("Already disposing");
462
- }
463
- if (this.state === CertificateManagerState.Uninitialized) {
464
- this.state = CertificateManagerState.Disposed;
465
- return;
466
- }
467
- // wait for initialization to be completed
468
- if (this.state === CertificateManagerState.Initializing) {
469
- yield new Promise((resolve) => setTimeout(resolve, 100));
470
- return yield this.dispose();
471
- }
472
- try {
473
- this.state = CertificateManagerState.Disposing;
474
- yield Promise.all(this._watchers.map((w) => w.close()));
475
- this._watchers.forEach((w) => w.removeAllListeners());
476
- this._watchers.splice(0);
477
- }
478
- finally {
479
- this.state = CertificateManagerState.Disposed;
480
- }
481
- });
482
- }
483
- // eslint-disable-next-line @typescript-eslint/ban-types
484
- withLock(action, callback) {
485
- this.withLock2((0, util_1.promisify)(action))
486
- .then((t) => callback(null, t))
487
- .catch((err) => callback(err));
488
- }
489
- withLock2(action) {
490
- return __awaiter(this, void 0, void 0, function* () {
491
- const lockFileName = path.join(this.rootDir, "mutex.lock");
492
- return (0, global_mutex_1.withLock)({ lockfile: lockFileName }, () => __awaiter(this, void 0, void 0, function* () {
493
- return yield action();
494
- }));
495
- });
496
- }
497
- createSelfSignedCertificate(params, ...args) {
498
- const callback = args[0];
499
- assert(typeof params.applicationUri === "string", "expecting applicationUri");
500
- if (!fs.existsSync(this.privateKey)) {
501
- return callback(new Error("Cannot find private key " + this.privateKey));
502
- }
503
- let certificateFilename = path.join(this.rootDir, "own/certs/self_signed_certificate.pem");
504
- certificateFilename = params.outputFile || certificateFilename;
505
- const _params = params;
506
- _params.rootDir = this.rootDir;
507
- _params.configFile = this.configFile;
508
- _params.privateKey = this.privateKey;
509
- this.withLock((callback) => {
510
- (0, toolbox_1.createSelfSignCertificate)(certificateFilename, _params, callback);
511
- }, callback);
512
- }
513
- createCertificateRequest(params, callback) {
514
- assert(params);
515
- const _params = params;
516
- if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
517
- throw new Error("rootDir should not be specified ");
518
- }
519
- assert(typeof callback === "function");
520
- assert(!_params.rootDir);
521
- assert(!_params.configFile);
522
- assert(!_params.privateKey);
523
- _params.rootDir = this.rootDir;
524
- _params.configFile = this.configFile;
525
- _params.privateKey = this.privateKey;
526
- this.withLock((callback) => {
527
- // compose a file name for the request
528
- const now = new Date();
529
- const today = now.toISOString().slice(0, 10) + "_" + now.getTime();
530
- const certificateSigningRequestFilename = path.join(this.rootDir, "own/certs", "certificate_" + today + ".csr");
531
- (0, toolbox_1.createCertificateSigningRequest)(certificateSigningRequestFilename, _params, (err) => {
532
- if (err) {
533
- return callback(err);
534
- }
535
- return callback(null, certificateSigningRequestFilename);
536
- });
537
- }, callback);
538
- }
539
- addIssuer(certificate, validate = false, addInTrustList = false) {
540
- return __awaiter(this, void 0, void 0, function* () {
541
- if (validate) {
542
- const status = yield this.verifyCertificate(certificate);
543
- if (status !== VerificationStatus.Good && status !== VerificationStatus.BadCertificateUntrusted) {
544
- return status;
545
- }
546
- }
547
- const pemCertificate = (0, node_opcua_crypto_1.toPem)(certificate, "CERTIFICATE");
548
- const fingerprint = makeFingerprint(certificate);
549
- if (this._thumbs.issuers.certs[fingerprint]) {
550
- // already in .. simply ignore
551
- return VerificationStatus.Good;
552
- }
553
- // write certificate
554
- const filename = path.join(this.issuersCertFolder, "issuer_" + buildIdealCertificateName(certificate) + ".pem");
555
- yield (0, util_1.promisify)(fs.writeFile)(filename, pemCertificate, "ascii");
556
- // first time seen, let's save it.
557
- this._thumbs.issuers.certs[fingerprint] = { certificate, filename };
558
- if (addInTrustList) {
559
- // add certificate in the trust list as well
560
- yield this.trustCertificate(certificate);
561
- }
562
- return VerificationStatus.Good;
563
- });
564
- }
565
- addRevocationList(crl) {
566
- return __awaiter(this, void 0, void 0, function* () {
567
- return this.withLock2(() => __awaiter(this, void 0, void 0, function* () {
568
- try {
569
- const crlInfo = (0, node_opcua_crypto_1.exploreCertificateRevocationList)(crl);
570
- const key = crlInfo.tbsCertList.issuerFingerprint;
571
- if (!this._thumbs.issuersCrl[key]) {
572
- this._thumbs.issuersCrl[key] = { crls: [], serialNumbers: {} };
573
- }
574
- const pemCertificate = (0, node_opcua_crypto_1.toPem)(crl, "X509 CRL");
575
- const filename = path.join(this.issuersCrlFolder, "crl_" + buildIdealCertificateName(crl) + ".pem");
576
- yield (0, util_1.promisify)(fs.writeFile)(filename, pemCertificate, "ascii");
577
- yield this._on_crl_file_added(this._thumbs.issuersCrl, filename);
578
- yield this.waitAndCheckCRLProcessingStatus();
579
- return VerificationStatus.Good;
580
- }
581
- catch (err) {
582
- (0, toolbox_1.debugLog)(err);
583
- return VerificationStatus.BadSecurityChecksFailed;
584
- }
585
- }));
586
- });
587
- }
588
- // find the issuer certificate
589
- findIssuerCertificate(certificate) {
590
- var _a, _b;
591
- return __awaiter(this, void 0, void 0, function* () {
592
- const certInfo = (0, node_opcua_crypto_1.exploreCertificate)(certificate);
593
- // istanbul ignore next
594
- if (isSelfSigned2(certInfo)) {
595
- // the certificate is self signed so is it's own issuer.
596
- return certificate;
597
- }
598
- const wantedIssuerKey = (_b = (_a = certInfo.tbsCertificate.extensions) === null || _a === void 0 ? void 0 : _a.authorityKeyIdentifier) === null || _b === void 0 ? void 0 : _b.keyIdentifier;
599
- // istanbul ignore next
600
- if (!wantedIssuerKey) {
601
- // Certificate has no extension 3 ! the certificate might have been generated by an old system
602
- (0, toolbox_1.debugLog)("Certificate has no extension 3");
603
- return null;
604
- }
605
- const issuerCertificates = Object.values(this._thumbs.issuers.certs);
606
- const selectedIssuerCertificates = findMatchingIssuerKey(issuerCertificates, wantedIssuerKey);
607
- if (selectedIssuerCertificates.length > 0) {
608
- if (selectedIssuerCertificates.length > 1) {
609
- // tslint:disable-next-line: no-console
610
- console.log("Warning more than one issuer certificate exists with subjectKeyIdentifier ", wantedIssuerKey);
611
- }
612
- return selectedIssuerCertificates[0].certificate || null;
613
- }
614
- // check also in trusted list
615
- const trustedCertificates = Object.values(this._thumbs.trusted);
616
- const selectedTrustedCertificates = findMatchingIssuerKey(trustedCertificates, wantedIssuerKey);
617
- // istanbul ignore next
618
- if (selectedTrustedCertificates.length > 1) {
619
- // tslint:disable-next-line: no-console
620
- console.log("Warning more than one certificate exists with subjectKeyIdentifier in trusted certificate list ", wantedIssuerKey, selectedTrustedCertificates.length);
621
- for (const entry of selectedTrustedCertificates) {
622
- (0, toolbox_1.dumpCertificate)(entry.filename, (err, data) => {
623
- (0, toolbox_1.debugLog)(" ", entry.filename);
624
- (0, toolbox_1.debugLog)(data);
625
- });
626
- }
627
- }
628
- return selectedTrustedCertificates.length > 0 ? selectedTrustedCertificates[0].certificate : null;
629
- });
630
- }
631
- _checkRejectedOrTrusted(certificate, ...args) {
632
- const callback = args[0];
633
- assert(callback && callback instanceof Function);
634
- assert(certificate instanceof Buffer);
635
- const fingerprint = makeFingerprint(certificate);
636
- (0, toolbox_1.debugLog)("_checkRejectedOrTrusted fingerprint ", short(fingerprint));
637
- this._readCertificates((err) => {
638
- // istanbul ignore next
639
- if (err) {
640
- return callback(err);
641
- }
642
- if (Object.prototype.hasOwnProperty.call(this._thumbs.rejected, fingerprint)) {
643
- return callback(null, "rejected");
644
- }
645
- if (Object.prototype.hasOwnProperty.call(this._thumbs.trusted, fingerprint)) {
646
- return callback(null, "trusted");
647
- }
648
- return callback(null, "unknown");
649
- });
650
- }
651
- _moveCertificate(certificate, newStatus, callback) {
652
- // a mutex is requested here
653
- assert(certificate instanceof Buffer);
654
- const fingerprint = makeFingerprint(certificate);
655
- this.getCertificateStatus(certificate, (err, status) => {
656
- var _a;
657
- // istanbul ignore next
658
- if (err) {
659
- return callback(err);
660
- }
661
- (0, toolbox_1.debugLog)("_moveCertificate", fingerprint.substr(0, 10), "from", status, "to", newStatus);
662
- assert(status === "rejected" || status === "trusted");
663
- if (status !== newStatus) {
664
- const certificateSrc = (_a = this._thumbs[status][fingerprint]) === null || _a === void 0 ? void 0 : _a.filename;
665
- // istanbul ignore next
666
- if (!certificateSrc) {
667
- (0, toolbox_1.debugLog)(" cannot find certificate ", fingerprint.substr(0, 10), " in", this._thumbs, [status]);
668
- return callback(new Error("internal"));
669
- }
670
- const destFolder = newStatus === "rejected"
671
- ? this.rejectedFolder
672
- : newStatus === "trusted"
673
- ? this.trustedFolder
674
- : this.rejectedFolder;
675
- const certificateDest = path.join(destFolder, path.basename(certificateSrc));
676
- (0, toolbox_1.debugLog)("_moveCertificate1", fingerprint.substr(0, 10), "old name", certificateSrc);
677
- (0, toolbox_1.debugLog)("_moveCertificate1", fingerprint.substr(0, 10), "new name", certificateDest);
678
- fs.rename(certificateSrc, certificateDest, (err) => {
679
- delete this._thumbs[status][fingerprint];
680
- this._thumbs[newStatus][fingerprint] = {
681
- certificate,
682
- filename: certificateDest,
683
- };
684
- // we do not return the error here
685
- return callback( /*err*/);
686
- });
687
- }
688
- else {
689
- return callback();
690
- }
691
- });
692
- }
693
- _findAssociatedCRLs(issuerCertificate) {
694
- const issuerCertificateInfo = (0, node_opcua_crypto_1.exploreCertificate)(issuerCertificate);
695
- const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
696
- return this._thumbs.issuersCrl[key] ? this._thumbs.issuersCrl[key] : this._thumbs.crl[key] ? this._thumbs.crl[key] : null;
697
- }
698
- isCertificateRevoked(certificate, issuerCertificate) {
699
- var _a, _b, _c, _d;
700
- return __awaiter(this, void 0, void 0, function* () {
701
- // istanbul ignore next
702
- if (isSelfSigned3(certificate)) {
703
- return VerificationStatus.Good;
704
- }
705
- if (!issuerCertificate) {
706
- issuerCertificate = yield this.findIssuerCertificate(certificate);
707
- }
708
- if (!issuerCertificate) {
709
- return VerificationStatus.BadCertificateChainIncomplete;
710
- }
711
- const crls = this._findAssociatedCRLs(issuerCertificate);
712
- if (!crls) {
713
- return VerificationStatus.BadCertificateRevocationUnknown;
714
- }
715
- const certInfo = (0, node_opcua_crypto_1.exploreCertificate)(certificate);
716
- const serialNumber = certInfo.tbsCertificate.serialNumber || ((_b = (_a = certInfo.tbsCertificate.extensions) === null || _a === void 0 ? void 0 : _a.authorityKeyIdentifier) === null || _b === void 0 ? void 0 : _b.serial) || "";
717
- const key = ((_d = (_c = certInfo.tbsCertificate.extensions) === null || _c === void 0 ? void 0 : _c.authorityKeyIdentifier) === null || _d === void 0 ? void 0 : _d.authorityCertIssuerFingerPrint) || "<unknown>";
718
- const crl2 = this._thumbs.crl[key] || null;
719
- if (crls.serialNumbers[serialNumber] || (crl2 && crl2.serialNumbers[serialNumber])) {
720
- return VerificationStatus.BadCertificateRevoked;
721
- }
722
- return VerificationStatus.Good;
723
- });
724
- }
725
- _on_crl_file_added(index, filename) {
726
- this.queue.push({ index, filename });
727
- this._pending_crl_to_process += 1;
728
- if (this._pending_crl_to_process === 1) {
729
- this._process_next_crl();
730
- }
731
- }
732
- _process_next_crl() {
733
- return __awaiter(this, void 0, void 0, function* () {
734
- try {
735
- const { index, filename } = this.queue.shift();
736
- const crl = yield (0, node_opcua_crypto_1.readCertificateRevocationList)(filename);
737
- const crlInfo = (0, node_opcua_crypto_1.exploreCertificateRevocationList)(crl);
738
- (0, toolbox_1.debugLog)(chalk.cyan("add CRL in folder "), filename); // stat);
739
- const fingerprint = crlInfo.tbsCertList.issuerFingerprint;
740
- index[fingerprint] = index[fingerprint] || {
741
- crls: [],
742
- serialNumbers: {},
743
- };
744
- index[fingerprint].crls.push({ crlInfo, filename });
745
- const serialNumbers = index[fingerprint].serialNumbers;
746
- // now inject serial numbers
747
- for (const revokedCertificate of crlInfo.tbsCertList.revokedCertificates) {
748
- const serialNumber = revokedCertificate.userCertificate;
749
- if (!serialNumbers[serialNumber]) {
750
- serialNumbers[serialNumber] = revokedCertificate.revocationDate;
751
- }
752
- }
753
- (0, toolbox_1.debugLog)(chalk.cyan("CRL"), fingerprint, "serial numbers = ", Object.keys(serialNumbers)); // stat);
754
- }
755
- catch (err) {
756
- (0, toolbox_1.debugLog)("CRL filename error =");
757
- (0, toolbox_1.debugLog)(err);
758
- }
759
- this._pending_crl_to_process -= 1;
760
- if (this._pending_crl_to_process === 0) {
761
- if (this._on_crl_process) {
762
- this._on_crl_process();
763
- this._on_crl_process = undefined;
764
- }
765
- }
766
- else {
767
- this._process_next_crl();
768
- }
769
- });
770
- }
771
- _readCertificates(callback) {
772
- if (this._readCertificatesCalled) {
773
- return callback();
774
- }
775
- this._readCertificatesCalled = true;
776
- const options = {
777
- usePolling: true,
778
- interval: Math.min(10 * 60 * 1000, Math.max(100, this.folderPoolingInterval)),
779
- persistent: false,
780
- awaitWriteFinish: {
781
- stabilityThreshold: 2000,
782
- pollInterval: 600,
783
- },
784
- };
785
- function _walkCRLFiles(folder, index, _innerCallback) {
786
- const w = chokidar.watch(folder, options);
787
- w.on("unlink", (filename, stat) => {
788
- // CRL never removed
789
- });
790
- w.on("add", (filename, stat) => {
791
- this._on_crl_file_added(index, filename);
792
- });
793
- w.on("change", (path, stat) => {
794
- (0, toolbox_1.debugLog)("change in folder ", folder, path, stat);
795
- });
796
- this._watchers.push(w);
797
- w.on("ready", () => {
798
- _innerCallback();
799
- });
800
- }
801
- function _walkAllFiles(folder, index, _innerCallback) {
802
- const w = chokidar.watch(folder, options);
803
- w.on("unlink", (filename, stat) => {
804
- (0, toolbox_1.debugLog)(chalk.cyan("unlink in folder " + folder), filename);
805
- const h = this._filenameToHash[filename];
806
- if (h && index[h]) {
807
- delete index[h];
808
- }
809
- });
810
- w.on("add", (filename, stat) => {
811
- var _a, _b;
812
- (0, toolbox_1.debugLog)(chalk.cyan("add in folder " + folder), filename); // stat);
813
- try {
814
- const certificate = (0, node_opcua_crypto_1.readCertificate)(filename);
815
- const info = (0, node_opcua_crypto_1.exploreCertificate)(certificate);
816
- const fingerprint = makeFingerprint(certificate);
817
- index[fingerprint] = {
818
- certificate,
819
- filename,
820
- };
821
- this._filenameToHash[filename] = fingerprint;
822
- (0, toolbox_1.debugLog)(chalk.magenta("CERT"), info.tbsCertificate.subjectFingerPrint, info.tbsCertificate.serialNumber, (_b = (_a = info.tbsCertificate.extensions) === null || _a === void 0 ? void 0 : _a.authorityKeyIdentifier) === null || _b === void 0 ? void 0 : _b.authorityCertIssuerFingerPrint);
823
- }
824
- catch (err) {
825
- (0, toolbox_1.debugLog)("Walk files in folder " + folder + " with file " + filename);
826
- (0, toolbox_1.debugLog)(err);
827
- }
828
- });
829
- w.on("change", (path, stat) => {
830
- (0, toolbox_1.debugLog)("change in folder ", folder, path);
831
- });
832
- this._watchers.push(w);
833
- w.on("ready", () => {
834
- _innerCallback();
835
- (0, toolbox_1.debugLog)("ready");
836
- (0, toolbox_1.debugLog)(Object.entries(index).map((kv) => kv[0].substr(0, 10)));
837
- });
838
- }
839
- async.parallelLimit([
840
- _walkAllFiles.bind(this, this.trustedFolder, this._thumbs.trusted),
841
- _walkAllFiles.bind(this, this.issuersCertFolder, this._thumbs.issuers.certs),
842
- _walkAllFiles.bind(this, this.rejectedFolder, this._thumbs.rejected),
843
- _walkCRLFiles.bind(this, this.crlFolder, this._thumbs.crl),
844
- _walkCRLFiles.bind(this, this.issuersCrlFolder, this._thumbs.issuersCrl),
845
- ], 2, (err) => {
846
- // istanbul ignore next
847
- if (err) {
848
- (0, toolbox_1.debugLog)("Error=", err);
849
- return callback(err);
850
- }
851
- this.waitAndCheckCRLProcessingStatus()
852
- .then(() => callback())
853
- .catch((err) => callback(err));
854
- });
855
- }
856
- // make sure that all crls have been processed.
857
- waitAndCheckCRLProcessingStatus() {
858
- return __awaiter(this, void 0, void 0, function* () {
859
- return new Promise((resolve, reject) => {
860
- if (this._pending_crl_to_process === 0) {
861
- setImmediate(resolve);
862
- return;
863
- }
864
- // istanbul ignore next
865
- if (this._on_crl_process) {
866
- return reject(new Error("Internal Error"));
867
- }
868
- this._on_crl_process = resolve;
869
- });
870
- });
871
- }
872
- }
873
- exports.CertificateManager = CertificateManager;
874
- const opts = { multiArgs: false };
875
- CertificateManager.prototype.rejectCertificate = thenify.withCallback(CertificateManager.prototype.rejectCertificate, opts);
876
- CertificateManager.prototype.trustCertificate = thenify.withCallback(CertificateManager.prototype.trustCertificate, opts);
877
- CertificateManager.prototype.createSelfSignedCertificate = thenify.withCallback(CertificateManager.prototype.createSelfSignedCertificate, opts);
878
- CertificateManager.prototype.createCertificateRequest = thenify.withCallback(CertificateManager.prototype.createCertificateRequest, opts);
879
- CertificateManager.prototype.initialize = thenify.withCallback(CertificateManager.prototype.initialize, opts);
880
- CertificateManager.prototype.getCertificateStatus = thenify.withCallback(CertificateManager.prototype.getCertificateStatus, opts);
881
- CertificateManager.prototype._checkRejectedOrTrusted = thenify.withCallback(CertificateManager.prototype._checkRejectedOrTrusted, opts);
882
- CertificateManager.prototype.verifyCertificate = thenify.withCallback(CertificateManager.prototype.verifyCertificate, opts);
883
- CertificateManager.prototype.isCertificateTrusted = thenify.withCallback((0, util_1.callbackify)(CertificateManager.prototype.isCertificateTrusted), opts);
1
+ "use strict";
2
+ // ---------------------------------------------------------------------------------------------------------------------
3
+ // node-opcua
4
+ // ---------------------------------------------------------------------------------------------------------------------
5
+ // Copyright (c) 2014-2022 - Etienne Rossignon - etienne.rossignon (at) gadz.org
6
+ // Copyright (c) 2022 - Sterfive.com
7
+ // ---------------------------------------------------------------------------------------------------------------------
8
+ //
9
+ // This project is licensed under the terms of the MIT license.
10
+ //
11
+ // Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
12
+ // documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
13
+ // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
14
+ // permit persons to whom the Software is furnished to do so, subject to the following conditions:
15
+ //
16
+ // The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
17
+ // Software.
18
+ //
19
+ // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
20
+ // WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
21
+ // COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
22
+ // OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23
+ // ---------------------------------------------------------------------------------------------------------------------
24
+ // tslint:disable:no-shadowed-variable
25
+ // tslint:disable:member-ordering
26
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
27
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
28
+ return new (P || (P = Promise))(function (resolve, reject) {
29
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
30
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
31
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
32
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
33
+ });
34
+ };
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.CertificateManager = exports.CertificateManagerState = exports.VerificationStatus = void 0;
37
+ const assert = require("assert");
38
+ const async = require("async");
39
+ const chalk = require("chalk");
40
+ const chokidar = require("chokidar");
41
+ const fs = require("fs");
42
+ const path = require("path");
43
+ const util_1 = require("util");
44
+ const node_opcua_crypto_1 = require("node-opcua-crypto");
45
+ const toolbox_1 = require("./toolbox");
46
+ const global_mutex_1 = require("@ster5/global-mutex");
47
+ // tslint:disable:max-line-length
48
+ // tslint:disable:no-var-requires
49
+ // eslint-disable-next-line @typescript-eslint/no-var-requires
50
+ const thenify = require("thenify");
51
+ const fsFileExists = (0, util_1.promisify)(fs.exists);
52
+ const fsWriteFile = (0, util_1.promisify)(fs.writeFile);
53
+ const fsReadFile = (0, util_1.promisify)(fs.readFile);
54
+ const fsRemoveFile = (0, util_1.promisify)(fs.unlink);
55
+ var VerificationStatus;
56
+ (function (VerificationStatus) {
57
+ /** The certificate provided as a parameter is not valid. */
58
+ VerificationStatus["BadCertificateInvalid"] = "BadCertificateInvalid";
59
+ /** An error occurred verifying security. */
60
+ VerificationStatus["BadSecurityChecksFailed"] = "BadSecurityChecksFailed";
61
+ /** The certificate does not meet the requirements of the security policy. */
62
+ VerificationStatus["BadCertificatePolicyCheckFailed"] = "BadCertificatePolicyCheckFailed";
63
+ /** The certificate has expired or is not yet valid. */
64
+ VerificationStatus["BadCertificateTimeInvalid"] = "BadCertificateTimeInvalid";
65
+ /** An issuer certificate has expired or is not yet valid. */
66
+ VerificationStatus["BadCertificateIssuerTimeInvalid"] = "BadCertificateIssuerTimeInvalid";
67
+ /** The HostName used to connect to a server does not match a HostName in the certificate. */
68
+ VerificationStatus["BadCertificateHostNameInvalid"] = "BadCertificateHostNameInvalid";
69
+ /** The URI specified in the ApplicationDescription does not match the URI in the certificate. */
70
+ VerificationStatus["BadCertificateUriInvalid"] = "BadCertificateUriInvalid";
71
+ /** The certificate may not be used for the requested operation. */
72
+ VerificationStatus["BadCertificateUseNotAllowed"] = "BadCertificateUseNotAllowed";
73
+ /** The issuer certificate may not be used for the requested operation. */
74
+ VerificationStatus["BadCertificateIssuerUseNotAllowed"] = "BadCertificateIssuerUseNotAllowed";
75
+ /** The certificate is not trusted. */
76
+ VerificationStatus["BadCertificateUntrusted"] = "BadCertificateUntrusted";
77
+ /** It was not possible to determine if the certificate has been revoked. */
78
+ VerificationStatus["BadCertificateRevocationUnknown"] = "BadCertificateRevocationUnknown";
79
+ /** It was not possible to determine if the issuer certificate has been revoked. */
80
+ VerificationStatus["BadCertificateIssuerRevocationUnknown"] = "BadCertificateIssuerRevocationUnknown";
81
+ /** The certificate has been revoked. */
82
+ VerificationStatus["BadCertificateRevoked"] = "BadCertificateRevoked";
83
+ /** The issuer certificate has been revoked. */
84
+ VerificationStatus["BadCertificateIssuerRevoked"] = "BadCertificateIssuerRevoked";
85
+ /** The certificate chain is incomplete. */
86
+ VerificationStatus["BadCertificateChainIncomplete"] = "BadCertificateChainIncomplete";
87
+ /** Validation OK. */
88
+ VerificationStatus["Good"] = "Good";
89
+ })(VerificationStatus = exports.VerificationStatus || (exports.VerificationStatus = {}));
90
+ function makeFingerprint(certificate) {
91
+ return (0, node_opcua_crypto_1.makeSHA1Thumbprint)(certificate).toString("hex");
92
+ }
93
+ function short(stringToShorten) {
94
+ return stringToShorten.substr(0, 10);
95
+ }
96
+ function buildIdealCertificateName(certificate) {
97
+ const fingerprint = makeFingerprint(certificate);
98
+ try {
99
+ const commonName = (0, node_opcua_crypto_1.exploreCertificate)(certificate).tbsCertificate.subject.commonName || "";
100
+ return commonName + "[" + fingerprint + "]";
101
+ }
102
+ catch (err) {
103
+ // make be certificate is incorrect !
104
+ return "invalid_certificate_[" + fingerprint + "]";
105
+ }
106
+ }
107
+ function findMatchingIssuerKey(entries, wantedIssuerKey) {
108
+ const selected = entries.filter(({ certificate }) => {
109
+ const info = (0, node_opcua_crypto_1.exploreCertificate)(certificate);
110
+ return info.tbsCertificate.extensions && info.tbsCertificate.extensions.subjectKeyIdentifier === wantedIssuerKey;
111
+ });
112
+ return selected;
113
+ }
114
+ function isSelfSigned2(info) {
115
+ var _a, _b, _c;
116
+ return (((_a = info.tbsCertificate.extensions) === null || _a === void 0 ? void 0 : _a.subjectKeyIdentifier) ===
117
+ ((_c = (_b = info.tbsCertificate.extensions) === null || _b === void 0 ? void 0 : _b.authorityKeyIdentifier) === null || _c === void 0 ? void 0 : _c.keyIdentifier));
118
+ }
119
+ function isSelfSigned3(certificate) {
120
+ const info = (0, node_opcua_crypto_1.exploreCertificate)(certificate);
121
+ return isSelfSigned2(info);
122
+ }
123
+ var CertificateManagerState;
124
+ (function (CertificateManagerState) {
125
+ CertificateManagerState[CertificateManagerState["Uninitialized"] = 0] = "Uninitialized";
126
+ CertificateManagerState[CertificateManagerState["Initializing"] = 1] = "Initializing";
127
+ CertificateManagerState[CertificateManagerState["Initialized"] = 2] = "Initialized";
128
+ CertificateManagerState[CertificateManagerState["Disposing"] = 3] = "Disposing";
129
+ CertificateManagerState[CertificateManagerState["Disposed"] = 4] = "Disposed";
130
+ })(CertificateManagerState = exports.CertificateManagerState || (exports.CertificateManagerState = {}));
131
+ class CertificateManager {
132
+ constructor(options) {
133
+ this.untrustUnknownCertificate = true;
134
+ this.state = CertificateManagerState.Uninitialized;
135
+ this.folderPoolingInterval = 5000;
136
+ this._watchers = [];
137
+ this._readCertificatesCalled = false;
138
+ this._filenameToHash = {};
139
+ this._thumbs = {
140
+ rejected: {},
141
+ trusted: {},
142
+ issuers: {
143
+ certs: {},
144
+ },
145
+ crl: {},
146
+ issuersCrl: {},
147
+ };
148
+ this._pending_crl_to_process = 0;
149
+ this.queue = [];
150
+ options.keySize = options.keySize || 2048;
151
+ assert(Object.prototype.hasOwnProperty.call(options, "location"));
152
+ assert(Object.prototype.hasOwnProperty.call(options, "keySize"));
153
+ assert(this.state === CertificateManagerState.Uninitialized);
154
+ this.location = (0, toolbox_1.make_path)(options.location, "");
155
+ this.keySize = options.keySize;
156
+ (0, toolbox_1.mkdir)(options.location);
157
+ // istanbul ignore next
158
+ if (!fs.existsSync(this.location)) {
159
+ throw new Error("CertificateManager cannot access location " + this.location);
160
+ }
161
+ }
162
+ get configFile() {
163
+ return path.join(this.rootDir, "own/openssl.cnf");
164
+ }
165
+ get rootDir() {
166
+ return this.location;
167
+ }
168
+ get privateKey() {
169
+ return path.join(this.rootDir, "own/private/private_key.pem");
170
+ }
171
+ get randomFile() {
172
+ return path.join(this.rootDir, "./random.rnd");
173
+ }
174
+ getCertificateStatus(certificate, ...args) {
175
+ const callback = args[0];
176
+ this.initialize(() => {
177
+ this._checkRejectedOrTrusted(certificate, (err, status) => {
178
+ if (err) {
179
+ return callback(err);
180
+ }
181
+ if (status === "unknown") {
182
+ assert(certificate instanceof Buffer);
183
+ const pem = (0, node_opcua_crypto_1.toPem)(certificate, "CERTIFICATE");
184
+ const fingerprint = makeFingerprint(certificate);
185
+ const filename = path.join(this.rejectedFolder, buildIdealCertificateName(certificate) + ".pem");
186
+ fs.writeFile(filename, pem, (err) => {
187
+ this._thumbs.rejected[fingerprint] = {
188
+ certificate,
189
+ filename,
190
+ };
191
+ if (err) {
192
+ return callback(err);
193
+ }
194
+ status = "rejected";
195
+ return callback(null, status);
196
+ });
197
+ return;
198
+ }
199
+ else {
200
+ return callback(null, status);
201
+ }
202
+ });
203
+ });
204
+ }
205
+ rejectCertificate(certificate, ...args) {
206
+ const callback = args[0];
207
+ assert(callback && callback instanceof Function, "expecting callback");
208
+ this._moveCertificate(certificate, "rejected", callback);
209
+ }
210
+ trustCertificate(certificate, ...args) {
211
+ const callback = args[0];
212
+ assert(callback && callback instanceof Function, "expecting callback");
213
+ this._moveCertificate(certificate, "trusted", callback);
214
+ }
215
+ get rejectedFolder() {
216
+ return path.join(this.rootDir, "rejected");
217
+ }
218
+ get trustedFolder() {
219
+ return path.join(this.rootDir, "trusted/certs");
220
+ }
221
+ get crlFolder() {
222
+ return path.join(this.rootDir, "trusted/crl");
223
+ }
224
+ get issuersCertFolder() {
225
+ return path.join(this.rootDir, "issuers/certs");
226
+ }
227
+ get issuersCrlFolder() {
228
+ return path.join(this.rootDir, "issuers/crl");
229
+ }
230
+ isCertificateTrusted(certificate) {
231
+ var _a;
232
+ return __awaiter(this, void 0, void 0, function* () {
233
+ const fingerprint = makeFingerprint(certificate);
234
+ const certificateInTrust = (_a = this._thumbs.trusted[fingerprint]) === null || _a === void 0 ? void 0 : _a.certificate;
235
+ if (certificateInTrust) {
236
+ return "Good";
237
+ }
238
+ else {
239
+ const certificateInRejected = this._thumbs.rejected[fingerprint];
240
+ if (!certificateInRejected) {
241
+ const certificateFilenameInRejected = path.join(this.rejectedFolder, buildIdealCertificateName(certificate) + ".pem");
242
+ if (!this.untrustUnknownCertificate) {
243
+ return "Good";
244
+ }
245
+ // Certificate should be mark as untrusted
246
+ // let's first verify that certificate is valid ,as we don't want to write invalid data
247
+ try {
248
+ const certificateInfo = (0, node_opcua_crypto_1.exploreCertificateInfo)(certificate);
249
+ }
250
+ catch (err) {
251
+ return "BadCertificateInvalid";
252
+ }
253
+ (0, toolbox_1.debugLog)("certificate has never been seen before and is now rejected (untrusted) ", certificateFilenameInRejected);
254
+ yield fsWriteFile(certificateFilenameInRejected, (0, node_opcua_crypto_1.toPem)(certificate, "CERTIFICATE"));
255
+ }
256
+ return "BadCertificateUntrusted";
257
+ }
258
+ });
259
+ }
260
+ _innerVerifyCertificateAsync(certificate, isIssuer, level) {
261
+ var _a, _b;
262
+ return __awaiter(this, void 0, void 0, function* () {
263
+ if (level >= 5) {
264
+ // maximum level of certificate in chain reached !
265
+ return VerificationStatus.BadSecurityChecksFailed;
266
+ }
267
+ const chain = (0, node_opcua_crypto_1.split_der)(certificate);
268
+ (0, toolbox_1.debugLog)("NB CERTIFICATE IN CHAIN = ", chain.length);
269
+ const info = (0, node_opcua_crypto_1.exploreCertificate)(chain[0]);
270
+ let hasValidIssuer = false;
271
+ let hasTrustedIssuer = false;
272
+ // check if certificate is attached to a issuer
273
+ const hasIssuerKey = (_b = (_a = info.tbsCertificate.extensions) === null || _a === void 0 ? void 0 : _a.authorityKeyIdentifier) === null || _b === void 0 ? void 0 : _b.keyIdentifier;
274
+ (0, toolbox_1.debugLog)("Certificate as an Issuer Key", hasIssuerKey);
275
+ // console.log(inspect(info, { depth: 100 }));
276
+ if (hasIssuerKey) {
277
+ const isSelfSigned = isSelfSigned2(info);
278
+ (0, toolbox_1.debugLog)("Is the Certificate self-signed ?", isSelfSigned);
279
+ if (!isSelfSigned) {
280
+ const issuerCertificate = yield this.findIssuerCertificate(chain[0]);
281
+ // console.log("issuer is found", !!issuerCertificate, info.tbsCertificate.extensions.subjectKeyIdentifier, info.tbsCertificate.extensions?.authorityKeyIdentifier?.keyIdentifier);
282
+ if (!issuerCertificate) {
283
+ // if the certificate is not self-signed we ust have a issuer.
284
+ // but no Issuer is not found
285
+ return VerificationStatus.BadCertificateChainIncomplete;
286
+ }
287
+ const issuerStatus = yield this._innerVerifyCertificateAsync(issuerCertificate, true, level + 1);
288
+ if (issuerStatus === VerificationStatus.BadCertificateRevocationUnknown) {
289
+ // the issuer must have a CRL available .... !
290
+ return VerificationStatus.BadCertificateIssuerRevocationUnknown;
291
+ }
292
+ if (issuerStatus === VerificationStatus.BadCertificateIssuerRevocationUnknown) {
293
+ // the issuer must have a CRL available .... !
294
+ return VerificationStatus.BadCertificateIssuerRevocationUnknown;
295
+ }
296
+ if (issuerStatus === VerificationStatus.BadCertificateTimeInvalid) {
297
+ // the issuer must have valid dates ....
298
+ return VerificationStatus.BadCertificateIssuerTimeInvalid;
299
+ }
300
+ if (issuerStatus !== VerificationStatus.Good && issuerStatus !== VerificationStatus.BadCertificateUntrusted) {
301
+ // if the issuer has other issue => let's drop!
302
+ return VerificationStatus.BadSecurityChecksFailed;
303
+ }
304
+ // verify that certificate was signed by issuer
305
+ const isCertificateSignatureOK = (0, node_opcua_crypto_1.verifyCertificateSignature)(certificate, issuerCertificate);
306
+ if (!isCertificateSignatureOK) {
307
+ // the certificate was not signed by the issuer as it claim to be ! Danger
308
+ return VerificationStatus.BadSecurityChecksFailed;
309
+ }
310
+ hasValidIssuer = true;
311
+ // let detected if our certificate is in the revocation list
312
+ const revokedStatus = yield this.isCertificateRevoked(certificate);
313
+ if (revokedStatus === VerificationStatus.BadCertificateRevocationUnknown) {
314
+ return VerificationStatus.BadCertificateRevocationUnknown;
315
+ }
316
+ if (revokedStatus !== VerificationStatus.Good) {
317
+ // certificate is revoked !!!
318
+ (0, toolbox_1.debugLog)("revokedStatus", revokedStatus);
319
+ return revokedStatus;
320
+ }
321
+ // let check if the issuer is explicitly trusted
322
+ const issuerTrustedStatus = yield this._checkRejectedOrTrusted(issuerCertificate);
323
+ (0, toolbox_1.debugLog)("issuerTrustedStatus", issuerTrustedStatus);
324
+ if (issuerTrustedStatus === "unknown") {
325
+ hasTrustedIssuer = false;
326
+ }
327
+ else if (issuerTrustedStatus === "trusted") {
328
+ hasTrustedIssuer = true;
329
+ }
330
+ else if (issuerTrustedStatus === "rejected") {
331
+ // we should never get there: this should have been detected before !!!
332
+ return VerificationStatus.BadSecurityChecksFailed;
333
+ }
334
+ }
335
+ else {
336
+ // verify that certificate was signed by issuer (self in this case)
337
+ const isCertificateSignatureOK = (0, node_opcua_crypto_1.verifyCertificateSignature)(certificate, certificate);
338
+ if (!isCertificateSignatureOK) {
339
+ (0, toolbox_1.debugLog)("Self-signed Certificate signature is not valid");
340
+ return VerificationStatus.BadSecurityChecksFailed;
341
+ }
342
+ const revokedStatus = yield this.isCertificateRevoked(certificate);
343
+ (0, toolbox_1.debugLog)("revokedStatus of self signed certificate:", revokedStatus);
344
+ }
345
+ }
346
+ const status = yield this._checkRejectedOrTrusted(certificate);
347
+ if (status === "rejected") {
348
+ return VerificationStatus.BadCertificateUntrusted;
349
+ }
350
+ const c2 = chain[1] ? (0, node_opcua_crypto_1.exploreCertificateInfo)(chain[1]) : "non";
351
+ // Has SoftwareCertificate passed its issue date and has it not expired ?
352
+ // check dates
353
+ const certificateInfo = (0, node_opcua_crypto_1.exploreCertificateInfo)(certificate);
354
+ const now = new Date();
355
+ let isTimeInvalid = false;
356
+ // check that certificate is active
357
+ if (certificateInfo.notBefore.getTime() > now.getTime()) {
358
+ // certificate is not active yet
359
+ (0, toolbox_1.debugLog)(chalk.red("certificate is invalid : certificate is not active yet !") +
360
+ " not before date =" +
361
+ certificateInfo.notBefore);
362
+ isTimeInvalid = true;
363
+ }
364
+ // check that certificate has not expired
365
+ if (certificateInfo.notAfter.getTime() <= now.getTime()) {
366
+ // certificate is obsolete
367
+ (0, toolbox_1.debugLog)(chalk.red("certificate is invalid : certificate has expired !") + " not after date =" + certificateInfo.notAfter);
368
+ isTimeInvalid = true;
369
+ }
370
+ if (status === "trusted") {
371
+ return isTimeInvalid ? VerificationStatus.BadCertificateTimeInvalid : VerificationStatus.Good;
372
+ }
373
+ assert(status === "unknown");
374
+ if (hasIssuerKey) {
375
+ if (!hasTrustedIssuer) {
376
+ return VerificationStatus.BadCertificateUntrusted;
377
+ }
378
+ if (!hasValidIssuer) {
379
+ return VerificationStatus.BadCertificateUntrusted;
380
+ }
381
+ return isTimeInvalid ? VerificationStatus.BadCertificateTimeInvalid : VerificationStatus.Good;
382
+ }
383
+ else {
384
+ return VerificationStatus.BadCertificateUntrusted;
385
+ }
386
+ });
387
+ }
388
+ verifyCertificateAsync(certificate) {
389
+ return __awaiter(this, void 0, void 0, function* () {
390
+ const status1 = yield this._innerVerifyCertificateAsync(certificate, false, 0);
391
+ return status1;
392
+ });
393
+ }
394
+ verifyCertificate(certificate, callback) {
395
+ // Is the signature on the SoftwareCertificate valid .?
396
+ if (!certificate) {
397
+ // missing certificate
398
+ return callback(null, VerificationStatus.BadSecurityChecksFailed);
399
+ }
400
+ (0, util_1.callbackify)(this.verifyCertificateAsync).call(this, certificate, callback);
401
+ }
402
+ initialize(...args) {
403
+ const callback = args[0];
404
+ assert(callback && callback instanceof Function);
405
+ if (this.state !== CertificateManagerState.Uninitialized) {
406
+ return callback();
407
+ }
408
+ this.state = CertificateManagerState.Initializing;
409
+ return this._initialize((err) => {
410
+ this.state = CertificateManagerState.Initialized;
411
+ return callback(err);
412
+ });
413
+ }
414
+ _initialize(callback) {
415
+ assert((this.state = CertificateManagerState.Initializing));
416
+ const pkiDir = this.location;
417
+ (0, toolbox_1.mkdir)(pkiDir);
418
+ (0, toolbox_1.mkdir)(path.join(pkiDir, "own"));
419
+ (0, toolbox_1.mkdir)(path.join(pkiDir, "own/certs"));
420
+ (0, toolbox_1.mkdir)(path.join(pkiDir, "own/private"));
421
+ (0, toolbox_1.mkdir)(path.join(pkiDir, "rejected"));
422
+ (0, toolbox_1.mkdir)(path.join(pkiDir, "trusted"));
423
+ (0, toolbox_1.mkdir)(path.join(pkiDir, "trusted/certs"));
424
+ (0, toolbox_1.mkdir)(path.join(pkiDir, "trusted/crl"));
425
+ (0, toolbox_1.mkdir)(path.join(pkiDir, "issuers"));
426
+ (0, toolbox_1.mkdir)(path.join(pkiDir, "issuers/certs")); // contains Trusted CA certificates
427
+ (0, toolbox_1.mkdir)(path.join(pkiDir, "issuers/crl")); // contains CRL of revoked CA certificates
428
+ this.withLock((callback) => {
429
+ assert(this.state !== CertificateManagerState.Disposing);
430
+ if (this.state === CertificateManagerState.Disposed) {
431
+ return callback();
432
+ }
433
+ assert(this.state === CertificateManagerState.Initializing);
434
+ fs.writeFileSync(this.configFile, toolbox_1.configurationFileSimpleTemplate);
435
+ // note : openssl 1.1.1 has a bug that causes a failure if
436
+ // random file cannot be found. (should be fixed in 1.1.1.a)
437
+ // if this issue become important we may have to consider checking that rndFile exists and recreate
438
+ // it if not . this could be achieved with the command :
439
+ // "openssl rand -writerand ${this.randomFile}"
440
+ //
441
+ // cf: https://github.com/node-opcua/node-opcua/issues/554
442
+ if (!fs.existsSync(this.privateKey)) {
443
+ (0, toolbox_1.debugLog)("generating private key ...");
444
+ (0, toolbox_1.setEnv)("RANDFILE", this.randomFile);
445
+ (0, toolbox_1.createPrivateKey)(this.privateKey, this.keySize, (err) => {
446
+ if (err) {
447
+ return callback(err);
448
+ }
449
+ this._readCertificates(() => callback());
450
+ });
451
+ }
452
+ else {
453
+ // debugLog(" initialize : private key already exists ... skipping");
454
+ this._readCertificates(() => callback());
455
+ }
456
+ }, callback);
457
+ }
458
+ dispose() {
459
+ return __awaiter(this, void 0, void 0, function* () {
460
+ if (this.state === CertificateManagerState.Disposing) {
461
+ throw new Error("Already disposing");
462
+ }
463
+ if (this.state === CertificateManagerState.Uninitialized) {
464
+ this.state = CertificateManagerState.Disposed;
465
+ return;
466
+ }
467
+ // wait for initialization to be completed
468
+ if (this.state === CertificateManagerState.Initializing) {
469
+ yield new Promise((resolve) => setTimeout(resolve, 100));
470
+ return yield this.dispose();
471
+ }
472
+ try {
473
+ this.state = CertificateManagerState.Disposing;
474
+ yield Promise.all(this._watchers.map((w) => w.close()));
475
+ this._watchers.forEach((w) => w.removeAllListeners());
476
+ this._watchers.splice(0);
477
+ }
478
+ finally {
479
+ this.state = CertificateManagerState.Disposed;
480
+ }
481
+ });
482
+ }
483
+ // eslint-disable-next-line @typescript-eslint/ban-types
484
+ withLock(action, callback) {
485
+ this.withLock2((0, util_1.promisify)(action))
486
+ .then((t) => callback(null, t))
487
+ .catch((err) => callback(err));
488
+ }
489
+ withLock2(action) {
490
+ return __awaiter(this, void 0, void 0, function* () {
491
+ const lockFileName = path.join(this.rootDir, "mutex.lock");
492
+ return (0, global_mutex_1.withLock)({ lockfile: lockFileName }, () => __awaiter(this, void 0, void 0, function* () {
493
+ return yield action();
494
+ }));
495
+ });
496
+ }
497
+ createSelfSignedCertificate(params, ...args) {
498
+ const callback = args[0];
499
+ assert(typeof params.applicationUri === "string", "expecting applicationUri");
500
+ if (!fs.existsSync(this.privateKey)) {
501
+ return callback(new Error("Cannot find private key " + this.privateKey));
502
+ }
503
+ let certificateFilename = path.join(this.rootDir, "own/certs/self_signed_certificate.pem");
504
+ certificateFilename = params.outputFile || certificateFilename;
505
+ const _params = params;
506
+ _params.rootDir = this.rootDir;
507
+ _params.configFile = this.configFile;
508
+ _params.privateKey = this.privateKey;
509
+ this.withLock((callback) => {
510
+ (0, toolbox_1.createSelfSignCertificate)(certificateFilename, _params, callback);
511
+ }, callback);
512
+ }
513
+ createCertificateRequest(params, callback) {
514
+ assert(params);
515
+ const _params = params;
516
+ if (Object.prototype.hasOwnProperty.call(_params, "rootDir")) {
517
+ throw new Error("rootDir should not be specified ");
518
+ }
519
+ assert(typeof callback === "function");
520
+ assert(!_params.rootDir);
521
+ assert(!_params.configFile);
522
+ assert(!_params.privateKey);
523
+ _params.rootDir = this.rootDir;
524
+ _params.configFile = this.configFile;
525
+ _params.privateKey = this.privateKey;
526
+ this.withLock((callback) => {
527
+ // compose a file name for the request
528
+ const now = new Date();
529
+ const today = now.toISOString().slice(0, 10) + "_" + now.getTime();
530
+ const certificateSigningRequestFilename = path.join(this.rootDir, "own/certs", "certificate_" + today + ".csr");
531
+ (0, toolbox_1.createCertificateSigningRequest)(certificateSigningRequestFilename, _params, (err) => {
532
+ if (err) {
533
+ return callback(err);
534
+ }
535
+ return callback(null, certificateSigningRequestFilename);
536
+ });
537
+ }, callback);
538
+ }
539
+ addIssuer(certificate, validate = false, addInTrustList = false) {
540
+ return __awaiter(this, void 0, void 0, function* () {
541
+ if (validate) {
542
+ const status = yield this.verifyCertificate(certificate);
543
+ if (status !== VerificationStatus.Good && status !== VerificationStatus.BadCertificateUntrusted) {
544
+ return status;
545
+ }
546
+ }
547
+ const pemCertificate = (0, node_opcua_crypto_1.toPem)(certificate, "CERTIFICATE");
548
+ const fingerprint = makeFingerprint(certificate);
549
+ if (this._thumbs.issuers.certs[fingerprint]) {
550
+ // already in .. simply ignore
551
+ return VerificationStatus.Good;
552
+ }
553
+ // write certificate
554
+ const filename = path.join(this.issuersCertFolder, "issuer_" + buildIdealCertificateName(certificate) + ".pem");
555
+ yield (0, util_1.promisify)(fs.writeFile)(filename, pemCertificate, "ascii");
556
+ // first time seen, let's save it.
557
+ this._thumbs.issuers.certs[fingerprint] = { certificate, filename };
558
+ if (addInTrustList) {
559
+ // add certificate in the trust list as well
560
+ yield this.trustCertificate(certificate);
561
+ }
562
+ return VerificationStatus.Good;
563
+ });
564
+ }
565
+ addRevocationList(crl) {
566
+ return __awaiter(this, void 0, void 0, function* () {
567
+ return this.withLock2(() => __awaiter(this, void 0, void 0, function* () {
568
+ try {
569
+ const crlInfo = (0, node_opcua_crypto_1.exploreCertificateRevocationList)(crl);
570
+ const key = crlInfo.tbsCertList.issuerFingerprint;
571
+ if (!this._thumbs.issuersCrl[key]) {
572
+ this._thumbs.issuersCrl[key] = { crls: [], serialNumbers: {} };
573
+ }
574
+ const pemCertificate = (0, node_opcua_crypto_1.toPem)(crl, "X509 CRL");
575
+ const filename = path.join(this.issuersCrlFolder, "crl_" + buildIdealCertificateName(crl) + ".pem");
576
+ yield (0, util_1.promisify)(fs.writeFile)(filename, pemCertificate, "ascii");
577
+ yield this._on_crl_file_added(this._thumbs.issuersCrl, filename);
578
+ yield this.waitAndCheckCRLProcessingStatus();
579
+ return VerificationStatus.Good;
580
+ }
581
+ catch (err) {
582
+ (0, toolbox_1.debugLog)(err);
583
+ return VerificationStatus.BadSecurityChecksFailed;
584
+ }
585
+ }));
586
+ });
587
+ }
588
+ // find the issuer certificate
589
+ findIssuerCertificate(certificate) {
590
+ var _a, _b;
591
+ return __awaiter(this, void 0, void 0, function* () {
592
+ const certInfo = (0, node_opcua_crypto_1.exploreCertificate)(certificate);
593
+ // istanbul ignore next
594
+ if (isSelfSigned2(certInfo)) {
595
+ // the certificate is self signed so is it's own issuer.
596
+ return certificate;
597
+ }
598
+ const wantedIssuerKey = (_b = (_a = certInfo.tbsCertificate.extensions) === null || _a === void 0 ? void 0 : _a.authorityKeyIdentifier) === null || _b === void 0 ? void 0 : _b.keyIdentifier;
599
+ // istanbul ignore next
600
+ if (!wantedIssuerKey) {
601
+ // Certificate has no extension 3 ! the certificate might have been generated by an old system
602
+ (0, toolbox_1.debugLog)("Certificate has no extension 3");
603
+ return null;
604
+ }
605
+ const issuerCertificates = Object.values(this._thumbs.issuers.certs);
606
+ const selectedIssuerCertificates = findMatchingIssuerKey(issuerCertificates, wantedIssuerKey);
607
+ if (selectedIssuerCertificates.length > 0) {
608
+ if (selectedIssuerCertificates.length > 1) {
609
+ // tslint:disable-next-line: no-console
610
+ console.log("Warning more than one issuer certificate exists with subjectKeyIdentifier ", wantedIssuerKey);
611
+ }
612
+ return selectedIssuerCertificates[0].certificate || null;
613
+ }
614
+ // check also in trusted list
615
+ const trustedCertificates = Object.values(this._thumbs.trusted);
616
+ const selectedTrustedCertificates = findMatchingIssuerKey(trustedCertificates, wantedIssuerKey);
617
+ // istanbul ignore next
618
+ if (selectedTrustedCertificates.length > 1) {
619
+ // tslint:disable-next-line: no-console
620
+ console.log("Warning more than one certificate exists with subjectKeyIdentifier in trusted certificate list ", wantedIssuerKey, selectedTrustedCertificates.length);
621
+ for (const entry of selectedTrustedCertificates) {
622
+ (0, toolbox_1.dumpCertificate)(entry.filename, (err, data) => {
623
+ (0, toolbox_1.debugLog)(" ", entry.filename);
624
+ (0, toolbox_1.debugLog)(data);
625
+ });
626
+ }
627
+ }
628
+ return selectedTrustedCertificates.length > 0 ? selectedTrustedCertificates[0].certificate : null;
629
+ });
630
+ }
631
+ _checkRejectedOrTrusted(certificate, ...args) {
632
+ const callback = args[0];
633
+ assert(callback && callback instanceof Function);
634
+ assert(certificate instanceof Buffer);
635
+ const fingerprint = makeFingerprint(certificate);
636
+ (0, toolbox_1.debugLog)("_checkRejectedOrTrusted fingerprint ", short(fingerprint));
637
+ this._readCertificates((err) => {
638
+ // istanbul ignore next
639
+ if (err) {
640
+ return callback(err);
641
+ }
642
+ if (Object.prototype.hasOwnProperty.call(this._thumbs.rejected, fingerprint)) {
643
+ return callback(null, "rejected");
644
+ }
645
+ if (Object.prototype.hasOwnProperty.call(this._thumbs.trusted, fingerprint)) {
646
+ return callback(null, "trusted");
647
+ }
648
+ return callback(null, "unknown");
649
+ });
650
+ }
651
+ _moveCertificate(certificate, newStatus, callback) {
652
+ // a mutex is requested here
653
+ assert(certificate instanceof Buffer);
654
+ const fingerprint = makeFingerprint(certificate);
655
+ this.getCertificateStatus(certificate, (err, status) => {
656
+ var _a;
657
+ // istanbul ignore next
658
+ if (err) {
659
+ return callback(err);
660
+ }
661
+ (0, toolbox_1.debugLog)("_moveCertificate", fingerprint.substr(0, 10), "from", status, "to", newStatus);
662
+ assert(status === "rejected" || status === "trusted");
663
+ if (status !== newStatus) {
664
+ const certificateSrc = (_a = this._thumbs[status][fingerprint]) === null || _a === void 0 ? void 0 : _a.filename;
665
+ // istanbul ignore next
666
+ if (!certificateSrc) {
667
+ (0, toolbox_1.debugLog)(" cannot find certificate ", fingerprint.substr(0, 10), " in", this._thumbs, [status]);
668
+ return callback(new Error("internal"));
669
+ }
670
+ const destFolder = newStatus === "rejected"
671
+ ? this.rejectedFolder
672
+ : newStatus === "trusted"
673
+ ? this.trustedFolder
674
+ : this.rejectedFolder;
675
+ const certificateDest = path.join(destFolder, path.basename(certificateSrc));
676
+ (0, toolbox_1.debugLog)("_moveCertificate1", fingerprint.substr(0, 10), "old name", certificateSrc);
677
+ (0, toolbox_1.debugLog)("_moveCertificate1", fingerprint.substr(0, 10), "new name", certificateDest);
678
+ fs.rename(certificateSrc, certificateDest, (err) => {
679
+ delete this._thumbs[status][fingerprint];
680
+ this._thumbs[newStatus][fingerprint] = {
681
+ certificate,
682
+ filename: certificateDest,
683
+ };
684
+ // we do not return the error here
685
+ return callback( /*err*/);
686
+ });
687
+ }
688
+ else {
689
+ return callback();
690
+ }
691
+ });
692
+ }
693
+ _findAssociatedCRLs(issuerCertificate) {
694
+ const issuerCertificateInfo = (0, node_opcua_crypto_1.exploreCertificate)(issuerCertificate);
695
+ const key = issuerCertificateInfo.tbsCertificate.subjectFingerPrint;
696
+ return this._thumbs.issuersCrl[key] ? this._thumbs.issuersCrl[key] : this._thumbs.crl[key] ? this._thumbs.crl[key] : null;
697
+ }
698
+ isCertificateRevoked(certificate, issuerCertificate) {
699
+ var _a, _b, _c, _d;
700
+ return __awaiter(this, void 0, void 0, function* () {
701
+ // istanbul ignore next
702
+ if (isSelfSigned3(certificate)) {
703
+ return VerificationStatus.Good;
704
+ }
705
+ if (!issuerCertificate) {
706
+ issuerCertificate = yield this.findIssuerCertificate(certificate);
707
+ }
708
+ if (!issuerCertificate) {
709
+ return VerificationStatus.BadCertificateChainIncomplete;
710
+ }
711
+ const crls = this._findAssociatedCRLs(issuerCertificate);
712
+ if (!crls) {
713
+ return VerificationStatus.BadCertificateRevocationUnknown;
714
+ }
715
+ const certInfo = (0, node_opcua_crypto_1.exploreCertificate)(certificate);
716
+ const serialNumber = certInfo.tbsCertificate.serialNumber || ((_b = (_a = certInfo.tbsCertificate.extensions) === null || _a === void 0 ? void 0 : _a.authorityKeyIdentifier) === null || _b === void 0 ? void 0 : _b.serial) || "";
717
+ const key = ((_d = (_c = certInfo.tbsCertificate.extensions) === null || _c === void 0 ? void 0 : _c.authorityKeyIdentifier) === null || _d === void 0 ? void 0 : _d.authorityCertIssuerFingerPrint) || "<unknown>";
718
+ const crl2 = this._thumbs.crl[key] || null;
719
+ if (crls.serialNumbers[serialNumber] || (crl2 && crl2.serialNumbers[serialNumber])) {
720
+ return VerificationStatus.BadCertificateRevoked;
721
+ }
722
+ return VerificationStatus.Good;
723
+ });
724
+ }
725
+ _on_crl_file_added(index, filename) {
726
+ this.queue.push({ index, filename });
727
+ this._pending_crl_to_process += 1;
728
+ if (this._pending_crl_to_process === 1) {
729
+ this._process_next_crl();
730
+ }
731
+ }
732
+ _process_next_crl() {
733
+ return __awaiter(this, void 0, void 0, function* () {
734
+ try {
735
+ const { index, filename } = this.queue.shift();
736
+ const crl = yield (0, node_opcua_crypto_1.readCertificateRevocationList)(filename);
737
+ const crlInfo = (0, node_opcua_crypto_1.exploreCertificateRevocationList)(crl);
738
+ (0, toolbox_1.debugLog)(chalk.cyan("add CRL in folder "), filename); // stat);
739
+ const fingerprint = crlInfo.tbsCertList.issuerFingerprint;
740
+ index[fingerprint] = index[fingerprint] || {
741
+ crls: [],
742
+ serialNumbers: {},
743
+ };
744
+ index[fingerprint].crls.push({ crlInfo, filename });
745
+ const serialNumbers = index[fingerprint].serialNumbers;
746
+ // now inject serial numbers
747
+ for (const revokedCertificate of crlInfo.tbsCertList.revokedCertificates) {
748
+ const serialNumber = revokedCertificate.userCertificate;
749
+ if (!serialNumbers[serialNumber]) {
750
+ serialNumbers[serialNumber] = revokedCertificate.revocationDate;
751
+ }
752
+ }
753
+ (0, toolbox_1.debugLog)(chalk.cyan("CRL"), fingerprint, "serial numbers = ", Object.keys(serialNumbers)); // stat);
754
+ }
755
+ catch (err) {
756
+ (0, toolbox_1.debugLog)("CRL filename error =");
757
+ (0, toolbox_1.debugLog)(err);
758
+ }
759
+ this._pending_crl_to_process -= 1;
760
+ if (this._pending_crl_to_process === 0) {
761
+ if (this._on_crl_process) {
762
+ this._on_crl_process();
763
+ this._on_crl_process = undefined;
764
+ }
765
+ }
766
+ else {
767
+ this._process_next_crl();
768
+ }
769
+ });
770
+ }
771
+ _readCertificates(callback) {
772
+ if (this._readCertificatesCalled) {
773
+ return callback();
774
+ }
775
+ this._readCertificatesCalled = true;
776
+ const options = {
777
+ usePolling: true,
778
+ interval: Math.min(10 * 60 * 1000, Math.max(100, this.folderPoolingInterval)),
779
+ persistent: false,
780
+ awaitWriteFinish: {
781
+ stabilityThreshold: 2000,
782
+ pollInterval: 600,
783
+ },
784
+ };
785
+ function _walkCRLFiles(folder, index, _innerCallback) {
786
+ const w = chokidar.watch(folder, options);
787
+ w.on("unlink", (filename, stat) => {
788
+ // CRL never removed
789
+ });
790
+ w.on("add", (filename, stat) => {
791
+ this._on_crl_file_added(index, filename);
792
+ });
793
+ w.on("change", (path, stat) => {
794
+ (0, toolbox_1.debugLog)("change in folder ", folder, path, stat);
795
+ });
796
+ this._watchers.push(w);
797
+ w.on("ready", () => {
798
+ _innerCallback();
799
+ });
800
+ }
801
+ function _walkAllFiles(folder, index, _innerCallback) {
802
+ const w = chokidar.watch(folder, options);
803
+ w.on("unlink", (filename, stat) => {
804
+ (0, toolbox_1.debugLog)(chalk.cyan("unlink in folder " + folder), filename);
805
+ const h = this._filenameToHash[filename];
806
+ if (h && index[h]) {
807
+ delete index[h];
808
+ }
809
+ });
810
+ w.on("add", (filename, stat) => {
811
+ var _a, _b;
812
+ (0, toolbox_1.debugLog)(chalk.cyan("add in folder " + folder), filename); // stat);
813
+ try {
814
+ const certificate = (0, node_opcua_crypto_1.readCertificate)(filename);
815
+ const info = (0, node_opcua_crypto_1.exploreCertificate)(certificate);
816
+ const fingerprint = makeFingerprint(certificate);
817
+ index[fingerprint] = {
818
+ certificate,
819
+ filename,
820
+ };
821
+ this._filenameToHash[filename] = fingerprint;
822
+ (0, toolbox_1.debugLog)(chalk.magenta("CERT"), info.tbsCertificate.subjectFingerPrint, info.tbsCertificate.serialNumber, (_b = (_a = info.tbsCertificate.extensions) === null || _a === void 0 ? void 0 : _a.authorityKeyIdentifier) === null || _b === void 0 ? void 0 : _b.authorityCertIssuerFingerPrint);
823
+ }
824
+ catch (err) {
825
+ (0, toolbox_1.debugLog)("Walk files in folder " + folder + " with file " + filename);
826
+ (0, toolbox_1.debugLog)(err);
827
+ }
828
+ });
829
+ w.on("change", (path, stat) => {
830
+ (0, toolbox_1.debugLog)("change in folder ", folder, path);
831
+ });
832
+ this._watchers.push(w);
833
+ w.on("ready", () => {
834
+ _innerCallback();
835
+ (0, toolbox_1.debugLog)("ready");
836
+ (0, toolbox_1.debugLog)(Object.entries(index).map((kv) => kv[0].substr(0, 10)));
837
+ });
838
+ }
839
+ async.parallelLimit([
840
+ _walkAllFiles.bind(this, this.trustedFolder, this._thumbs.trusted),
841
+ _walkAllFiles.bind(this, this.issuersCertFolder, this._thumbs.issuers.certs),
842
+ _walkAllFiles.bind(this, this.rejectedFolder, this._thumbs.rejected),
843
+ _walkCRLFiles.bind(this, this.crlFolder, this._thumbs.crl),
844
+ _walkCRLFiles.bind(this, this.issuersCrlFolder, this._thumbs.issuersCrl),
845
+ ], 2, (err) => {
846
+ // istanbul ignore next
847
+ if (err) {
848
+ (0, toolbox_1.debugLog)("Error=", err);
849
+ return callback(err);
850
+ }
851
+ this.waitAndCheckCRLProcessingStatus()
852
+ .then(() => callback())
853
+ .catch((err) => callback(err));
854
+ });
855
+ }
856
+ // make sure that all crls have been processed.
857
+ waitAndCheckCRLProcessingStatus() {
858
+ return __awaiter(this, void 0, void 0, function* () {
859
+ return new Promise((resolve, reject) => {
860
+ if (this._pending_crl_to_process === 0) {
861
+ setImmediate(resolve);
862
+ return;
863
+ }
864
+ // istanbul ignore next
865
+ if (this._on_crl_process) {
866
+ return reject(new Error("Internal Error"));
867
+ }
868
+ this._on_crl_process = resolve;
869
+ });
870
+ });
871
+ }
872
+ }
873
+ exports.CertificateManager = CertificateManager;
874
+ const opts = { multiArgs: false };
875
+ CertificateManager.prototype.rejectCertificate = thenify.withCallback(CertificateManager.prototype.rejectCertificate, opts);
876
+ CertificateManager.prototype.trustCertificate = thenify.withCallback(CertificateManager.prototype.trustCertificate, opts);
877
+ CertificateManager.prototype.createSelfSignedCertificate = thenify.withCallback(CertificateManager.prototype.createSelfSignedCertificate, opts);
878
+ CertificateManager.prototype.createCertificateRequest = thenify.withCallback(CertificateManager.prototype.createCertificateRequest, opts);
879
+ CertificateManager.prototype.initialize = thenify.withCallback(CertificateManager.prototype.initialize, opts);
880
+ CertificateManager.prototype.getCertificateStatus = thenify.withCallback(CertificateManager.prototype.getCertificateStatus, opts);
881
+ CertificateManager.prototype._checkRejectedOrTrusted = thenify.withCallback(CertificateManager.prototype._checkRejectedOrTrusted, opts);
882
+ CertificateManager.prototype.verifyCertificate = thenify.withCallback(CertificateManager.prototype.verifyCertificate, opts);
883
+ CertificateManager.prototype.isCertificateTrusted = thenify.withCallback((0, util_1.callbackify)(CertificateManager.prototype.isCertificateTrusted), opts);
884
884
  //# sourceMappingURL=certificate_manager.js.map