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.
- package/.ignore +6 -6
- package/.prettierrc +5 -5
- package/LICENSE +22 -22
- package/bin/crypto_create_CA.js +0 -0
- package/bin/crypto_create_CA_config.example.js +18 -18
- package/bin/install_prerequisite.js +9 -9
- package/dist/crypto_create_CA.d.ts +2 -2
- package/dist/crypto_create_CA.js +897 -897
- package/dist/index.d.ts +6 -6
- package/dist/index.js +44 -44
- package/dist/misc/applicationurn.d.ts +1 -1
- package/dist/misc/applicationurn.js +46 -46
- package/dist/misc/hostname.d.ts +8 -8
- package/dist/misc/hostname.js +102 -102
- package/dist/misc/install_prerequisite.d.ts +9 -9
- package/dist/misc/install_prerequisite.js +363 -360
- package/dist/misc/install_prerequisite.js.map +1 -1
- package/dist/misc/subject.d.ts +26 -26
- package/dist/misc/subject.js +121 -121
- package/dist/pki/certificate_authority.d.ts +61 -61
- package/dist/pki/certificate_authority.js +481 -481
- package/dist/pki/certificate_manager.d.ts +144 -144
- package/dist/pki/certificate_manager.js +883 -883
- package/dist/pki/certificate_manager.js.map +1 -1
- package/dist/pki/common.d.ts +5 -5
- package/dist/pki/common.js +2 -2
- package/dist/pki/templates/ca_config_template.cnf.d.ts +2 -2
- package/dist/pki/templates/ca_config_template.cnf.js +129 -129
- package/dist/pki/templates/simple_config_template.cnf.d.ts +2 -2
- package/dist/pki/templates/simple_config_template.cnf.js +75 -75
- package/dist/pki/toolbox.d.ts +160 -160
- package/dist/pki/toolbox.js +699 -699
- package/dist/pki/toolbox_pfx.js +18 -18
- package/lib/crypto_create_CA.ts +1135 -1135
- package/lib/index.ts +28 -28
- package/lib/misc/applicationurn.ts +45 -45
- package/lib/misc/hostname.ts +89 -89
- package/lib/misc/install_prerequisite.ts +454 -454
- package/lib/misc/subject.ts +141 -141
- package/lib/pki/certificate_manager.ts +1 -1
- package/lib/pki/common.ts +5 -5
- package/lib/pki/templates/ca_config_template.cnf.ts +129 -129
- package/lib/pki/templates/simple_config_template.cnf.ts +75 -75
- package/lib/pki/toolbox_pfx.ts +19 -19
- package/package.json +89 -89
- package/readme.md +214 -214
- package/tsconfig.json +20 -20
- package/dist/misc/fs.d.ts +0 -24
- package/dist/misc/fs.js +0 -21
- package/dist/misc/fs.js.map +0 -1
- package/dist/misc/get_default_filesystem.d.ts +0 -2
- package/dist/misc/get_default_filesystem.js +0 -9
- package/dist/misc/get_default_filesystem.js.map +0 -1
|
@@ -1,482 +1,482 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.CertificateAuthority = exports.defaultSubject = void 0;
|
|
4
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
5
|
-
// node-opcua
|
|
6
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
7
|
-
// Copyright (c) 2014-2022 - Etienne Rossignon - etienne.rossignon (at) gadz.org
|
|
8
|
-
// Copyright (c) 2022 - Sterfive.com
|
|
9
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
10
|
-
//
|
|
11
|
-
// This project is licensed under the terms of the MIT license.
|
|
12
|
-
//
|
|
13
|
-
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
|
14
|
-
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
|
15
|
-
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
|
16
|
-
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
17
|
-
//
|
|
18
|
-
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
|
19
|
-
// Software.
|
|
20
|
-
//
|
|
21
|
-
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
22
|
-
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
23
|
-
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
24
|
-
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
25
|
-
// ---------------------------------------------------------------------------------------------------------------------
|
|
26
|
-
// tslint:disable:no-shadowed-variable
|
|
27
|
-
const assert = require("assert");
|
|
28
|
-
const async = require("async");
|
|
29
|
-
const chalk = require("chalk");
|
|
30
|
-
const fs = require("fs");
|
|
31
|
-
const path = require("path");
|
|
32
|
-
const toolbox_1 = require("./toolbox");
|
|
33
|
-
const subject_1 = require("../misc/subject");
|
|
34
|
-
const node_opcua_crypto_1 = require("node-opcua-crypto");
|
|
35
|
-
exports.defaultSubject = "/C=FR/ST=IDF/L=Paris/O=Local NODE-OPCUA Certificate Authority/CN=NodeOPCUA-CA";
|
|
36
|
-
const config = {
|
|
37
|
-
certificateDir: "INVALID",
|
|
38
|
-
forceCA: false,
|
|
39
|
-
pkiDir: "INVALID",
|
|
40
|
-
};
|
|
41
|
-
const n = toolbox_1.make_path;
|
|
42
|
-
const q = toolbox_1.quote;
|
|
43
|
-
// convert 'c07b9179' to "192.123.145.121"
|
|
44
|
-
function octetStringToIpAddress(a) {
|
|
45
|
-
return (parseInt(a.substr(0, 2), 16).toString() +
|
|
46
|
-
"." +
|
|
47
|
-
parseInt(a.substr(2, 2), 16).toString() +
|
|
48
|
-
"." +
|
|
49
|
-
parseInt(a.substr(4, 2), 16).toString() +
|
|
50
|
-
"." +
|
|
51
|
-
parseInt(a.substr(6, 2), 16).toString());
|
|
52
|
-
}
|
|
53
|
-
function construct_CertificateAuthority(certificateAuthority, callback) {
|
|
54
|
-
// create the CA directory store
|
|
55
|
-
// create the CA directory store
|
|
56
|
-
//
|
|
57
|
-
// PKI/CA
|
|
58
|
-
// |
|
|
59
|
-
// +-+> private
|
|
60
|
-
// |
|
|
61
|
-
// +-+> public
|
|
62
|
-
// |
|
|
63
|
-
// +-+> certs
|
|
64
|
-
// |
|
|
65
|
-
// +-+> crl
|
|
66
|
-
// |
|
|
67
|
-
// +-+> conf
|
|
68
|
-
// |
|
|
69
|
-
// +-f: serial
|
|
70
|
-
// +-f: crlNumber
|
|
71
|
-
// +-f: index.txt
|
|
72
|
-
//
|
|
73
|
-
const subject = certificateAuthority.subject;
|
|
74
|
-
const caRootDir = certificateAuthority.rootDir;
|
|
75
|
-
function make_folders() {
|
|
76
|
-
(0, toolbox_1.mkdir)(caRootDir);
|
|
77
|
-
(0, toolbox_1.mkdir)(path.join(caRootDir, "private"));
|
|
78
|
-
(0, toolbox_1.mkdir)(path.join(caRootDir, "public"));
|
|
79
|
-
// xx execute("chmod 700 private");
|
|
80
|
-
(0, toolbox_1.mkdir)(path.join(caRootDir, "certs"));
|
|
81
|
-
(0, toolbox_1.mkdir)(path.join(caRootDir, "crl"));
|
|
82
|
-
(0, toolbox_1.mkdir)(path.join(caRootDir, "conf"));
|
|
83
|
-
}
|
|
84
|
-
make_folders();
|
|
85
|
-
function construct_default_files() {
|
|
86
|
-
const serial = path.join(caRootDir, "serial");
|
|
87
|
-
if (!fs.existsSync(serial)) {
|
|
88
|
-
fs.writeFileSync(serial, "1000");
|
|
89
|
-
}
|
|
90
|
-
const crlNumber = path.join(caRootDir, "crlnumber");
|
|
91
|
-
if (!fs.existsSync(crlNumber)) {
|
|
92
|
-
fs.writeFileSync(crlNumber, "1000");
|
|
93
|
-
}
|
|
94
|
-
const indexFile = path.join(caRootDir, "index.txt");
|
|
95
|
-
if (!fs.existsSync(indexFile)) {
|
|
96
|
-
fs.writeFileSync(indexFile, "");
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
construct_default_files();
|
|
100
|
-
if (fs.existsSync(path.join(caRootDir, "private/cakey.pem")) && !config.forceCA) {
|
|
101
|
-
// certificate already exists => do not overwrite
|
|
102
|
-
(0, toolbox_1.debugLog)("CA private key already exists ... skipping");
|
|
103
|
-
return callback();
|
|
104
|
-
}
|
|
105
|
-
// tslint:disable:no-empty
|
|
106
|
-
(0, toolbox_1.displayTitle)("Create Certificate Authority (CA)", (_err) => { });
|
|
107
|
-
const indexFileAttr = path.join(caRootDir, "index.txt.attr");
|
|
108
|
-
if (!fs.existsSync(indexFileAttr)) {
|
|
109
|
-
fs.writeFileSync(indexFileAttr, "unique_subject = no");
|
|
110
|
-
}
|
|
111
|
-
const caConfigFile = certificateAuthority.configFile;
|
|
112
|
-
// eslint-disable-next-line no-constant-condition
|
|
113
|
-
if (1 || !fs.existsSync(caConfigFile)) {
|
|
114
|
-
let data = toolbox_1.configurationFileTemplate; // inlineText(configurationFile);
|
|
115
|
-
data = data.replace(/%%ROOT_FOLDER%%/, (0, toolbox_1.make_path)(caRootDir));
|
|
116
|
-
fs.writeFileSync(caConfigFile, data);
|
|
117
|
-
}
|
|
118
|
-
// http://www.akadia.com/services/ssh_test_certificate.html
|
|
119
|
-
const subjectOpt = " -subj \"" + subject.toString() + "\" ";
|
|
120
|
-
const options = { cwd: caRootDir };
|
|
121
|
-
(0, toolbox_1.processAltNames)({});
|
|
122
|
-
const configFile = (0, toolbox_1.generateStaticConfig)("conf/caconfig.cnf", options);
|
|
123
|
-
const configOption = " -config " + q(n(configFile));
|
|
124
|
-
const keySize = certificateAuthority.keySize;
|
|
125
|
-
const randomFile = "random.rnd";
|
|
126
|
-
const tasks = [
|
|
127
|
-
(callback) => (0, toolbox_1.displayTitle)("Creating random file random.rnd", callback),
|
|
128
|
-
(callback) => (0, toolbox_1.createRandomFileIfNotExist)(randomFile, options, callback),
|
|
129
|
-
(callback) => (0, toolbox_1.displayTitle)("Generate the CA private Key - " + keySize, callback),
|
|
130
|
-
// The first step is to create your RSA Private Key.
|
|
131
|
-
// This key is a 1025,2048,3072 or 2038 bit RSA key which is encrypted using
|
|
132
|
-
// Triple-DES and stored in a PEM format so that it is readable as ASCII text.
|
|
133
|
-
(callback) => (0, toolbox_1.execute_openssl)("genrsa " + " -out private/cakey.pem" + ((0, toolbox_1.useRandFile)() ? " -rand " + randomFile : "") + " " + keySize, options, callback),
|
|
134
|
-
(callback) => (0, toolbox_1.displayTitle)("Generate a certificate request for the CA key", callback),
|
|
135
|
-
// Once the private key is generated a Certificate Signing Request can be generated.
|
|
136
|
-
// The CSR is then used in one of two ways. Ideally, the CSR will be sent to a Certificate Authority, such as
|
|
137
|
-
// Thawte or Verisign who will verify the identity of the requestor and issue a signed certificate.
|
|
138
|
-
// The second option is to self-sign the CSR, which will be demonstrated in the next section
|
|
139
|
-
(callback) => (0, toolbox_1.execute_openssl)("req -new" +
|
|
140
|
-
" -sha256 " +
|
|
141
|
-
" -text " +
|
|
142
|
-
" -extensions v3_ca" +
|
|
143
|
-
configOption +
|
|
144
|
-
" -key private/cakey.pem " +
|
|
145
|
-
" -out private/cakey.csr " +
|
|
146
|
-
subjectOpt, options, callback),
|
|
147
|
-
// xx // Step 3: Remove Passphrase from Key
|
|
148
|
-
// xx execute("cp private/cakey.pem private/cakey.pem.org");
|
|
149
|
-
// xx execute(openssl_path + " rsa -in private/cakey.pem.org -out private/cakey.pem -passin pass:"+paraphrase);
|
|
150
|
-
(callback) => (0, toolbox_1.displayTitle)("Generate CA Certificate (self-signed)", callback),
|
|
151
|
-
(callback) => (0, toolbox_1.execute_openssl)(" x509 -sha256 -req -days 3650 " +
|
|
152
|
-
" -text " +
|
|
153
|
-
" -extensions v3_ca" +
|
|
154
|
-
" -extfile " +
|
|
155
|
-
q(n(configFile)) +
|
|
156
|
-
" -in private/cakey.csr " +
|
|
157
|
-
" -signkey private/cakey.pem " +
|
|
158
|
-
" -out public/cacert.pem", options, callback),
|
|
159
|
-
(callback) => (0, toolbox_1.displaySubtitle)("generate initial CRL (Certificate Revocation List)", callback),
|
|
160
|
-
(callback) => regenerateCrl(certificateAuthority.revocationList, configOption, options, callback),
|
|
161
|
-
(callback) => (0, toolbox_1.displayTitle)("Create Certificate Authority (CA) ---> DONE", callback),
|
|
162
|
-
];
|
|
163
|
-
async.series(tasks, callback);
|
|
164
|
-
}
|
|
165
|
-
function regenerateCrl(revocationList, configOption, options, callback) {
|
|
166
|
-
const tasks = [
|
|
167
|
-
(callback) => (0, toolbox_1.displaySubtitle)("regenerate CRL (Certificate Revocation List)", callback),
|
|
168
|
-
(callback) =>
|
|
169
|
-
// produce a CRL in PEM format
|
|
170
|
-
(0, toolbox_1.execute_openssl)("ca -gencrl " + configOption + " -out crl/revocation_list.crl", options, callback),
|
|
171
|
-
(callback) => (0, toolbox_1.execute_openssl)("crl " + " -in crl/revocation_list.crl -out crl/revocation_list.der " + " -outform der", options, callback),
|
|
172
|
-
(callback) => (0, toolbox_1.displaySubtitle)("Display (Certificate Revocation List)", callback),
|
|
173
|
-
(callback) => (0, toolbox_1.execute_openssl)("crl " + " -in " + q(n(revocationList)) + " -text " + " -noout", options, callback),
|
|
174
|
-
];
|
|
175
|
-
async.series(tasks, callback);
|
|
176
|
-
}
|
|
177
|
-
class CertificateAuthority {
|
|
178
|
-
constructor(options) {
|
|
179
|
-
assert(Object.prototype.hasOwnProperty.call(options, "location"));
|
|
180
|
-
assert(Object.prototype.hasOwnProperty.call(options, "keySize"));
|
|
181
|
-
this.location = options.location;
|
|
182
|
-
this.keySize = options.keySize || 2048;
|
|
183
|
-
this.subject = new subject_1.Subject(options.subject || exports.defaultSubject);
|
|
184
|
-
}
|
|
185
|
-
get rootDir() {
|
|
186
|
-
return this.location;
|
|
187
|
-
}
|
|
188
|
-
get configFile() {
|
|
189
|
-
return path.normalize(path.join(this.rootDir, "./conf/caconfig.cnf"));
|
|
190
|
-
}
|
|
191
|
-
get caCertificate() {
|
|
192
|
-
// the Certificate Authority Certificate
|
|
193
|
-
return (0, toolbox_1.make_path)(this.rootDir, "./public/cacert.pem");
|
|
194
|
-
}
|
|
195
|
-
/**
|
|
196
|
-
* the file name where the current Certificate Revocation List is stored (in DER format)
|
|
197
|
-
*/
|
|
198
|
-
get revocationListDER() {
|
|
199
|
-
return (0, toolbox_1.make_path)(this.rootDir, "./crl/revocation_list.der");
|
|
200
|
-
}
|
|
201
|
-
/**
|
|
202
|
-
* the file name where the current Certificate Revocation List is stored (in PEM format)
|
|
203
|
-
*/
|
|
204
|
-
get revocationList() {
|
|
205
|
-
return (0, toolbox_1.make_path)(this.rootDir, "./crl/revocation_list.crl");
|
|
206
|
-
}
|
|
207
|
-
get caCertificateWithCrl() {
|
|
208
|
-
return (0, toolbox_1.make_path)(this.rootDir, "./public/cacertificate_with_crl.pem");
|
|
209
|
-
}
|
|
210
|
-
initialize(callback) {
|
|
211
|
-
assert(typeof callback === "function");
|
|
212
|
-
construct_CertificateAuthority(this, callback);
|
|
213
|
-
}
|
|
214
|
-
constructCACertificateWithCRL(callback) {
|
|
215
|
-
assert(typeof callback === "function");
|
|
216
|
-
const cacertWithCRL = this.caCertificateWithCrl;
|
|
217
|
-
// note : in order to check if the certificate is revoked,
|
|
218
|
-
// you need to specify -crl_check and have both the CA cert and the (applicable) CRL in your trust store.
|
|
219
|
-
// There are two ways to do that:
|
|
220
|
-
// 1. concatenate cacert.pem and crl.pem into one file and use that for -CAfile.
|
|
221
|
-
// 2. use some linked
|
|
222
|
-
// ( from http://security.stackexchange.com/a/58305/59982)
|
|
223
|
-
if (fs.existsSync(this.revocationList)) {
|
|
224
|
-
fs.writeFileSync(cacertWithCRL, fs.readFileSync(this.caCertificate, "utf8") + fs.readFileSync(this.revocationList, "utf8"));
|
|
225
|
-
}
|
|
226
|
-
else {
|
|
227
|
-
// there is no revocation list yet
|
|
228
|
-
fs.writeFileSync(cacertWithCRL, fs.readFileSync(this.caCertificate));
|
|
229
|
-
}
|
|
230
|
-
callback();
|
|
231
|
-
}
|
|
232
|
-
constructCertificateChain(certificate, callback) {
|
|
233
|
-
assert(typeof callback === "function");
|
|
234
|
-
assert(fs.existsSync(certificate));
|
|
235
|
-
assert(fs.existsSync(this.caCertificate));
|
|
236
|
-
(0, toolbox_1.debugLog)(chalk.yellow(" certificate file :"), chalk.cyan(certificate));
|
|
237
|
-
// append
|
|
238
|
-
fs.writeFileSync(certificate, fs.readFileSync(certificate, "utf8") + fs.readFileSync(this.caCertificate, "utf8")
|
|
239
|
-
// + fs.readFileSync(this.revocationList)
|
|
240
|
-
);
|
|
241
|
-
callback();
|
|
242
|
-
}
|
|
243
|
-
createSelfSignedCertificate(certificateFile, privateKey, params, callback) {
|
|
244
|
-
assert(typeof privateKey === "string");
|
|
245
|
-
assert(fs.existsSync(privateKey));
|
|
246
|
-
assert(typeof callback === "function");
|
|
247
|
-
if (!(0, toolbox_1.certificateFileExist)(certificateFile)) {
|
|
248
|
-
return callback();
|
|
249
|
-
}
|
|
250
|
-
(0, toolbox_1.adjustDate)(params);
|
|
251
|
-
(0, toolbox_1.adjustApplicationUri)(params);
|
|
252
|
-
(0, toolbox_1.processAltNames)(params);
|
|
253
|
-
const csrFile = certificateFile + "_csr";
|
|
254
|
-
assert(csrFile);
|
|
255
|
-
const configFile = (0, toolbox_1.generateStaticConfig)(this.configFile);
|
|
256
|
-
const options = {
|
|
257
|
-
cwd: this.rootDir,
|
|
258
|
-
openssl_conf: (0, toolbox_1.make_path)(configFile),
|
|
259
|
-
};
|
|
260
|
-
const configOption = "";
|
|
261
|
-
const subject = params.subject ? new subject_1.Subject(params.subject).toString() : "";
|
|
262
|
-
const subjectOptions = subject && subject.length > 1 ? " -subj " + subject + " " : "";
|
|
263
|
-
const tasks = [];
|
|
264
|
-
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- the certificate signing request", callback));
|
|
265
|
-
tasks.push((callback) => (0, toolbox_1.execute_openssl)("req " +
|
|
266
|
-
" -new -sha256 -text " +
|
|
267
|
-
configOption +
|
|
268
|
-
subjectOptions +
|
|
269
|
-
" -batch -key " +
|
|
270
|
-
q(n(privateKey)) +
|
|
271
|
-
" -out " +
|
|
272
|
-
q(n(csrFile)), options, callback));
|
|
273
|
-
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- creating the self-signed certificate", callback));
|
|
274
|
-
tasks.push((callback) => (0, toolbox_1.execute_openssl)("ca " +
|
|
275
|
-
" -selfsign " +
|
|
276
|
-
" -keyfile " +
|
|
277
|
-
q(n(privateKey)) +
|
|
278
|
-
" -startdate " +
|
|
279
|
-
(0, toolbox_1.x509Date)(params.startDate) +
|
|
280
|
-
" -enddate " +
|
|
281
|
-
(0, toolbox_1.x509Date)(params.endDate) +
|
|
282
|
-
" -batch -out " +
|
|
283
|
-
q(n(certificateFile)) +
|
|
284
|
-
" -in " +
|
|
285
|
-
q(n(csrFile)), options, callback));
|
|
286
|
-
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- dump the certificate for a check", callback));
|
|
287
|
-
tasks.push((callback) => (0, toolbox_1.execute_openssl)("x509 -in " + q(n(certificateFile)) + " -dates -fingerprint -purpose -noout", {}, callback));
|
|
288
|
-
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- verify self-signed certificate", callback));
|
|
289
|
-
tasks.push((callback) => (0, toolbox_1.execute_openssl_no_failure)("verify -verbose -CAfile " + q(n(certificateFile)) + " " + q(n(certificateFile)), options, callback));
|
|
290
|
-
tasks.push((callback) => fs.unlink(csrFile, callback));
|
|
291
|
-
async.series(tasks, (err) => {
|
|
292
|
-
callback(err);
|
|
293
|
-
});
|
|
294
|
-
}
|
|
295
|
-
revokeCertificate(certificate, params, callback) {
|
|
296
|
-
assert(typeof callback === "function");
|
|
297
|
-
const crlReasons = [
|
|
298
|
-
"unspecified",
|
|
299
|
-
"keyCompromise",
|
|
300
|
-
"CACompromise",
|
|
301
|
-
"affiliationChanged",
|
|
302
|
-
"superseded",
|
|
303
|
-
"cessationOfOperation",
|
|
304
|
-
"certificateHold",
|
|
305
|
-
"removeFromCRL",
|
|
306
|
-
];
|
|
307
|
-
const configFile = (0, toolbox_1.generateStaticConfig)("conf/caconfig.cnf", { cwd: this.rootDir });
|
|
308
|
-
const options = {
|
|
309
|
-
cwd: this.rootDir,
|
|
310
|
-
openssl_conf: (0, toolbox_1.make_path)(configFile),
|
|
311
|
-
};
|
|
312
|
-
(0, toolbox_1.setEnv)("ALTNAME", "");
|
|
313
|
-
const randomFile = path.join(this.rootDir, "random.rnd");
|
|
314
|
-
(0, toolbox_1.setEnv)("RANDFILE", randomFile);
|
|
315
|
-
// // tslint:disable-next-line:no-string-literal
|
|
316
|
-
// if (!fs.existsSync((process.env as any)["OPENSSL_CONF"])) {
|
|
317
|
-
// throw new Error("Cannot find OPENSSL_CONF");
|
|
318
|
-
// }
|
|
319
|
-
const configOption = " -config " + q(n(configFile));
|
|
320
|
-
const reason = params.reason || "keyCompromise";
|
|
321
|
-
assert(crlReasons.indexOf(reason) >= 0);
|
|
322
|
-
const tasks = [
|
|
323
|
-
(callback) => (0, toolbox_1.displayTitle)("Revoking certificate " + certificate, callback),
|
|
324
|
-
(callback) => (0, toolbox_1.displaySubtitle)("Make sure random file exists" + randomFile, callback),
|
|
325
|
-
(callback) => (0, toolbox_1.createRandomFileIfNotExist)(randomFile, {}, callback),
|
|
326
|
-
(callback) => (0, toolbox_1.displaySubtitle)("Revoke certificate", callback),
|
|
327
|
-
(callback) => {
|
|
328
|
-
(0, toolbox_1.execute_openssl_no_failure)("ca -verbose " + configOption + " -revoke " + q(certificate) + " -crl_reason " + reason, options, callback);
|
|
329
|
-
},
|
|
330
|
-
// regenerate CRL (Certificate Revocation List)
|
|
331
|
-
(callback) => regenerateCrl(this.revocationList, configOption, options, callback),
|
|
332
|
-
(callback) => (0, toolbox_1.displaySubtitle)("Verify that certificate is revoked ", callback),
|
|
333
|
-
(callback) => {
|
|
334
|
-
(0, toolbox_1.execute_openssl_no_failure)("verify -verbose" +
|
|
335
|
-
// configOption +
|
|
336
|
-
" -CRLfile " +
|
|
337
|
-
q(n(this.revocationList)) +
|
|
338
|
-
" -CAfile " +
|
|
339
|
-
q(n(this.caCertificate)) +
|
|
340
|
-
" -crl_check " +
|
|
341
|
-
q(n(certificate)), options, (err, output) => {
|
|
342
|
-
callback();
|
|
343
|
-
});
|
|
344
|
-
},
|
|
345
|
-
// produce CRL in DER format
|
|
346
|
-
(callback) => (0, toolbox_1.displaySubtitle)("Produce CRL in DER form ", callback),
|
|
347
|
-
(callback) => (0, toolbox_1.execute_openssl)("crl " + " -in " + q(n(this.revocationList)) + " -out " + "crl/revocation_list.der " + " -outform der", options, callback),
|
|
348
|
-
// produce CRL in PEM format with text
|
|
349
|
-
(callback) => (0, toolbox_1.displaySubtitle)("Produce CRL in PEM form ", callback),
|
|
350
|
-
(callback) => (0, toolbox_1.execute_openssl)("crl " +
|
|
351
|
-
" -in " +
|
|
352
|
-
q(n(this.revocationList)) +
|
|
353
|
-
" -out " +
|
|
354
|
-
"crl/revocation_list.pem " +
|
|
355
|
-
" -outform pem" +
|
|
356
|
-
" -text ", options, callback),
|
|
357
|
-
];
|
|
358
|
-
async.series(tasks, callback);
|
|
359
|
-
}
|
|
360
|
-
signCertificateRequest(certificate, certificateSigningRequestFilename, params, callback) {
|
|
361
|
-
// istanbul ignore next
|
|
362
|
-
if (!callback) {
|
|
363
|
-
throw new Error("Internal Error");
|
|
364
|
-
}
|
|
365
|
-
(0, toolbox_1.ensure_openssl_installed)(() => {
|
|
366
|
-
try {
|
|
367
|
-
assert(fs.existsSync(certificateSigningRequestFilename));
|
|
368
|
-
assert(typeof callback === "function");
|
|
369
|
-
if (!(0, toolbox_1.certificateFileExist)(certificate)) {
|
|
370
|
-
return callback(null);
|
|
371
|
-
}
|
|
372
|
-
(0, toolbox_1.adjustDate)(params);
|
|
373
|
-
(0, toolbox_1.adjustApplicationUri)(params);
|
|
374
|
-
(0, toolbox_1.processAltNames)(params);
|
|
375
|
-
const options = { cwd: this.rootDir };
|
|
376
|
-
let configFile;
|
|
377
|
-
const tasks = [];
|
|
378
|
-
let csrInfo;
|
|
379
|
-
// note :
|
|
380
|
-
// subjectAltName is not copied across
|
|
381
|
-
// see https://github.com/openssl/openssl/issues/10458
|
|
382
|
-
tasks.push((callback) => {
|
|
383
|
-
(0, node_opcua_crypto_1.readCertificateSigningRequest)(certificateSigningRequestFilename)
|
|
384
|
-
.then((csr) => {
|
|
385
|
-
csrInfo = (0, node_opcua_crypto_1.exploreCertificateSigningRequest)(csr);
|
|
386
|
-
callback();
|
|
387
|
-
})
|
|
388
|
-
.catch((err) => callback(err));
|
|
389
|
-
});
|
|
390
|
-
tasks.push((callback) => {
|
|
391
|
-
const applicationUri = csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier[0];
|
|
392
|
-
if (typeof applicationUri !== "string") {
|
|
393
|
-
return callback(new Error("Cannot find applicationUri in CSR"));
|
|
394
|
-
}
|
|
395
|
-
const dns = csrInfo.extensionRequest.subjectAltName.dNSName || [];
|
|
396
|
-
let ip = csrInfo.extensionRequest.subjectAltName.iPAddress || [];
|
|
397
|
-
ip = ip.map(octetStringToIpAddress);
|
|
398
|
-
const params = {
|
|
399
|
-
applicationUri,
|
|
400
|
-
dns,
|
|
401
|
-
ip,
|
|
402
|
-
};
|
|
403
|
-
(0, toolbox_1.processAltNames)(params);
|
|
404
|
-
configFile = (0, toolbox_1.generateStaticConfig)("conf/caconfig.cnf", options);
|
|
405
|
-
callback();
|
|
406
|
-
});
|
|
407
|
-
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- then we ask the authority to sign the certificate signing request", callback));
|
|
408
|
-
tasks.push((callback) => {
|
|
409
|
-
const configOption = " -config " + configFile;
|
|
410
|
-
(0, toolbox_1.execute_openssl)("ca " +
|
|
411
|
-
configOption +
|
|
412
|
-
" -startdate " +
|
|
413
|
-
(0, toolbox_1.x509Date)(params.startDate) +
|
|
414
|
-
" -enddate " +
|
|
415
|
-
(0, toolbox_1.x509Date)(params.endDate) +
|
|
416
|
-
" -batch -out " +
|
|
417
|
-
q(n(certificate)) +
|
|
418
|
-
" -in " +
|
|
419
|
-
q(n(certificateSigningRequestFilename)), options, callback);
|
|
420
|
-
});
|
|
421
|
-
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- dump the certificate for a check", callback));
|
|
422
|
-
tasks.push((callback) => (0, toolbox_1.execute_openssl)("x509 -in " + q(n(certificate)) + " -dates -fingerprint -purpose -noout", options, callback));
|
|
423
|
-
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- construct CA certificate with CRL", callback));
|
|
424
|
-
tasks.push((callback) => {
|
|
425
|
-
this.constructCACertificateWithCRL(callback);
|
|
426
|
-
});
|
|
427
|
-
// construct certificate chain
|
|
428
|
-
// concatenate certificate with CA Certificate and revocation list
|
|
429
|
-
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- construct certificate chain", callback));
|
|
430
|
-
tasks.push((callback) => {
|
|
431
|
-
this.constructCertificateChain(certificate, callback);
|
|
432
|
-
});
|
|
433
|
-
// todo
|
|
434
|
-
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- verify certificate against the root CA", callback));
|
|
435
|
-
tasks.push((callback) => {
|
|
436
|
-
this.verifyCertificate(certificate, callback);
|
|
437
|
-
});
|
|
438
|
-
async.series(tasks, (err) => {
|
|
439
|
-
// istanbul ignore next
|
|
440
|
-
if (err) {
|
|
441
|
-
return callback(err);
|
|
442
|
-
}
|
|
443
|
-
callback(null, certificate);
|
|
444
|
-
});
|
|
445
|
-
}
|
|
446
|
-
catch (err) {
|
|
447
|
-
callback(err);
|
|
448
|
-
}
|
|
449
|
-
});
|
|
450
|
-
}
|
|
451
|
-
verifyCertificate(certificate, callback) {
|
|
452
|
-
// openssl verify crashes on windows! we cannot use it reliably
|
|
453
|
-
// istanbul ignore next
|
|
454
|
-
const isImplemented = false;
|
|
455
|
-
// istanbul ignore next
|
|
456
|
-
if (isImplemented) {
|
|
457
|
-
const options = { cwd: this.rootDir };
|
|
458
|
-
const configFile = (0, toolbox_1.generateStaticConfig)("conf/caconfig.cnf", options);
|
|
459
|
-
(0, toolbox_1.setEnv)("OPENSSL_CONF", (0, toolbox_1.make_path)(configFile));
|
|
460
|
-
const configOption = " -config " + configFile;
|
|
461
|
-
(0, toolbox_1.execute_openssl_no_failure)("verify -verbose " + " -CAfile " + q(n(this.caCertificateWithCrl)) + " " + q(n(certificate)), options, (err) => {
|
|
462
|
-
callback(err ? err : undefined);
|
|
463
|
-
});
|
|
464
|
-
}
|
|
465
|
-
else {
|
|
466
|
-
return callback();
|
|
467
|
-
}
|
|
468
|
-
}
|
|
469
|
-
}
|
|
470
|
-
exports.CertificateAuthority = CertificateAuthority;
|
|
471
|
-
// tslint:disable:no-var-requires
|
|
472
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
473
|
-
const thenify = require("thenify");
|
|
474
|
-
const opts = { multiArgs: false };
|
|
475
|
-
CertificateAuthority.prototype.initialize = thenify.withCallback(CertificateAuthority.prototype.initialize, opts);
|
|
476
|
-
CertificateAuthority.prototype.constructCACertificateWithCRL = thenify.withCallback(CertificateAuthority.prototype.constructCACertificateWithCRL, opts);
|
|
477
|
-
CertificateAuthority.prototype.constructCertificateChain = thenify.withCallback(CertificateAuthority.prototype.constructCertificateChain, opts);
|
|
478
|
-
CertificateAuthority.prototype.createSelfSignedCertificate = thenify.withCallback(CertificateAuthority.prototype.createSelfSignedCertificate, opts);
|
|
479
|
-
CertificateAuthority.prototype.revokeCertificate = thenify.withCallback(CertificateAuthority.prototype.revokeCertificate, opts);
|
|
480
|
-
CertificateAuthority.prototype.verifyCertificate = thenify.withCallback(CertificateAuthority.prototype.verifyCertificate, opts);
|
|
481
|
-
CertificateAuthority.prototype.signCertificateRequest = thenify.withCallback(CertificateAuthority.prototype.signCertificateRequest, opts);
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.CertificateAuthority = exports.defaultSubject = void 0;
|
|
4
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
5
|
+
// node-opcua
|
|
6
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
7
|
+
// Copyright (c) 2014-2022 - Etienne Rossignon - etienne.rossignon (at) gadz.org
|
|
8
|
+
// Copyright (c) 2022 - Sterfive.com
|
|
9
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
10
|
+
//
|
|
11
|
+
// This project is licensed under the terms of the MIT license.
|
|
12
|
+
//
|
|
13
|
+
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
|
14
|
+
// documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
|
|
15
|
+
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
|
|
16
|
+
// permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
17
|
+
//
|
|
18
|
+
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
|
|
19
|
+
// Software.
|
|
20
|
+
//
|
|
21
|
+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
|
|
22
|
+
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
23
|
+
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
|
|
24
|
+
// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
25
|
+
// ---------------------------------------------------------------------------------------------------------------------
|
|
26
|
+
// tslint:disable:no-shadowed-variable
|
|
27
|
+
const assert = require("assert");
|
|
28
|
+
const async = require("async");
|
|
29
|
+
const chalk = require("chalk");
|
|
30
|
+
const fs = require("fs");
|
|
31
|
+
const path = require("path");
|
|
32
|
+
const toolbox_1 = require("./toolbox");
|
|
33
|
+
const subject_1 = require("../misc/subject");
|
|
34
|
+
const node_opcua_crypto_1 = require("node-opcua-crypto");
|
|
35
|
+
exports.defaultSubject = "/C=FR/ST=IDF/L=Paris/O=Local NODE-OPCUA Certificate Authority/CN=NodeOPCUA-CA";
|
|
36
|
+
const config = {
|
|
37
|
+
certificateDir: "INVALID",
|
|
38
|
+
forceCA: false,
|
|
39
|
+
pkiDir: "INVALID",
|
|
40
|
+
};
|
|
41
|
+
const n = toolbox_1.make_path;
|
|
42
|
+
const q = toolbox_1.quote;
|
|
43
|
+
// convert 'c07b9179' to "192.123.145.121"
|
|
44
|
+
function octetStringToIpAddress(a) {
|
|
45
|
+
return (parseInt(a.substr(0, 2), 16).toString() +
|
|
46
|
+
"." +
|
|
47
|
+
parseInt(a.substr(2, 2), 16).toString() +
|
|
48
|
+
"." +
|
|
49
|
+
parseInt(a.substr(4, 2), 16).toString() +
|
|
50
|
+
"." +
|
|
51
|
+
parseInt(a.substr(6, 2), 16).toString());
|
|
52
|
+
}
|
|
53
|
+
function construct_CertificateAuthority(certificateAuthority, callback) {
|
|
54
|
+
// create the CA directory store
|
|
55
|
+
// create the CA directory store
|
|
56
|
+
//
|
|
57
|
+
// PKI/CA
|
|
58
|
+
// |
|
|
59
|
+
// +-+> private
|
|
60
|
+
// |
|
|
61
|
+
// +-+> public
|
|
62
|
+
// |
|
|
63
|
+
// +-+> certs
|
|
64
|
+
// |
|
|
65
|
+
// +-+> crl
|
|
66
|
+
// |
|
|
67
|
+
// +-+> conf
|
|
68
|
+
// |
|
|
69
|
+
// +-f: serial
|
|
70
|
+
// +-f: crlNumber
|
|
71
|
+
// +-f: index.txt
|
|
72
|
+
//
|
|
73
|
+
const subject = certificateAuthority.subject;
|
|
74
|
+
const caRootDir = certificateAuthority.rootDir;
|
|
75
|
+
function make_folders() {
|
|
76
|
+
(0, toolbox_1.mkdir)(caRootDir);
|
|
77
|
+
(0, toolbox_1.mkdir)(path.join(caRootDir, "private"));
|
|
78
|
+
(0, toolbox_1.mkdir)(path.join(caRootDir, "public"));
|
|
79
|
+
// xx execute("chmod 700 private");
|
|
80
|
+
(0, toolbox_1.mkdir)(path.join(caRootDir, "certs"));
|
|
81
|
+
(0, toolbox_1.mkdir)(path.join(caRootDir, "crl"));
|
|
82
|
+
(0, toolbox_1.mkdir)(path.join(caRootDir, "conf"));
|
|
83
|
+
}
|
|
84
|
+
make_folders();
|
|
85
|
+
function construct_default_files() {
|
|
86
|
+
const serial = path.join(caRootDir, "serial");
|
|
87
|
+
if (!fs.existsSync(serial)) {
|
|
88
|
+
fs.writeFileSync(serial, "1000");
|
|
89
|
+
}
|
|
90
|
+
const crlNumber = path.join(caRootDir, "crlnumber");
|
|
91
|
+
if (!fs.existsSync(crlNumber)) {
|
|
92
|
+
fs.writeFileSync(crlNumber, "1000");
|
|
93
|
+
}
|
|
94
|
+
const indexFile = path.join(caRootDir, "index.txt");
|
|
95
|
+
if (!fs.existsSync(indexFile)) {
|
|
96
|
+
fs.writeFileSync(indexFile, "");
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
construct_default_files();
|
|
100
|
+
if (fs.existsSync(path.join(caRootDir, "private/cakey.pem")) && !config.forceCA) {
|
|
101
|
+
// certificate already exists => do not overwrite
|
|
102
|
+
(0, toolbox_1.debugLog)("CA private key already exists ... skipping");
|
|
103
|
+
return callback();
|
|
104
|
+
}
|
|
105
|
+
// tslint:disable:no-empty
|
|
106
|
+
(0, toolbox_1.displayTitle)("Create Certificate Authority (CA)", (_err) => { });
|
|
107
|
+
const indexFileAttr = path.join(caRootDir, "index.txt.attr");
|
|
108
|
+
if (!fs.existsSync(indexFileAttr)) {
|
|
109
|
+
fs.writeFileSync(indexFileAttr, "unique_subject = no");
|
|
110
|
+
}
|
|
111
|
+
const caConfigFile = certificateAuthority.configFile;
|
|
112
|
+
// eslint-disable-next-line no-constant-condition
|
|
113
|
+
if (1 || !fs.existsSync(caConfigFile)) {
|
|
114
|
+
let data = toolbox_1.configurationFileTemplate; // inlineText(configurationFile);
|
|
115
|
+
data = data.replace(/%%ROOT_FOLDER%%/, (0, toolbox_1.make_path)(caRootDir));
|
|
116
|
+
fs.writeFileSync(caConfigFile, data);
|
|
117
|
+
}
|
|
118
|
+
// http://www.akadia.com/services/ssh_test_certificate.html
|
|
119
|
+
const subjectOpt = " -subj \"" + subject.toString() + "\" ";
|
|
120
|
+
const options = { cwd: caRootDir };
|
|
121
|
+
(0, toolbox_1.processAltNames)({});
|
|
122
|
+
const configFile = (0, toolbox_1.generateStaticConfig)("conf/caconfig.cnf", options);
|
|
123
|
+
const configOption = " -config " + q(n(configFile));
|
|
124
|
+
const keySize = certificateAuthority.keySize;
|
|
125
|
+
const randomFile = "random.rnd";
|
|
126
|
+
const tasks = [
|
|
127
|
+
(callback) => (0, toolbox_1.displayTitle)("Creating random file random.rnd", callback),
|
|
128
|
+
(callback) => (0, toolbox_1.createRandomFileIfNotExist)(randomFile, options, callback),
|
|
129
|
+
(callback) => (0, toolbox_1.displayTitle)("Generate the CA private Key - " + keySize, callback),
|
|
130
|
+
// The first step is to create your RSA Private Key.
|
|
131
|
+
// This key is a 1025,2048,3072 or 2038 bit RSA key which is encrypted using
|
|
132
|
+
// Triple-DES and stored in a PEM format so that it is readable as ASCII text.
|
|
133
|
+
(callback) => (0, toolbox_1.execute_openssl)("genrsa " + " -out private/cakey.pem" + ((0, toolbox_1.useRandFile)() ? " -rand " + randomFile : "") + " " + keySize, options, callback),
|
|
134
|
+
(callback) => (0, toolbox_1.displayTitle)("Generate a certificate request for the CA key", callback),
|
|
135
|
+
// Once the private key is generated a Certificate Signing Request can be generated.
|
|
136
|
+
// The CSR is then used in one of two ways. Ideally, the CSR will be sent to a Certificate Authority, such as
|
|
137
|
+
// Thawte or Verisign who will verify the identity of the requestor and issue a signed certificate.
|
|
138
|
+
// The second option is to self-sign the CSR, which will be demonstrated in the next section
|
|
139
|
+
(callback) => (0, toolbox_1.execute_openssl)("req -new" +
|
|
140
|
+
" -sha256 " +
|
|
141
|
+
" -text " +
|
|
142
|
+
" -extensions v3_ca" +
|
|
143
|
+
configOption +
|
|
144
|
+
" -key private/cakey.pem " +
|
|
145
|
+
" -out private/cakey.csr " +
|
|
146
|
+
subjectOpt, options, callback),
|
|
147
|
+
// xx // Step 3: Remove Passphrase from Key
|
|
148
|
+
// xx execute("cp private/cakey.pem private/cakey.pem.org");
|
|
149
|
+
// xx execute(openssl_path + " rsa -in private/cakey.pem.org -out private/cakey.pem -passin pass:"+paraphrase);
|
|
150
|
+
(callback) => (0, toolbox_1.displayTitle)("Generate CA Certificate (self-signed)", callback),
|
|
151
|
+
(callback) => (0, toolbox_1.execute_openssl)(" x509 -sha256 -req -days 3650 " +
|
|
152
|
+
" -text " +
|
|
153
|
+
" -extensions v3_ca" +
|
|
154
|
+
" -extfile " +
|
|
155
|
+
q(n(configFile)) +
|
|
156
|
+
" -in private/cakey.csr " +
|
|
157
|
+
" -signkey private/cakey.pem " +
|
|
158
|
+
" -out public/cacert.pem", options, callback),
|
|
159
|
+
(callback) => (0, toolbox_1.displaySubtitle)("generate initial CRL (Certificate Revocation List)", callback),
|
|
160
|
+
(callback) => regenerateCrl(certificateAuthority.revocationList, configOption, options, callback),
|
|
161
|
+
(callback) => (0, toolbox_1.displayTitle)("Create Certificate Authority (CA) ---> DONE", callback),
|
|
162
|
+
];
|
|
163
|
+
async.series(tasks, callback);
|
|
164
|
+
}
|
|
165
|
+
function regenerateCrl(revocationList, configOption, options, callback) {
|
|
166
|
+
const tasks = [
|
|
167
|
+
(callback) => (0, toolbox_1.displaySubtitle)("regenerate CRL (Certificate Revocation List)", callback),
|
|
168
|
+
(callback) =>
|
|
169
|
+
// produce a CRL in PEM format
|
|
170
|
+
(0, toolbox_1.execute_openssl)("ca -gencrl " + configOption + " -out crl/revocation_list.crl", options, callback),
|
|
171
|
+
(callback) => (0, toolbox_1.execute_openssl)("crl " + " -in crl/revocation_list.crl -out crl/revocation_list.der " + " -outform der", options, callback),
|
|
172
|
+
(callback) => (0, toolbox_1.displaySubtitle)("Display (Certificate Revocation List)", callback),
|
|
173
|
+
(callback) => (0, toolbox_1.execute_openssl)("crl " + " -in " + q(n(revocationList)) + " -text " + " -noout", options, callback),
|
|
174
|
+
];
|
|
175
|
+
async.series(tasks, callback);
|
|
176
|
+
}
|
|
177
|
+
class CertificateAuthority {
|
|
178
|
+
constructor(options) {
|
|
179
|
+
assert(Object.prototype.hasOwnProperty.call(options, "location"));
|
|
180
|
+
assert(Object.prototype.hasOwnProperty.call(options, "keySize"));
|
|
181
|
+
this.location = options.location;
|
|
182
|
+
this.keySize = options.keySize || 2048;
|
|
183
|
+
this.subject = new subject_1.Subject(options.subject || exports.defaultSubject);
|
|
184
|
+
}
|
|
185
|
+
get rootDir() {
|
|
186
|
+
return this.location;
|
|
187
|
+
}
|
|
188
|
+
get configFile() {
|
|
189
|
+
return path.normalize(path.join(this.rootDir, "./conf/caconfig.cnf"));
|
|
190
|
+
}
|
|
191
|
+
get caCertificate() {
|
|
192
|
+
// the Certificate Authority Certificate
|
|
193
|
+
return (0, toolbox_1.make_path)(this.rootDir, "./public/cacert.pem");
|
|
194
|
+
}
|
|
195
|
+
/**
|
|
196
|
+
* the file name where the current Certificate Revocation List is stored (in DER format)
|
|
197
|
+
*/
|
|
198
|
+
get revocationListDER() {
|
|
199
|
+
return (0, toolbox_1.make_path)(this.rootDir, "./crl/revocation_list.der");
|
|
200
|
+
}
|
|
201
|
+
/**
|
|
202
|
+
* the file name where the current Certificate Revocation List is stored (in PEM format)
|
|
203
|
+
*/
|
|
204
|
+
get revocationList() {
|
|
205
|
+
return (0, toolbox_1.make_path)(this.rootDir, "./crl/revocation_list.crl");
|
|
206
|
+
}
|
|
207
|
+
get caCertificateWithCrl() {
|
|
208
|
+
return (0, toolbox_1.make_path)(this.rootDir, "./public/cacertificate_with_crl.pem");
|
|
209
|
+
}
|
|
210
|
+
initialize(callback) {
|
|
211
|
+
assert(typeof callback === "function");
|
|
212
|
+
construct_CertificateAuthority(this, callback);
|
|
213
|
+
}
|
|
214
|
+
constructCACertificateWithCRL(callback) {
|
|
215
|
+
assert(typeof callback === "function");
|
|
216
|
+
const cacertWithCRL = this.caCertificateWithCrl;
|
|
217
|
+
// note : in order to check if the certificate is revoked,
|
|
218
|
+
// you need to specify -crl_check and have both the CA cert and the (applicable) CRL in your trust store.
|
|
219
|
+
// There are two ways to do that:
|
|
220
|
+
// 1. concatenate cacert.pem and crl.pem into one file and use that for -CAfile.
|
|
221
|
+
// 2. use some linked
|
|
222
|
+
// ( from http://security.stackexchange.com/a/58305/59982)
|
|
223
|
+
if (fs.existsSync(this.revocationList)) {
|
|
224
|
+
fs.writeFileSync(cacertWithCRL, fs.readFileSync(this.caCertificate, "utf8") + fs.readFileSync(this.revocationList, "utf8"));
|
|
225
|
+
}
|
|
226
|
+
else {
|
|
227
|
+
// there is no revocation list yet
|
|
228
|
+
fs.writeFileSync(cacertWithCRL, fs.readFileSync(this.caCertificate));
|
|
229
|
+
}
|
|
230
|
+
callback();
|
|
231
|
+
}
|
|
232
|
+
constructCertificateChain(certificate, callback) {
|
|
233
|
+
assert(typeof callback === "function");
|
|
234
|
+
assert(fs.existsSync(certificate));
|
|
235
|
+
assert(fs.existsSync(this.caCertificate));
|
|
236
|
+
(0, toolbox_1.debugLog)(chalk.yellow(" certificate file :"), chalk.cyan(certificate));
|
|
237
|
+
// append
|
|
238
|
+
fs.writeFileSync(certificate, fs.readFileSync(certificate, "utf8") + fs.readFileSync(this.caCertificate, "utf8")
|
|
239
|
+
// + fs.readFileSync(this.revocationList)
|
|
240
|
+
);
|
|
241
|
+
callback();
|
|
242
|
+
}
|
|
243
|
+
createSelfSignedCertificate(certificateFile, privateKey, params, callback) {
|
|
244
|
+
assert(typeof privateKey === "string");
|
|
245
|
+
assert(fs.existsSync(privateKey));
|
|
246
|
+
assert(typeof callback === "function");
|
|
247
|
+
if (!(0, toolbox_1.certificateFileExist)(certificateFile)) {
|
|
248
|
+
return callback();
|
|
249
|
+
}
|
|
250
|
+
(0, toolbox_1.adjustDate)(params);
|
|
251
|
+
(0, toolbox_1.adjustApplicationUri)(params);
|
|
252
|
+
(0, toolbox_1.processAltNames)(params);
|
|
253
|
+
const csrFile = certificateFile + "_csr";
|
|
254
|
+
assert(csrFile);
|
|
255
|
+
const configFile = (0, toolbox_1.generateStaticConfig)(this.configFile);
|
|
256
|
+
const options = {
|
|
257
|
+
cwd: this.rootDir,
|
|
258
|
+
openssl_conf: (0, toolbox_1.make_path)(configFile),
|
|
259
|
+
};
|
|
260
|
+
const configOption = "";
|
|
261
|
+
const subject = params.subject ? new subject_1.Subject(params.subject).toString() : "";
|
|
262
|
+
const subjectOptions = subject && subject.length > 1 ? " -subj " + subject + " " : "";
|
|
263
|
+
const tasks = [];
|
|
264
|
+
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- the certificate signing request", callback));
|
|
265
|
+
tasks.push((callback) => (0, toolbox_1.execute_openssl)("req " +
|
|
266
|
+
" -new -sha256 -text " +
|
|
267
|
+
configOption +
|
|
268
|
+
subjectOptions +
|
|
269
|
+
" -batch -key " +
|
|
270
|
+
q(n(privateKey)) +
|
|
271
|
+
" -out " +
|
|
272
|
+
q(n(csrFile)), options, callback));
|
|
273
|
+
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- creating the self-signed certificate", callback));
|
|
274
|
+
tasks.push((callback) => (0, toolbox_1.execute_openssl)("ca " +
|
|
275
|
+
" -selfsign " +
|
|
276
|
+
" -keyfile " +
|
|
277
|
+
q(n(privateKey)) +
|
|
278
|
+
" -startdate " +
|
|
279
|
+
(0, toolbox_1.x509Date)(params.startDate) +
|
|
280
|
+
" -enddate " +
|
|
281
|
+
(0, toolbox_1.x509Date)(params.endDate) +
|
|
282
|
+
" -batch -out " +
|
|
283
|
+
q(n(certificateFile)) +
|
|
284
|
+
" -in " +
|
|
285
|
+
q(n(csrFile)), options, callback));
|
|
286
|
+
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- dump the certificate for a check", callback));
|
|
287
|
+
tasks.push((callback) => (0, toolbox_1.execute_openssl)("x509 -in " + q(n(certificateFile)) + " -dates -fingerprint -purpose -noout", {}, callback));
|
|
288
|
+
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- verify self-signed certificate", callback));
|
|
289
|
+
tasks.push((callback) => (0, toolbox_1.execute_openssl_no_failure)("verify -verbose -CAfile " + q(n(certificateFile)) + " " + q(n(certificateFile)), options, callback));
|
|
290
|
+
tasks.push((callback) => fs.unlink(csrFile, callback));
|
|
291
|
+
async.series(tasks, (err) => {
|
|
292
|
+
callback(err);
|
|
293
|
+
});
|
|
294
|
+
}
|
|
295
|
+
revokeCertificate(certificate, params, callback) {
|
|
296
|
+
assert(typeof callback === "function");
|
|
297
|
+
const crlReasons = [
|
|
298
|
+
"unspecified",
|
|
299
|
+
"keyCompromise",
|
|
300
|
+
"CACompromise",
|
|
301
|
+
"affiliationChanged",
|
|
302
|
+
"superseded",
|
|
303
|
+
"cessationOfOperation",
|
|
304
|
+
"certificateHold",
|
|
305
|
+
"removeFromCRL",
|
|
306
|
+
];
|
|
307
|
+
const configFile = (0, toolbox_1.generateStaticConfig)("conf/caconfig.cnf", { cwd: this.rootDir });
|
|
308
|
+
const options = {
|
|
309
|
+
cwd: this.rootDir,
|
|
310
|
+
openssl_conf: (0, toolbox_1.make_path)(configFile),
|
|
311
|
+
};
|
|
312
|
+
(0, toolbox_1.setEnv)("ALTNAME", "");
|
|
313
|
+
const randomFile = path.join(this.rootDir, "random.rnd");
|
|
314
|
+
(0, toolbox_1.setEnv)("RANDFILE", randomFile);
|
|
315
|
+
// // tslint:disable-next-line:no-string-literal
|
|
316
|
+
// if (!fs.existsSync((process.env as any)["OPENSSL_CONF"])) {
|
|
317
|
+
// throw new Error("Cannot find OPENSSL_CONF");
|
|
318
|
+
// }
|
|
319
|
+
const configOption = " -config " + q(n(configFile));
|
|
320
|
+
const reason = params.reason || "keyCompromise";
|
|
321
|
+
assert(crlReasons.indexOf(reason) >= 0);
|
|
322
|
+
const tasks = [
|
|
323
|
+
(callback) => (0, toolbox_1.displayTitle)("Revoking certificate " + certificate, callback),
|
|
324
|
+
(callback) => (0, toolbox_1.displaySubtitle)("Make sure random file exists" + randomFile, callback),
|
|
325
|
+
(callback) => (0, toolbox_1.createRandomFileIfNotExist)(randomFile, {}, callback),
|
|
326
|
+
(callback) => (0, toolbox_1.displaySubtitle)("Revoke certificate", callback),
|
|
327
|
+
(callback) => {
|
|
328
|
+
(0, toolbox_1.execute_openssl_no_failure)("ca -verbose " + configOption + " -revoke " + q(certificate) + " -crl_reason " + reason, options, callback);
|
|
329
|
+
},
|
|
330
|
+
// regenerate CRL (Certificate Revocation List)
|
|
331
|
+
(callback) => regenerateCrl(this.revocationList, configOption, options, callback),
|
|
332
|
+
(callback) => (0, toolbox_1.displaySubtitle)("Verify that certificate is revoked ", callback),
|
|
333
|
+
(callback) => {
|
|
334
|
+
(0, toolbox_1.execute_openssl_no_failure)("verify -verbose" +
|
|
335
|
+
// configOption +
|
|
336
|
+
" -CRLfile " +
|
|
337
|
+
q(n(this.revocationList)) +
|
|
338
|
+
" -CAfile " +
|
|
339
|
+
q(n(this.caCertificate)) +
|
|
340
|
+
" -crl_check " +
|
|
341
|
+
q(n(certificate)), options, (err, output) => {
|
|
342
|
+
callback();
|
|
343
|
+
});
|
|
344
|
+
},
|
|
345
|
+
// produce CRL in DER format
|
|
346
|
+
(callback) => (0, toolbox_1.displaySubtitle)("Produce CRL in DER form ", callback),
|
|
347
|
+
(callback) => (0, toolbox_1.execute_openssl)("crl " + " -in " + q(n(this.revocationList)) + " -out " + "crl/revocation_list.der " + " -outform der", options, callback),
|
|
348
|
+
// produce CRL in PEM format with text
|
|
349
|
+
(callback) => (0, toolbox_1.displaySubtitle)("Produce CRL in PEM form ", callback),
|
|
350
|
+
(callback) => (0, toolbox_1.execute_openssl)("crl " +
|
|
351
|
+
" -in " +
|
|
352
|
+
q(n(this.revocationList)) +
|
|
353
|
+
" -out " +
|
|
354
|
+
"crl/revocation_list.pem " +
|
|
355
|
+
" -outform pem" +
|
|
356
|
+
" -text ", options, callback),
|
|
357
|
+
];
|
|
358
|
+
async.series(tasks, callback);
|
|
359
|
+
}
|
|
360
|
+
signCertificateRequest(certificate, certificateSigningRequestFilename, params, callback) {
|
|
361
|
+
// istanbul ignore next
|
|
362
|
+
if (!callback) {
|
|
363
|
+
throw new Error("Internal Error");
|
|
364
|
+
}
|
|
365
|
+
(0, toolbox_1.ensure_openssl_installed)(() => {
|
|
366
|
+
try {
|
|
367
|
+
assert(fs.existsSync(certificateSigningRequestFilename));
|
|
368
|
+
assert(typeof callback === "function");
|
|
369
|
+
if (!(0, toolbox_1.certificateFileExist)(certificate)) {
|
|
370
|
+
return callback(null);
|
|
371
|
+
}
|
|
372
|
+
(0, toolbox_1.adjustDate)(params);
|
|
373
|
+
(0, toolbox_1.adjustApplicationUri)(params);
|
|
374
|
+
(0, toolbox_1.processAltNames)(params);
|
|
375
|
+
const options = { cwd: this.rootDir };
|
|
376
|
+
let configFile;
|
|
377
|
+
const tasks = [];
|
|
378
|
+
let csrInfo;
|
|
379
|
+
// note :
|
|
380
|
+
// subjectAltName is not copied across
|
|
381
|
+
// see https://github.com/openssl/openssl/issues/10458
|
|
382
|
+
tasks.push((callback) => {
|
|
383
|
+
(0, node_opcua_crypto_1.readCertificateSigningRequest)(certificateSigningRequestFilename)
|
|
384
|
+
.then((csr) => {
|
|
385
|
+
csrInfo = (0, node_opcua_crypto_1.exploreCertificateSigningRequest)(csr);
|
|
386
|
+
callback();
|
|
387
|
+
})
|
|
388
|
+
.catch((err) => callback(err));
|
|
389
|
+
});
|
|
390
|
+
tasks.push((callback) => {
|
|
391
|
+
const applicationUri = csrInfo.extensionRequest.subjectAltName.uniformResourceIdentifier[0];
|
|
392
|
+
if (typeof applicationUri !== "string") {
|
|
393
|
+
return callback(new Error("Cannot find applicationUri in CSR"));
|
|
394
|
+
}
|
|
395
|
+
const dns = csrInfo.extensionRequest.subjectAltName.dNSName || [];
|
|
396
|
+
let ip = csrInfo.extensionRequest.subjectAltName.iPAddress || [];
|
|
397
|
+
ip = ip.map(octetStringToIpAddress);
|
|
398
|
+
const params = {
|
|
399
|
+
applicationUri,
|
|
400
|
+
dns,
|
|
401
|
+
ip,
|
|
402
|
+
};
|
|
403
|
+
(0, toolbox_1.processAltNames)(params);
|
|
404
|
+
configFile = (0, toolbox_1.generateStaticConfig)("conf/caconfig.cnf", options);
|
|
405
|
+
callback();
|
|
406
|
+
});
|
|
407
|
+
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- then we ask the authority to sign the certificate signing request", callback));
|
|
408
|
+
tasks.push((callback) => {
|
|
409
|
+
const configOption = " -config " + configFile;
|
|
410
|
+
(0, toolbox_1.execute_openssl)("ca " +
|
|
411
|
+
configOption +
|
|
412
|
+
" -startdate " +
|
|
413
|
+
(0, toolbox_1.x509Date)(params.startDate) +
|
|
414
|
+
" -enddate " +
|
|
415
|
+
(0, toolbox_1.x509Date)(params.endDate) +
|
|
416
|
+
" -batch -out " +
|
|
417
|
+
q(n(certificate)) +
|
|
418
|
+
" -in " +
|
|
419
|
+
q(n(certificateSigningRequestFilename)), options, callback);
|
|
420
|
+
});
|
|
421
|
+
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- dump the certificate for a check", callback));
|
|
422
|
+
tasks.push((callback) => (0, toolbox_1.execute_openssl)("x509 -in " + q(n(certificate)) + " -dates -fingerprint -purpose -noout", options, callback));
|
|
423
|
+
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- construct CA certificate with CRL", callback));
|
|
424
|
+
tasks.push((callback) => {
|
|
425
|
+
this.constructCACertificateWithCRL(callback);
|
|
426
|
+
});
|
|
427
|
+
// construct certificate chain
|
|
428
|
+
// concatenate certificate with CA Certificate and revocation list
|
|
429
|
+
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- construct certificate chain", callback));
|
|
430
|
+
tasks.push((callback) => {
|
|
431
|
+
this.constructCertificateChain(certificate, callback);
|
|
432
|
+
});
|
|
433
|
+
// todo
|
|
434
|
+
tasks.push((callback) => (0, toolbox_1.displaySubtitle)("- verify certificate against the root CA", callback));
|
|
435
|
+
tasks.push((callback) => {
|
|
436
|
+
this.verifyCertificate(certificate, callback);
|
|
437
|
+
});
|
|
438
|
+
async.series(tasks, (err) => {
|
|
439
|
+
// istanbul ignore next
|
|
440
|
+
if (err) {
|
|
441
|
+
return callback(err);
|
|
442
|
+
}
|
|
443
|
+
callback(null, certificate);
|
|
444
|
+
});
|
|
445
|
+
}
|
|
446
|
+
catch (err) {
|
|
447
|
+
callback(err);
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
}
|
|
451
|
+
verifyCertificate(certificate, callback) {
|
|
452
|
+
// openssl verify crashes on windows! we cannot use it reliably
|
|
453
|
+
// istanbul ignore next
|
|
454
|
+
const isImplemented = false;
|
|
455
|
+
// istanbul ignore next
|
|
456
|
+
if (isImplemented) {
|
|
457
|
+
const options = { cwd: this.rootDir };
|
|
458
|
+
const configFile = (0, toolbox_1.generateStaticConfig)("conf/caconfig.cnf", options);
|
|
459
|
+
(0, toolbox_1.setEnv)("OPENSSL_CONF", (0, toolbox_1.make_path)(configFile));
|
|
460
|
+
const configOption = " -config " + configFile;
|
|
461
|
+
(0, toolbox_1.execute_openssl_no_failure)("verify -verbose " + " -CAfile " + q(n(this.caCertificateWithCrl)) + " " + q(n(certificate)), options, (err) => {
|
|
462
|
+
callback(err ? err : undefined);
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
return callback();
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
exports.CertificateAuthority = CertificateAuthority;
|
|
471
|
+
// tslint:disable:no-var-requires
|
|
472
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
473
|
+
const thenify = require("thenify");
|
|
474
|
+
const opts = { multiArgs: false };
|
|
475
|
+
CertificateAuthority.prototype.initialize = thenify.withCallback(CertificateAuthority.prototype.initialize, opts);
|
|
476
|
+
CertificateAuthority.prototype.constructCACertificateWithCRL = thenify.withCallback(CertificateAuthority.prototype.constructCACertificateWithCRL, opts);
|
|
477
|
+
CertificateAuthority.prototype.constructCertificateChain = thenify.withCallback(CertificateAuthority.prototype.constructCertificateChain, opts);
|
|
478
|
+
CertificateAuthority.prototype.createSelfSignedCertificate = thenify.withCallback(CertificateAuthority.prototype.createSelfSignedCertificate, opts);
|
|
479
|
+
CertificateAuthority.prototype.revokeCertificate = thenify.withCallback(CertificateAuthority.prototype.revokeCertificate, opts);
|
|
480
|
+
CertificateAuthority.prototype.verifyCertificate = thenify.withCallback(CertificateAuthority.prototype.verifyCertificate, opts);
|
|
481
|
+
CertificateAuthority.prototype.signCertificateRequest = thenify.withCallback(CertificateAuthority.prototype.signCertificateRequest, opts);
|
|
482
482
|
//# sourceMappingURL=certificate_authority.js.map
|