master
js 1,434 lines 84.3 KB
Raw
1 /**
2 * @description Certificate generator
3 * @author Joko Sastriawan / Ylian Saint-Hilaire
4 * @copyright Intel Corporation 2018-2022
5 * @license Apache-2.0
6 * @version v0.0.1
7 */
8
9 /*xjslint node: true */
10 /*xjslint plusplus: true */
11 /*xjslint maxlen: 256 */
12 /*jshint node: true */
13 /*jshint strict: false */
14 /*jshint esversion: 6 */
15 "use strict";
16
17 module.exports.CertificateOperations = function (parent) {
18 var obj = {};
19
20 obj.parent = parent;
21 obj.fs = require('fs');
22 obj.forge = require('node-forge');
23 obj.crypto = require('crypto');
24 obj.tls = require('tls');
25 obj.pki = obj.forge.pki;
26 obj.dirExists = function (filePath) { try { return obj.fs.statSync(filePath).isDirectory(); } catch (err) { return false; } };
27 obj.getFilesizeInBytes = function (filename) { try { return obj.fs.statSync(filename).size; } catch (err) { return -1; } };
28
29 const TopLevelDomainExtendedSupport = { 'net': 2, 'com': 2, 'arpa': 3, 'org': 2, 'gov': 2, 'edu': 2, 'de': 2, 'fr': 3, 'cn': 3, 'nl': 3, 'br': 3, 'mx': 3, 'uk': 3, 'pl': 3, 'tw': 3, 'ca': 3, 'fi': 3, 'be': 3, 'ru': 3, 'se': 3, 'ch': 2, 'dk': 2, 'ar': 3, 'es': 3, 'no': 3, 'at': 3, 'in': 3, 'tr': 3, 'cz': 2, 'ro': 3, 'hu': 3, 'nz': 3, 'pt': 3, 'il': 3, 'gr': 3, 'co': 3, 'ie': 3, 'za': 3, 'th': 3, 'sg': 3, 'hk': 3, 'cl': 2, 'lt': 3, 'id': 3, 'hr': 3, 'ee': 3, 'bg': 3, 'ua': 2 };
30
31 // Return true if the trusted FQDN matched the certificate common name
32 function checkAcmActivationCertName(commonName, trustedFqdn) {
33 commonName = commonName.toLowerCase();
34 trustedFqdn = trustedFqdn.toLowerCase();
35 if (commonName.startsWith('*.') && (commonName.length > 2)) { commonName = commonName.substring(2); }
36 return ((commonName == trustedFqdn) || (trustedFqdn.endsWith('.' + commonName)));
37 }
38
39 // Sign a Intel AMT TLS ACM activation request
40 obj.getAcmCertChain = function (domain, fqdn, hash) {
41 if ((domain == null) || (domain.amtacmactivation == null) || (domain.amtacmactivation.certs == null) || (fqdn == null) || (hash == null)) return { action: 'acmactivate', error: 1, errorText: 'Invalid arguments' };
42 if (parent.common.validateString(fqdn, 4, 256) == false) return { action: 'acmactivate', error: 1, errorText: "Invalid FQDN argument." };
43 if (parent.common.validateString(hash, 16, 256) == false) return { action: 'acmactivate', error: 1, errorText: "Invalid hash argument." };
44
45 // Look for the signing certificate
46 var signkey = null, certChain = null, hashAlgo = null, certIndex = null;
47 for (var i in domain.amtacmactivation.certs) {
48 const certEntry = domain.amtacmactivation.certs[i];
49 if ((certEntry.sha256 == hash) && ((certEntry.cn == '*') || checkAcmActivationCertName(certEntry.cn, fqdn))) { hashAlgo = 'sha256'; signkey = certEntry.key; certChain = certEntry.certs; certIndex = i; break; }
50 if ((certEntry.sha1 == hash) && ((certEntry.cn == '*') || checkAcmActivationCertName(certEntry.cn, fqdn))) { hashAlgo = 'sha1'; signkey = certEntry.key; certChain = certEntry.certs; certIndex = i; break; }
51 }
52 if (signkey == null) return { action: 'acmactivate', error: 2, errorText: "Can't create ACM cert chain, no signing certificate found." }; // Did not find a match.
53
54 // If the matching certificate our wildcard root cert, we can use the root to match any FQDN
55 if (domain.amtacmactivation.certs[certIndex].cn == '*') {
56 // Create a leaf certificate that matches the FQDN we want
57 // TODO: This is an expensive operation, work on ways to pre-generate or cache this leaf certificate.
58 var rootcert = { cert: domain.amtacmactivation.certs[certIndex].rootcert, key: obj.pki.privateKeyFromPem(domain.amtacmactivation.certs[certIndex].key) };
59 var leafcert = obj.IssueWebServerCertificate(rootcert, false, fqdn, 'mc', 'Intel(R) Client Setup Certificate', { serverAuth: true, '2.16.840.1.113741.1.2.3': true }, false);
60
61 // Setup the certificate chain and key
62 certChain = [ obj.pki.certificateToPem(leafcert.cert), obj.pki.certificateToPem(domain.amtacmactivation.certs[certIndex].rootcert) ];
63 signkey = obj.pki.privateKeyToPem(leafcert.key);
64 } else {
65 // Make sure the cert chain is in PEM format
66 var certChain2 = [];
67 for (var i in certChain) { certChain2.push("-----BEGIN CERTIFICATE-----\r\n" + certChain[i] + "\r\n-----END CERTIFICATE-----\r\n"); }
68 certChain = certChain2;
69 }
70
71 // Hash the leaf certificate and return the certificate chain and signing key
72 return { action: 'acmactivate', certs: certChain, signkey: signkey, hash384: obj.getCertHash(certChain[0]), hash256: obj.getCertHashSha256(certChain[0]) };
73 }
74
75 // Sign a Intel AMT ACM activation request
76 obj.signAcmRequest = function (domain, request, user, pass, ipport, nodeid, meshid, computerName, agentId) {
77 if ((domain == null) || (domain.amtacmactivation == null) || (domain.amtacmactivation.certs == null) || (request == null) || (request.nonce == null) || (request.realm == null) || (request.fqdn == null) || (request.hash == null)) return { 'action': 'acmactivate', 'error': 1, 'errorText': 'Invalid arguments' };
78 if (parent.common.validateString(request.nonce, 16, 256) == false) return { 'action': 'acmactivate', 'error': 1, 'errorText': "Invalid nonce argument." };
79 if (parent.common.validateString(request.realm, 16, 256) == false) return { 'action': 'acmactivate', 'error': 1, 'errorText': "Invalid realm argument." };
80 if (parent.common.validateString(request.fqdn, 4, 256) == false) return { 'action': 'acmactivate', 'error': 1, 'errorText': "Invalid FQDN argument." };
81 if (parent.common.validateString(request.hash, 16, 256) == false) return { 'action': 'acmactivate', 'error': 1, 'errorText': "Invalid hash argument." };
82 if (parent.common.validateString(request.uuid, 36, 36) == false) return { 'action': 'acmactivate', 'error': 1, 'errorText': "Invalid UUID argument." };
83
84 // Look for the signing certificate
85 var signkey = null, certChain = null, hashAlgo = null, certIndex = null;
86 for (var i in domain.amtacmactivation.certs) {
87 const certEntry = domain.amtacmactivation.certs[i];
88 if ((certEntry.sha256 == request.hash) && ((certEntry.cn == '*') || checkAcmActivationCertName(certEntry.cn, request.fqdn))) { hashAlgo = 'sha256'; signkey = certEntry.key; certChain = certEntry.certs; certIndex = i; break; }
89 if ((certEntry.sha1 == request.hash) && ((certEntry.cn == '*') || checkAcmActivationCertName(certEntry.cn, request.fqdn))) { hashAlgo = 'sha1'; signkey = certEntry.key; certChain = certEntry.certs; certIndex = i; break; }
90 }
91 if (signkey == null) return { 'action': 'acmactivate', 'error': 2, 'errorText': "Can't sign ACM request, no signing certificate found." }; // Did not find a match.
92
93 // If the matching certificate our wildcard root cert, we can use the root to match any FQDN
94 if (domain.amtacmactivation.certs[certIndex].cn == '*') {
95 // Create a leaf certificate that matches the FQDN we want
96 // TODO: This is an expensive operation, work on ways to pre-generate or cache this leaf certificate.
97 var rootcert = { cert: domain.amtacmactivation.certs[certIndex].rootcert, key: obj.pki.privateKeyFromPem(domain.amtacmactivation.certs[certIndex].key) };
98 var leafcert = obj.IssueWebServerCertificate(rootcert, false, request.fqdn, 'mc', 'Intel(R) Client Setup Certificate', { serverAuth: true, '2.16.840.1.113741.1.2.3': true }, false);
99
100 // Setup the certificate chain and key
101 certChain = [pemToBase64(obj.pki.certificateToPem(leafcert.cert)), pemToBase64(obj.pki.certificateToPem(domain.amtacmactivation.certs[certIndex].rootcert))];
102 signkey = obj.pki.privateKeyToPem(leafcert.key);
103 }
104
105 // Setup both nonces, ready to be signed
106 const mcNonce = Buffer.from(obj.crypto.randomBytes(20), 'binary');
107 const fwNonce = Buffer.from(request.nonce, 'base64');
108
109 // Sign the request
110 var signature = null;
111 try {
112 var signer = obj.crypto.createSign(hashAlgo);
113 signer.update(Buffer.concat([fwNonce, mcNonce]));
114 signature = signer.sign(signkey, 'base64');
115 } catch (ex) {
116 return { 'action': 'acmactivate', 'error': 4, 'errorText': "Unable to perform signature." };
117 }
118
119 // Log the activation request, logging is a required step for activation.
120 if (obj.logAmtActivation(domain, { time: new Date(), action: 'acmactivate', domain: domain.id, amtUuid: request.uuid, certHash: request.hash, hashType: hashAlgo, amtRealm: request.realm, amtFqdn: request.fqdn, user: user, password: pass, ipport: ipport, nodeid: nodeid, meshid: meshid, computerName: computerName, agentId: agentId, tag: request.tag, name: request.name }) == false) return { 'action': 'acmactivate', 'error': 5, 'errorText': "Unable to log operation." };
121
122 // Return the signature with the computed account password hash
123 return { 'action': 'acmactivate', 'signature': signature, 'password': obj.crypto.createHash('md5').update(user + ':' + request.realm + ':' + pass).digest('hex'), 'nonce': mcNonce.toString('base64'), 'certs': certChain };
124 }
125
126 // Remove the PEM header, footer and carriage returns so we only have the Base64 DER.
127 function pemToBase64(pem) { return pem.split('-----BEGIN CERTIFICATE-----').join('').split('-----END CERTIFICATE-----').join('').split('\r\n').join(''); }
128
129 // Return true if both arrays match
130 function compareArrays(a1, a2) {
131 if (Array.isArray(a1) == false) return false;
132 if (Array.isArray(a2) == false) return false;
133 if (a1.length !== a2.length) return false;
134 for (var i = 0; i < a1.length; i++) { if (a1[i] !== a2[i]) return false; }
135 return true;
136 }
137
138 // Log the Intel AMT activation operation in the domain log
139 obj.logAmtActivation = function (domain, x) {
140 if (x == null) return true;
141
142 // Add the password to the Intel AMT list of UUID to passwords
143 if ((typeof x.amtUuid == 'string') && (typeof x.password == 'string')) {
144 if (parent.amtPasswords == null) { parent.amtPasswords = {}; }
145 if (parent.amtPasswords[x.amtUuid] == null) {
146 parent.amtPasswords[x.amtUuid] = [x.password]; // Add password to array
147 parent.amtPasswords = parent.common.sortObj(parent.amtPasswords);
148 } else {
149 if (parent.amtPasswords[x.amtUuid].indexOf(x.password) == -1) {
150 parent.amtPasswords[x.amtUuid].unshift(x.password); // Add password at the start of the array
151 while (parent.amtPasswords[x.amtUuid].length > 3) { parent.amtPasswords[x.amtUuid].pop(); } // Only keep the 3 last passwords for any given device
152 }
153 }
154 }
155
156 // Append to the log file
157 var logpath = null;
158 if ((domain.amtacmactivation == null) || (domain.amtacmactivation.log == null) || (typeof domain.amtacmactivation.log != 'string')) {
159 if (domain.id == '') { logpath = parent.path.join(obj.parent.datapath, 'amtactivation.log'); } else { logpath = parent.path.join(obj.parent.datapath, 'amtactivation-' + domain.id + '.log'); }
160 } else {
161 logpath = parent.common.joinPath(obj.parent.datapath, domain.amtacmactivation.log);
162 }
163 try { obj.fs.appendFileSync(logpath, JSON.stringify(x) + '\r\n'); } catch (ex) { console.log(ex); return false; }
164 return true;
165 }
166
167 // Load Intel AMT ACM activation certificates
168 obj.loadIntelAmtAcmCerts = function (amtacmactivation) {
169 if (amtacmactivation == null) return;
170 var acmCerts = [], acmmatch = [];
171 amtacmactivation.acmCertErrors = [];
172 if (amtacmactivation.certs != null) {
173 for (var j in amtacmactivation.certs) {
174 if (j.startsWith('_')) continue; // Skip any certificates that start with underscore as the name.
175 var acmconfig = amtacmactivation.certs[j], r = null;
176
177 if ((typeof acmconfig.certpfx == 'string') && (typeof acmconfig.certpfxpass == 'string')) {
178 // P12 format, certpfx and certpfxpass
179 const certFilePath = parent.common.joinPath(obj.parent.datapath, acmconfig.certpfx);
180 try { r = obj.loadPfxCertificate(certFilePath, acmconfig.certpfxpass); } catch (ex) { console.log(ex); }
181 if ((r == null) || (r.certs == null) || (r.keys == null)) { amtacmactivation.acmCertErrors.push("Unable to load certificate file: " + certFilePath + "."); continue; }
182 if (r.certs.length < 2) { amtacmactivation.acmCertErrors.push("Certificate file contains less then 2 certificates: " + certFilePath + "."); continue; }
183 if (r.keys.length != 1) { amtacmactivation.acmCertErrors.push("Certificate file must contain exactly one private key: " + certFilePath + "."); continue; }
184 } else if ((typeof acmconfig.certfiles == 'object') && (typeof acmconfig.keyfile == 'string')) {
185 // PEM format, certfiles and keyfile
186 r = { certs: [], keys: [] };
187 for (var k in acmconfig.certfiles) {
188 const certFilePath = parent.common.joinPath(obj.parent.datapath, acmconfig.certfiles[k]);
189 try { r.certs.push(obj.pki.certificateFromPem(obj.fs.readFileSync(certFilePath))); } catch (ex) { amtacmactivation.acmCertErrors.push("Unable to load certificate file: " + certFilePath + "."); }
190 }
191 r.keys.push(obj.pki.privateKeyFromPem(obj.fs.readFileSync(parent.common.joinPath(obj.parent.datapath, acmconfig.keyfile))));
192 if (r.certs.length < 2) { amtacmactivation.acmCertErrors.push("Certificate file contains less then 2 certificates: " + certFilePath + "."); continue; }
193 if (r.keys.length != 1) { amtacmactivation.acmCertErrors.push("Certificate file must contain exactly one private key: " + certFilePath + "."); continue; }
194 }
195
196 // Reorder the certificates from leaf to root.
197 var orderedCerts = [], or = [], currenthash = null, orderingError = false;;
198 while ((orderingError == false) && (orderedCerts.length < r.certs.length)) {
199 orderingError = true;
200 for (var k in r.certs) {
201 if (((currenthash == null) && (r.certs[k].subject.hash == r.certs[k].issuer.hash)) || ((r.certs[k].issuer.hash == currenthash) && (r.certs[k].subject.hash != r.certs[k].issuer.hash))) {
202 currenthash = r.certs[k].subject.hash;
203 orderedCerts.unshift(Buffer.from(obj.forge.asn1.toDer(obj.pki.certificateToAsn1(r.certs[k])).data, 'binary').toString('base64'));
204 or.unshift(r.certs[k]);
205 orderingError = false;
206 }
207 }
208 }
209 if (orderingError == true) { amtacmactivation.acmCertErrors.push("Unable to order Intel AMT ACM activation certificates to create a full chain."); continue; }
210 r.certs = or;
211
212 // Check that the certificate and private key match
213 if ((compareArrays(r.certs[0].publicKey.n.data, r.keys[0].n.data) == false) || (compareArrays(r.certs[0].publicKey.e.data, r.keys[0].e.data) == false)) {
214 amtacmactivation.acmCertErrors.push("Intel AMT activation certificate provided with a mismatching private key.");
215 continue;
216 }
217
218 /*
219 // Debug: Display all certs & key as PEM
220 for (var k in r.certs) {
221 var cn = r.certs[k].subject.getField('CN');
222 if (cn != null) { console.log(cn.value + '\r\n' + obj.pki.certificateToPem(r.certs[k])); } else { console.log(obj.pki.certificateToPem(r.certs[k])); }
223 }
224 console.log(obj.pki.privateKeyToPem(r.keys[0]));
225 */
226
227 // Check if the right OU or OID is present for Intel AMT activation
228 var validActivationCert = false;
229 for (var k in r.certs[0].extensions) { if (r.certs[0].extensions[k]['2.16.840.1.113741.1.2.3'] == true) { validActivationCert = true; } }
230 var orgName = r.certs[0].subject.getField('OU');
231 if ((orgName != null) && (orgName.value == 'Intel(R) Client Setup Certificate')) { validActivationCert = true; }
232 if (validActivationCert == false) { amtacmactivation.acmCertErrors.push("Intel AMT activation certificate must have usage OID \"2.16.840.1.113741.1.2.3\" or organization name \"Intel(R) Client Setup Certificate\"."); continue; }
233
234 // Compute the SHA256 and SHA1 hashes of the root certificate
235 for (var k in r.certs) {
236 if (r.certs[k].subject.hash != r.certs[k].issuer.hash) continue;
237 const certdata = obj.forge.asn1.toDer(obj.pki.certificateToAsn1(r.certs[k])).data;
238 var md = obj.forge.md.sha256.create();
239 md.update(certdata);
240 acmconfig.sha256 = Buffer.from(md.digest().getBytes(), 'binary').toString('hex');
241 md = obj.forge.md.sha1.create();
242 md.update(certdata);
243 acmconfig.sha1 = Buffer.from(md.digest().getBytes(), 'binary').toString('hex');
244 }
245 if ((acmconfig.sha1 == null) || (acmconfig.sha256 == null)) { amtacmactivation.acmCertErrors.push("Unable to compute Intel AMT activation certificate SHA1 and SHA256 hashes."); continue; }
246
247 // Get the certificate common name
248 var certCommonName = r.certs[0].subject.getField('CN');
249 if (certCommonName == null) { amtacmactivation.acmCertErrors.push("Unable to get Intel AMT activation certificate common name."); continue; }
250 if (amtacmactivation.strictcommonname == true) {
251 // Use the certificate common name exactly
252 acmconfig.cn = certCommonName.value;
253 } else {
254 // Check if Intel AMT will allow some flexibility in the certificate common name
255 var certCommonNameSplit = certCommonName.value.split('.');
256 var topLevel = certCommonNameSplit[certCommonNameSplit.length - 1].toLowerCase();
257 var topLevelNum = TopLevelDomainExtendedSupport[topLevel];
258 if (topLevelNum != null) {
259 while (certCommonNameSplit.length > topLevelNum) { certCommonNameSplit.shift(); }
260 acmconfig.cn = certCommonNameSplit.join('.');
261 } else {
262 acmconfig.cn = certCommonName.value;
263 }
264 }
265 if(r.certs[0].md){
266 acmconfig.hashAlgorithm = r.certs[0].md.algorithm;
267 }
268
269 delete acmconfig.cert;
270 delete acmconfig.certpass;
271 acmconfig.certs = orderedCerts;
272 acmconfig.key = obj.pki.privateKeyToPem(r.keys[0]);
273 acmCerts.push(acmconfig);
274 acmmatch.push({ sha256: acmconfig.sha256, sha1: acmconfig.sha1, cn: acmconfig.cn });
275 }
276 }
277 amtacmactivation.acmmatch = acmmatch;
278 amtacmactivation.certs = acmCerts;
279
280 // Add the MeshCentral root cert as a possible activation cert
281 if (obj.parent.certificates.root) {
282 var x1 = obj.parent.certificates.root.cert.indexOf('-----BEGIN CERTIFICATE-----'), x2 = obj.parent.certificates.root.cert.indexOf('-----END CERTIFICATE-----');
283 if ((x1 >= 0) && (x2 > x1)) {
284 var sha256 = obj.crypto.createHash('sha256').update(Buffer.from(obj.parent.certificates.root.cert.substring(x1 + 27, x2), 'base64')).digest('hex');
285 var sha1 = obj.crypto.createHash('sha1').update(Buffer.from(obj.parent.certificates.root.cert.substring(x1 + 27, x2), 'base64')).digest('hex');
286 amtacmactivation.certs.push({ 'sha256': sha256, 'sha1': sha1, 'cn': '*', rootcert: obj.pki.certificateFromPem(obj.parent.certificates.root.cert), key: obj.parent.certificates.root.key });
287 amtacmactivation.acmmatch.push({ 'sha256': sha256, 'sha1': sha1, 'cn': '*' });
288 }
289 }
290 }
291
292 // Load a generic certificate and key from PFX/P12 or PEM format. Load both keys and attributes.
293 obj.loadGenericCertAndKey = function (config) {
294 if ((typeof config.certpfx == 'string') || (typeof config.certpfxpass == 'string')) {
295 // Load a PFX certificate
296 var r = null;
297 try { r = obj.loadPfxCertificate(parent.getConfigFilePath(config.certpfx), config.certpfxpass); } catch (ex) { console.log(ex); }
298 if ((r != null) && (r.keys.length > 0) && (r.certs.length > 0)) {
299 var attributes = {};
300 for (var j in r.certs[0].subject.attributes) { attributes[r.certs[0].subject.attributes[j].shortName] = r.certs[0].subject.attributes[j].value; }
301 return { cert: obj.pki.certificateToPem(r.certs[0]), key: obj.pki.privateKeyToPem(r.keys[0]), attributes: attributes };
302 }
303 }
304 if ((typeof config.certfile == 'string') || (typeof config.keyfile == 'string')) {
305 // Load a PEM certificate
306 var r = {}
307 r.cert = obj.fs.readFileSync(parent.getConfigFilePath(config.certfile), 'utf8');
308 r.key = obj.fs.readFileSync(parent.getConfigFilePath(config.keyfile), 'utf8');
309 var cert = obj.pki.certificateFromPem(r.cert);
310 r.attributes = {};
311 for (var j in cert.subject.attributes) { r.attributes[cert.subject.attributes[j].shortName] = cert.subject.attributes[j].value; }
312 return r;
313 }
314 return null;
315 }
316
317 // Get the setup.bin file
318 obj.GetSetupBinFile = function (amtacmactivation, oldmebxpass, newmebxpass, domain, user) {
319 // Create a setup.bin file for our own root cert
320 // Get the wiadcard certificate hash
321 var wildcardCertSha256 = null;
322 for (var i = 0; i < amtacmactivation.acmmatch.length; i++) { if (amtacmactivation.acmmatch[i].cn == '*') { wildcardCertSha256 = amtacmactivation.acmmatch[i].sha256; } }
323
324 // Create the Setup.bin stack
325 const AmtSetupBinStack = require('./amt/amt-setupbin')();
326 var setupbin = AmtSetupBinStack.AmtSetupBinCreate(3, 1); // Version 3, 1 = Records will not be consumed.
327 var certRootName = 'MeshCentral';
328
329 // Figure out what trusted FQDN to use.
330 var trustedFQDN = 'rootcert.meshcentral.com'; // Default DNS name. Any DNS name will do, we this is the fallback.
331 if (typeof domain.dns == 'string') {
332 // Use domain DNS name
333 trustedFQDN = domain.dns;
334 } else if (typeof parent.config.settings.cert == 'string') {
335 // Use main DNS name
336 trustedFQDN = parent.config.settings.cert;
337 }
338
339 // Create a new record
340 var r = {};
341 r.typeIdentifier = 1;
342 r.flags = 1; // Valid, unscrambled record.
343 r.chunkCount = 0;
344 r.headerByteCount = 0;
345 r.number = 0;
346 r.variables = [];
347 setupbin.records.push(r);
348
349 // Create "Current MEBx Password" variable
350 var v = {};
351 v.moduleid = 1;
352 v.varid = 1;
353 v.length = -1;
354 v.value = oldmebxpass;
355 setupbin.records[0].variables.push(v);
356
357 // Create "New MEBx Password" variable
358 v = {};
359 v.moduleid = 1;
360 v.varid = 2;
361 v.length = -1;
362 v.value = newmebxpass;
363 setupbin.records[0].variables.push(v);
364
365 // Create "User Defined Certificate Addition" variable
366 v = {};
367 v.moduleid = 2;
368 v.varid = 8;
369 v.length = -1;
370 v.value = String.fromCharCode(2) + Buffer.from(wildcardCertSha256, 'hex').toString('binary') + String.fromCharCode(certRootName.length) + certRootName; // 2 = SHA256 hash type
371 setupbin.records[0].variables.push(v);
372
373 // Create "PKI DNS Suffix" variable
374 v = {};
375 v.moduleid = 2;
376 v.varid = 3;
377 v.length = -1;
378 v.value = trustedFQDN;
379 setupbin.records[0].variables.push(v);
380
381 // Create "ME Provision Halt Active" variable
382 v = {};
383 v.moduleid = 2;
384 v.varid = 28;
385 v.length = -1;
386 v.value = 0; // Stop
387 setupbin.records[0].variables.push(v);
388
389 // Write to log file
390 obj.logAmtActivation(domain, { time: new Date(), action: 'setupbin', domain: domain.id, userid: user._id, oldmebx: oldmebxpass, newmebx: newmebxpass, rootname: certRootName, hash: wildcardCertSha256, dns: trustedFQDN });
391
392 // Encode the setup.bin file
393 return AmtSetupBinStack.AmtSetupBinEncode(setupbin);
394 }
395
396
397 // Get a bare metal setup.bin file
398 obj.GetBareMetalSetupBinFile = function (amtacmactivation, oldmebxpass, newmebxpass, domain, user) {
399 // Create a setup.bin file for our own root cert
400 // Get the wiadcard certificate hash
401 var wildcardCertSha256 = null;
402 for (var i = 0; i < amtacmactivation.acmmatch.length; i++) { if (amtacmactivation.acmmatch[i].cn == '*') { wildcardCertSha256 = amtacmactivation.acmmatch[i].sha256; } }
403
404 // Create the Setup.bin stack
405 const AmtSetupBinStack = require('./amt/amt-setupbin')();
406 var setupbin = AmtSetupBinStack.AmtSetupBinCreate(3, 1); // Version 3, 1 = Records will not be consumed.
407 var certRootName = 'MeshCentral';
408
409 // Figure out what trusted FQDN to use.
410 var trustedFQDN = parent.config.settings.amtprovisioningserver.trustedfqdn
411
412 // Figure out the provisioning server port
413 var port = 9971;
414 if (typeof parent.config.settings.amtprovisioningserver.port == 'number') { port = parent.config.settings.amtprovisioningserver.port; }
415
416 // Get the provisioning server IP address from the config file
417 if (typeof parent.config.settings.amtprovisioningserver.ip != 'string') return null;
418 var ipaddr = parent.config.settings.amtprovisioningserver.ip;
419 var ipaddrSplit = ipaddr.split('.');
420 var ipaddrStr = String.fromCharCode(parseInt(ipaddrSplit[3])) + String.fromCharCode(parseInt(ipaddrSplit[2])) + String.fromCharCode(parseInt(ipaddrSplit[1])) + String.fromCharCode(parseInt(ipaddrSplit[0]));
421
422 // Create a new record
423 var r = {};
424 r.typeIdentifier = 1;
425 r.flags = 1; // Valid, unscrambled record.
426 r.chunkCount = 0;
427 r.headerByteCount = 0;
428 r.number = 0;
429 r.variables = [];
430 setupbin.records.push(r);
431
432 // Create "Current MEBx Password" variable
433 var v = {};
434 v.moduleid = 1;
435 v.varid = 1;
436 v.length = -1;
437 v.value = oldmebxpass;
438 setupbin.records[0].variables.push(v);
439
440 // Create "New MEBx Password" variable
441 v = {};
442 v.moduleid = 1;
443 v.varid = 2;
444 v.length = -1;
445 v.value = newmebxpass;
446 setupbin.records[0].variables.push(v);
447
448 // Create "User Defined Certificate Addition" variable
449 v = {};
450 v.moduleid = 2;
451 v.varid = 8;
452 v.length = -1;
453 v.value = String.fromCharCode(2) + Buffer.from(wildcardCertSha256, 'hex').toString('binary') + String.fromCharCode(certRootName.length) + certRootName; // 2 = SHA256 hash type
454 setupbin.records[0].variables.push(v);
455
456 // Create "PKI DNS Suffix" variable
457 v = {};
458 v.moduleid = 2;
459 v.varid = 3;
460 v.length = -1;
461 v.value = trustedFQDN;
462 setupbin.records[0].variables.push(v);
463
464 // Create "Configuration Server FQDN" variable
465 v = {};
466 v.moduleid = 2;
467 v.varid = 4;
468 v.length = -1;
469 v.value = trustedFQDN;
470 setupbin.records[0].variables.push(v);
471
472 // Create "Provisioning Server Address" variable
473 v = {};
474 v.moduleid = 2;
475 v.varid = 17;
476 v.length = -1;
477 v.value = ipaddrStr;
478 setupbin.records[0].variables.push(v);
479
480 // Create "Provisioning Server Port Number" variable
481 v = {};
482 v.moduleid = 2;
483 v.varid = 18;
484 v.length = -1;
485 v.value = port;
486 setupbin.records[0].variables.push(v);
487
488 // Create "ME Provision Halt Active" variable
489 v = {};
490 v.moduleid = 2;
491 v.varid = 28;
492 v.length = -1;
493 v.value = 1; // Start
494 setupbin.records[0].variables.push(v);
495
496 // Write to log file
497 obj.logAmtActivation(domain, { time: new Date(), action: 'setupbin-bare-metal', domain: domain.id, userid: user._id, oldmebx: oldmebxpass, newmebx: newmebxpass, rootname: certRootName, hash: wildcardCertSha256, dns: trustedFQDN, ip: ipaddr, port: port });
498
499 // Encode the setup.bin file
500 return AmtSetupBinStack.AmtSetupBinEncode(setupbin);
501 }
502
503 // Return the certificate of the remote HTTPS server
504 obj.loadPfxCertificate = function (filename, password) {
505 var r = { certs: [], keys: [] };
506 var pfxb64 = Buffer.from(obj.fs.readFileSync(filename)).toString('base64');
507 var pfx = obj.forge.pkcs12.pkcs12FromAsn1(obj.forge.asn1.fromDer(obj.forge.util.decode64(pfxb64)), true, password);
508
509 // Get the certs from certbags
510 var bags = pfx.getBags({ bagType: obj.forge.pki.oids.certBag });
511 for (var i = 0; i < bags[obj.forge.pki.oids.certBag].length; i++) { r.certs.push(bags[obj.forge.pki.oids.certBag][i].cert); }
512
513 // Get shrouded key from key bags
514 bags = pfx.getBags({ bagType: obj.forge.pki.oids.pkcs8ShroudedKeyBag });
515 for (var i = 0; i < bags[obj.forge.pki.oids.pkcs8ShroudedKeyBag].length; i++) { r.keys.push(bags[obj.forge.pki.oids.pkcs8ShroudedKeyBag][i].key); }
516 return r;
517 }
518
519 // Return a text file from a remote HTTPS server
520 obj.loadTextFile = function (url, tag, func) {
521 const u = new URL(url);
522 if (u.protocol == 'https:') {
523 // Read from HTTPS
524 const https = require('https');
525 const options = { timeout: 10000 };
526 if (process.env['HTTPS_PROXY'] || process.env['HTTP_PROXY'] || process.env['https_proxy'] || process.env['http_proxy']) {
527 options.agent = new (require('https-proxy-agent').HttpsProxyAgent)(process.env['HTTPS_PROXY'] || process.env['HTTP_PROXY'] || process.env['https_proxy'] || process.env['http_proxy']);
528 }
529 const req = https.get(url, options, function(resp) {
530 if (resp.statusCode < 200 || resp.statusCode >= 300) { resp.resume(); func(url, null, tag); return; }
531 var data = '';
532 resp.on('data', function(chunk) { data += chunk; });
533 resp.on('end', function() { func(url, data, tag); });
534 resp.on('error', function() { func(url, null, tag); });
535 });
536 req.on('error', function() { func(url, null, tag); });
537 req.on('timeout', function() { req.destroy(); func(url, null, tag); });
538 } else if (u.protocol == 'file:') {
539 // Read a file
540 obj.fs.readFile(url.substring(7), 'utf8', function (err, data) {
541 func(url, err ? null : data, tag);
542 });
543 } else { func(url, null, tag); }
544 };
545
546 // Return the certificate of the remote HTTPS server
547 obj.loadCertificate = function (url, hostname, tag, func) {
548 const u = new URL(url);
549 if (u.protocol == 'https:') {
550 // Read the certificate from HTTPS
551 if (hostname == null) { hostname = u.hostname; }
552 parent.debug('cert', "loadCertificate() - Loading certificate from " + u.hostname + ":" + (u.port ? u.port : 443) + ", Hostname: " + hostname + "...");
553 const tlssocket = obj.tls.connect((u.port ? u.port : 443), u.hostname, { servername: hostname, rejectUnauthorized: false }, function () {
554 this.xxcert = this.getPeerCertificate();
555 parent.debug('cert', "loadCertificate() - TLS connected, " + ((this.xxcert != null) ? "got certificate." : "no certificate."));
556 try { this.destroy(); } catch (ex) { }
557 this.xxfunc(this.xxurl, (this.xxcert == null)?null:(this.xxcert.raw.toString('binary')), hostname, this.xxtag);
558 });
559 tlssocket.xxurl = url;
560 tlssocket.xxfunc = func;
561 tlssocket.xxtag = tag;
562 tlssocket.on('error', function (error) { try { this.destroy(); } catch (ex) { } parent.debug('cert', "loadCertificate() - TLS error: " + error); this.xxfunc(this.xxurl, null, hostname, this.xxtag); });
563 } else if (u.protocol == 'file:') {
564 // Read the certificate from a file
565 obj.fs.readFile(url.substring(7), 'utf8', function (err, data) {
566 if (err) { func(url, null, hostname, tag); return; }
567 var x1 = data.indexOf('-----BEGIN CERTIFICATE-----'), x2 = data.indexOf('-----END CERTIFICATE-----');
568 if ((x1 >= 0) && (x2 > x1)) {
569 func(url, Buffer.from(data.substring(x1 + 27, x2), 'base64').toString('binary'), hostname, tag);
570 } else {
571 func(url, data, hostname, tag);
572 }
573 });
574 } else { func(url, null, hostname, tag); }
575 };
576
577 // Check if a configuration file exists
578 obj.fileExists = function (filename) {
579 if ((parent.configurationFiles != null) && (parent.configurationFiles[filename] != null)) { return true; }
580 var filePath = parent.getConfigFilePath(filename);
581 try { return obj.fs.statSync(filePath).isFile(); } catch (err) { return false; }
582 };
583
584 // Load a configuration file
585 obj.fileLoad = function (filename, encoding) {
586 if ((parent.configurationFiles != null) && (parent.configurationFiles[filename] != null)) {
587 if (typeof parent.configurationFiles[filename] == 'string') { return fixEndOfLines(parent.configurationFiles[filename]); }
588 return fixEndOfLines(parent.configurationFiles[filename].toString());
589 } else {
590 return fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath(filename), encoding));
591 }
592 }
593
594 // Return the SHA384 hash of the certificate public key
595 obj.getPublicKeyHash = function (cert) {
596 var publickey = obj.pki.certificateFromPem(cert).publicKey;
597 return obj.pki.getPublicKeyFingerprint(publickey, { encoding: 'hex', md: obj.forge.md.sha384.create() });
598 };
599
600 // Return the SHA1 hash of the certificate, return hex
601 obj.getCertHashSha1 = function (cert) {
602 try {
603 var md = obj.forge.md.sha1.create();
604 md.update(obj.forge.asn1.toDer(obj.pki.certificateToAsn1(obj.pki.certificateFromPem(cert))).getBytes());
605 return md.digest().toHex();
606 } catch (ex) {
607 // If this is not an RSA certificate, hash the raw PKCS7 out of the PEM file
608 var x1 = cert.indexOf('-----BEGIN CERTIFICATE-----'), x2 = cert.indexOf('-----END CERTIFICATE-----');
609 if ((x1 >= 0) && (x2 > x1)) {
610 return obj.crypto.createHash('sha1').update(Buffer.from(cert.substring(x1 + 27, x2), 'base64')).digest('hex');
611 } else { console.log("ERROR: Unable to decode certificate."); return null; }
612 }
613 };
614
615 // Return the SHA256 hash of the certificate, return hex
616 obj.getCertHashSha256 = function (cert) {
617 try {
618 var md = obj.forge.md.sha256.create();
619 md.update(obj.forge.asn1.toDer(obj.pki.certificateToAsn1(obj.pki.certificateFromPem(cert))).getBytes());
620 return md.digest().toHex();
621 } catch (ex) {
622 // If this is not an RSA certificate, hash the raw PKCS7 out of the PEM file
623 var x1 = cert.indexOf('-----BEGIN CERTIFICATE-----'), x2 = cert.indexOf('-----END CERTIFICATE-----');
624 if ((x1 >= 0) && (x2 > x1)) {
625 return obj.crypto.createHash('sha256').update(Buffer.from(cert.substring(x1 + 27, x2), 'base64')).digest('hex');
626 } else { console.log("ERROR: Unable to decode certificate."); return null; }
627 }
628 };
629
630 // Return the SHA384 hash of the certificate, return hex
631 obj.getCertHash = function (cert) {
632 try {
633 var md = obj.forge.md.sha384.create();
634 md.update(obj.forge.asn1.toDer(obj.pki.certificateToAsn1(obj.pki.certificateFromPem(cert))).getBytes());
635 return md.digest().toHex();
636 } catch (ex) {
637 // If this is not an RSA certificate, hash the raw PKCS7 out of the PEM file
638 var x1 = cert.indexOf('-----BEGIN CERTIFICATE-----'), x2 = cert.indexOf('-----END CERTIFICATE-----');
639 if ((x1 >= 0) && (x2 > x1)) {
640 return obj.crypto.createHash('sha384').update(Buffer.from(cert.substring(x1 + 27, x2), 'base64')).digest('hex');
641 } else { console.log("ERROR: Unable to decode certificate."); return null; }
642 }
643 };
644
645 // Return the SHA384 hash of the certificate public key
646 obj.getPublicKeyHashBinary = function (pem) {
647 const { X509Certificate } = require('crypto');
648 if (X509Certificate == null) {
649 // This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
650 return obj.pki.getPublicKeyFingerprint(obj.pki.certificateFromPem(pem).publicKey, { encoding: 'binary', md: obj.forge.md.sha384.create() });
651 } else {
652 // This version of NodeJS supports x509 certificates
653 var cert = new X509Certificate(pem);
654 return obj.crypto.createHash('sha384').update(cert.publicKey.export({ type: ((cert.publicKey.asymmetricKeyType == 'rsa') ? 'pkcs1' : 'spki'), format: 'der' })).digest('binary');
655 }
656 };
657
658 // Return the SHA384 hash of the certificate, return binary
659 obj.getCertHashBinary = function (cert) {
660 try {
661 // If this is a RSA certificate, we can use Forge to hash the ASN1
662 var md = obj.forge.md.sha384.create();
663 md.update(obj.forge.asn1.toDer(obj.pki.certificateToAsn1(obj.pki.certificateFromPem(cert))).getBytes());
664 return md.digest().getBytes();
665 } catch (ex) {
666 // If this is not an RSA certificate, hash the raw PKCS7 out of the PEM file
667 var x1 = cert.indexOf('-----BEGIN CERTIFICATE-----'), x2 = cert.indexOf('-----END CERTIFICATE-----');
668 if ((x1 >= 0) && (x2 > x1)) {
669 return obj.crypto.createHash('sha384').update(Buffer.from(cert.substring(x1 + 27, x2), 'base64')).digest('binary');
670 } else { console.log("ERROR: Unable to decode certificate."); return null; }
671 }
672 };
673
674 // Create a self-signed certificate
675 obj.GenerateRootCertificate = function (addThumbPrintToName, commonName, country, organization, strong) {
676 var keys = obj.pki.rsa.generateKeyPair({ bits: (strong == true) ? 3072 : 2048, e: 0x10001 });
677 var cert = obj.pki.createCertificate();
678 cert.publicKey = keys.publicKey;
679 cert.serialNumber = '' + require('crypto').randomBytes(4).readUInt32BE(0);
680 cert.validity.notBefore = new Date(2018, 0, 1);
681 cert.validity.notAfter = new Date(2049, 11, 31);
682 if (addThumbPrintToName === true) { commonName += '-' + obj.pki.getPublicKeyFingerprint(cert.publicKey, { encoding: 'hex' }).substring(0, 6); }
683 if (country == null) { country = "unknown"; }
684 if (organization == null) { organization = "unknown"; }
685 var attrs = [{ name: 'commonName', value: commonName }, { name: 'organizationName', value: organization }, { name: 'countryName', value: country }];
686 cert.setSubject(attrs);
687 cert.setIssuer(attrs);
688 // Create a root certificate
689 //cert.setExtensions([{ name: 'basicConstraints', cA: true }, { name: 'nsCertType', sslCA: true, emailCA: true, objCA: true }, { name: 'subjectKeyIdentifier' }]);
690 cert.setExtensions([{ name: 'basicConstraints', cA: true }, { name: 'subjectKeyIdentifier' }, { name: 'keyUsage', keyCertSign: true }]);
691 cert.sign(keys.privateKey, obj.forge.md.sha384.create());
692
693 return { cert: cert, key: keys.privateKey };
694 };
695
696 // Issue a certificate from a root
697 obj.IssueWebServerCertificate = function (rootcert, addThumbPrintToName, commonName, country, organization, extKeyUsage, strong) {
698 var keys = obj.pki.rsa.generateKeyPair({ bits: (strong == true) ? 3072 : 2048, e: 0x10001 });
699 var cert = obj.pki.createCertificate();
700 cert.publicKey = keys.publicKey;
701 cert.serialNumber = '' + require('crypto').randomBytes(4).readUInt32BE(0);
702 cert.validity.notBefore = new Date(2018, 0, 1);
703 cert.validity.notAfter = new Date(2049, 11, 31);
704 if (addThumbPrintToName === true) { commonName += "-" + obj.pki.getPublicKeyFingerprint(cert.publicKey, { encoding: 'hex' }).substring(0, 6); }
705 var attrs = [{ name: 'commonName', value: commonName }];
706 if (country != null) { attrs.push({ name: 'countryName', value: country }); }
707 if (organization != null) { attrs.push({ name: 'organizationName', value: organization }); }
708 cert.setSubject(attrs);
709 cert.setIssuer(rootcert.cert.subject.attributes);
710
711 if (extKeyUsage == null) { extKeyUsage = { name: 'extKeyUsage', serverAuth: true }; } else { extKeyUsage.name = 'extKeyUsage'; }
712 //var extensions = [{ name: 'basicConstraints', cA: false }, { name: 'keyUsage', keyCertSign: true, digitalSignature: true, nonRepudiation: true, keyEncipherment: true, dataEncipherment: true }, extKeyUsage, { name: "nsCertType", client: false, server: true, email: false, objsign: false, sslCA: false, emailCA: false, objCA: false }, { name: "subjectKeyIdentifier" }];
713 var extensions = [{ name: 'basicConstraints', cA: false }, { name: 'keyUsage', keyCertSign: false, digitalSignature: true, nonRepudiation: false, keyEncipherment: true, dataEncipherment: (extKeyUsage.serverAuth !== true) }, extKeyUsage, { name: "subjectKeyIdentifier" }];
714
715 if (extKeyUsage.serverAuth === true) {
716 // Set subjectAltName according to commonName parsing.
717 // Ideally, we should let opportunity in given interface to set any type of altNames according to node_forge library
718 // such as type 2, 6 and 7. (2 -> DNS, 6 -> URI, 7 -> IP)
719 var altNames = [];
720
721 // According to commonName parsing (IP or DNS), add URI and DNS and/or IP altNames
722 if (require('net').isIP(commonName)) {
723 // set both IP and DNS when commonName is an IP@
724 altNames.push({ type: 7, ip: commonName });
725 altNames.push({ type: 2, value: commonName });
726 } else {
727 // set only DNS when commonName is a FQDN
728 altNames.push({ type: 2, value: commonName });
729 }
730 altNames.push({ type: 6, value: 'http://' + commonName + '/' })
731
732 // Add localhost stuff for easy testing on localhost ;)
733 altNames.push({ type: 2, value: 'localhost' });
734 altNames.push({ type: 6, value: 'http://localhost/' });
735 altNames.push({ type: 7, ip: '127.0.0.1' });
736
737 extensions.push({ name: 'subjectAltName', altNames: altNames });
738 }
739
740 if (extKeyUsage.codeSign === true) {
741 extensions = [{ name: 'basicConstraints', cA: false }, { name: 'keyUsage', keyCertSign: false, digitalSignature: true, nonRepudiation: false, keyEncipherment: false, dataEncipherment: false }, { name: 'extKeyUsage', codeSigning: true }, { name: "subjectKeyIdentifier" }];
742 }
743
744 cert.setExtensions(extensions);
745 cert.sign(rootcert.key, obj.forge.md.sha384.create());
746
747 return { cert: cert, key: keys.privateKey };
748 };
749
750 // Make sure a string with Mac style CR endo of line is changed to Linux LF style.
751 function fixEndOfLines(str) {
752 if (typeof (str) != 'string') return str; // If this is not a string, do nothing.
753 var i = str.indexOf('-----'); // Remove everything before "-----".
754 if (i > 0) { str = str.substring(i); } // this solves problems with editors that save text file type indicators ahead of the text.
755 if ((typeof(str) != 'string') || (str.indexOf('\n') > 0)) return str; // If there is a \n in the file, keep the file as-is.
756 return str.split('\r').join('\n'); // If there is no \n, replace all \r with \n.
757 }
758
759 // Return true if the name is found in the certificates names, we support wildcard certificates
760 obj.compareCertificateNames = function (certNames, name) {
761 if (certNames == null) return false;
762 name = name.toLowerCase();
763 var xcertNames = [];
764 for (var i in certNames) { xcertNames.push(certNames[i].toLowerCase()); }
765 if (xcertNames.indexOf(name) >= 0) return true;
766 for (var i in xcertNames) {
767 if ((xcertNames[i].startsWith('*.') == true) && (name.endsWith(xcertNames[i].substring(1)) == true)) { return true; }
768 if (xcertNames[i].startsWith('http://*.') == true) {
769 if (name.endsWith(xcertNames[i].substring(8)) == true) { return true; }
770 if ((xcertNames[i].endsWith('/') == true) && (name.endsWith(xcertNames[i].substring(8, xcertNames[i].length - 1)) == true)) { return true; }
771 }
772 }
773 return false;
774 }
775
776 // Return true if the certificate is valid
777 obj.checkCertificate = function (pem, key) {
778 const { X509Certificate } = require('crypto');
779 if (X509Certificate == null) {
780 // This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
781 var cert = null;
782 try { cert = obj.pki.certificateFromPem(pem); } catch (ex) { return false; } // Unable to decode certificate
783 if (cert.serialNumber == '') return false; // Empty serial number is not allowed.
784 } else {
785 // This version of NodeJS supports x509 certificates
786 try {
787 const cert = new X509Certificate(pem);
788 if ((cert.serialNumber == '') || (cert.serialNumber == null)) return false; // Empty serial number is not allowed.
789 } catch (ex) { return false; } // Unable to decode certificate
790 }
791 return true;
792 }
793
794 // Get the Common Name from a certificate
795 obj.getCertificateCommonName = function (pem, field) {
796 if (field == null) { field = 'CN'; }
797 const { X509Certificate } = require('crypto');
798 if (X509Certificate == null) {
799 // This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
800 var cert = obj.pki.certificateFromPem(pem);
801 if (cert.subject.getField(field) != null) return cert.subject.getField(field).value;
802 } else {
803 // This version of NodeJS supports x509 certificates
804 const subjects = new X509Certificate(pem).subject.split('\n');
805 for (var i in subjects) { if (subjects[i].startsWith(field + '=')) { return subjects[i].substring(field.length + 1); } }
806 }
807 return null;
808 }
809
810 // Get the Issuer Common Name from a certificate
811 obj.getCertificateIssuerCommonName = function (pem, field) {
812 if (field == null) { field = 'CN'; }
813 const { X509Certificate } = require('crypto');
814 if (X509Certificate == null) {
815 // This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
816 var cert = obj.pki.certificateFromPem(pem);
817 if (cert.issuer.getField(field) != null) return cert.issuer.getField(field).value;
818 } else {
819 // This version of NodeJS supports x509 certificates
820 const subjects = new X509Certificate(pem).issuer.split('\n');
821 for (var i in subjects) { if (subjects[i].startsWith(field + '=')) { return subjects[i].substring(field.length + 1); } }
822 }
823 return null;
824 }
825
826 // Get the Common Name and alternate names from a certificate
827 obj.getCertificateAltNames = function (pem) {
828 const altNamesResults = [];
829 const { X509Certificate } = require('crypto');
830 if (X509Certificate == null) {
831 // This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
832 var cert = obj.pki.certificateFromPem(pem);
833 if (cert.subject.getField('CN') != null) { altNamesResults.push(cert.subject.getField('CN').value); }
834 var altNames = cert.getExtension('subjectAltName');
835 if (altNames) {
836 for (i = 0; i < altNames.altNames.length; i++) {
837 if ((altNames.altNames[i] != null) && (altNames.altNames[i].type === 2) && (typeof altNames.altNames[i].value === 'string')) {
838 var acn = altNames.altNames[i].value.toLowerCase();
839 if (altNamesResults.indexOf(acn) == -1) { altNamesResults.push(acn); }
840 }
841 }
842 }
843 } else {
844 // This version of NodeJS supports x509 certificates
845 const cert = new X509Certificate(pem);
846 const subjects = cert.subject.split('\n');
847 for (var i in subjects) { if (subjects[i].startsWith('CN=')) { altNamesResults.push(subjects[i].substring(3)); } }
848 var subjectAltNames = cert.subjectAltName;
849 if (subjectAltNames != null) {
850 subjectAltNames = subjectAltNames.split(', ');
851 for (var i = 0; i < subjectAltNames.length; i++) {
852 if (subjectAltNames[i].startsWith('DNS:') && altNamesResults.indexOf(subjectAltNames[i].substring(4)) == -1) {
853 altNamesResults.push(subjectAltNames[i].substring(4));
854 }
855 }
856 }
857 }
858 return altNamesResults;
859 }
860
861 // Get the expiration time from a certificate
862 obj.getCertificateExpire = function (pem) {
863 const altNamesResults = [];
864 const { X509Certificate } = require('crypto');
865 if (X509Certificate == null) {
866 // This version of NodeJS (<v15.6.0) does not support X509 certs, use Node-Forge instead which only supports RSA certs.
867 return Date.parse(parent.certificateOperations.forge.pki.certificateFromPem(parent.certificates.web.cert).validity.notAfter);
868 } else {
869 // This version of NodeJS supports x509 certificates
870 return Date.parse(new X509Certificate(pem).validTo);
871 }
872 }
873
874 // Decrypt private key if needed
875 obj.decryptPrivateKey = function (key) {
876 if (typeof key != 'string') return key;
877 var i = key.indexOf('-----BEGIN ENCRYPTED PRIVATE KEY-----');
878 var j = key.indexOf('-----END ENCRYPTED PRIVATE KEY-----');
879 if ((i >= 0) && (j > i)) {
880 var passwords = parent.config.settings.certificateprivatekeypassword;
881 if (parent.config.settings.certificateprivatekeypassword == null) { passwords = []; }
882 else if (typeof parent.config.settings.certificateprivatekeypassword == 'string') { passwords = [parent.config.settings.certificateprivatekeypassword ]; }
883 var privateKey = null;
884 for (var k in passwords) { if (privateKey == null) { try { privateKey = obj.pki.decryptRsaPrivateKey(key, passwords[k]); } catch (ex) { } } }
885 if (privateKey == null) {
886 console.log("Private certificate key is encrypted, but no correct password was found.");
887 console.log("Add the password to the \"certificatePrivateKeyPassword\" value in the Settings section of the config.json.");
888 console.log("Example: \"certificatePrivateKeyPassword\": [ \"MyPassword\" ]");
889 process.exit();
890 return null;
891 }
892 return obj.pki.privateKeyToPem(privateKey);
893 }
894 return key;
895 }
896
897 // Returns the web server TLS certificate and private key, if not present, create demonstration ones.
898 obj.GetMeshServerCertificate = function (args, config, func) {
899 var i = 0;
900 var certargs = args.cert;
901 var mpscertargs = args.mpscert;
902 var strongCertificate = (args.fastcert ? false : true);
903 var rcountmax = 5;
904 var caindex = 1;
905 var caok = false;
906 var calist = [];
907 var dnsname = null;
908 // commonName, country, organization
909
910 // If the certificates directory does not exist, create it.
911 if (!obj.dirExists(parent.datapath)) { obj.fs.mkdirSync(parent.datapath); }
912 var r = {};
913 var rcount = 0;
914
915 // If the root certificate already exist, load it
916 if (obj.fileExists('root-cert-public.crt') && obj.fileExists('root-cert-private.key')) {
917 var rootCertificate = obj.fileLoad('root-cert-public.crt', 'utf8');
918 var rootPrivateKey = obj.decryptPrivateKey(obj.fileLoad('root-cert-private.key', 'utf8'));
919 r.root = { cert: rootCertificate, key: rootPrivateKey };
920 rcount++;
921
922 // Check if the root certificate has the "Certificate Signing (04)" Key usage.
923 // This option is required for newer versions of Intel AMT for CIRA/WS-EVENTS.
924 var xroot = obj.pki.certificateFromPem(rootCertificate);
925 var xext = xroot.getExtension('keyUsage');
926 if ((xext == null) || (xext.keyCertSign !== true) || (xroot.serialNumber == '')) {
927 // We need to fix this certificate
928 parent.common.moveOldFiles(['root-cert-public-backup.crt']);
929 obj.fs.writeFileSync(parent.getConfigFilePath('root-cert-public-backup.crt'), rootCertificate);
930 if (xroot.serialNumber == '') { console.log("Fixing root certificate to add serial number..."); xroot.serialNumber = '' + require('crypto').randomBytes(4).readUInt32BE(0); }
931 if ((xext == null) || (xext.keyCertSign !== true)) { console.log("Fixing root certificate to add signing key usage..."); xroot.setExtensions([{ name: 'basicConstraints', cA: true }, { name: 'subjectKeyIdentifier' }, { name: 'keyUsage', keyCertSign: true }]); }
932 var xrootPrivateKey = obj.pki.privateKeyFromPem(rootPrivateKey);
933 xroot.sign(xrootPrivateKey, obj.forge.md.sha384.create());
934 r.root.cert = obj.pki.certificateToPem(xroot);
935 parent.common.moveOldFiles([parent.getConfigFilePath('root-cert-public.crt')]);
936 try { obj.fs.writeFileSync(parent.getConfigFilePath('root-cert-public.crt'), r.root.cert); } catch (ex) { }
937 }
938 }
939
940 // If web certificate exist, load it as default. This is useful for agent-only port. Load both certificate and private key
941 if (obj.fileExists('webserver-cert-public.crt') && obj.fileExists('webserver-cert-private.key')) {
942 r.webdefault = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('webserver-cert-private.key', 'utf8')) };
943 if (obj.checkCertificate(r.webdefault.cert, r.webdefault.key) == false) { delete r.webdefault; }
944 }
945
946 if (args.tlsoffload) {
947 // If the web certificate already exist, load it. Load just the certificate since we are in TLS offload situation
948 if (obj.fileExists('webserver-cert-public.crt')) {
949 r.web = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8') };
950 if (obj.checkCertificate(r.web.cert, null) == false) { delete r.web; } else { rcount++; }
951 }
952 } else {
953 // If the web certificate already exist, load it. Load both certificate and private key
954 if (obj.fileExists('webserver-cert-public.crt') && obj.decryptPrivateKey(obj.fileExists('webserver-cert-private.key'))) {
955 r.web = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('webserver-cert-private.key', 'utf8')) };
956 if (obj.checkCertificate(r.web.cert, r.web.key) == false) { delete r.web; } else { rcount++; }
957 }
958 }
959
960 // If the mps certificate already exist, load it
961 if (obj.fileExists('mpsserver-cert-public.crt') && obj.fileExists('mpsserver-cert-private.key')) {
962 r.mps = { cert: obj.fileLoad('mpsserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('mpsserver-cert-private.key', 'utf8')) };
963 if (obj.checkCertificate(r.mps.cert, r.mps.key) == false) { delete r.mps; } else { rcount++; }
964 }
965
966 // If the agent certificate already exist, load it
967 if (obj.fileExists("agentserver-cert-public.crt") && obj.fileExists("agentserver-cert-private.key")) {
968 r.agent = { cert: obj.fileLoad("agentserver-cert-public.crt", 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad("agentserver-cert-private.key", 'utf8')) };
969 if (obj.checkCertificate(r.agent.cert, r.agent.key) == false) { delete r.agent; } else { rcount++; }
970 }
971
972 // If the code signing certificate already exist, load it
973 if (obj.fileExists("codesign-cert-public.crt") && obj.fileExists("codesign-cert-private.key")) {
974 r.codesign = { cert: obj.fileLoad("codesign-cert-public.crt", 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad("codesign-cert-private.key", 'utf8')) };
975 if (obj.checkCertificate(r.codesign.cert, r.codesign.key) == false) { delete r.codesign; } else { rcount++; }
976 } else {
977 // If we are reading certificates from a database or vault and are just missing the code signing cert, skip it.
978 if (parent.configurationFiles != null) { rcount++; }
979 }
980
981 // If the swarm server certificate exist, load it (This is an optional certificate)
982 if (obj.fileExists('swarmserver-cert-public.crt') && obj.fileExists('swarmserver-cert-private.key')) {
983 r.swarmserver = { cert: obj.fileLoad('swarmserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('swarmserver-cert-private.key', 'utf8')) };
984 if (obj.checkCertificate(r.swarmserver.cert, r.swarmserver.key) == false) { delete r.swarmserver; }
985 }
986
987 // If the swarm server root certificate exist, load it (This is an optional certificate)
988 if (obj.fileExists('swarmserverroot-cert-public.crt')) {
989 r.swarmserverroot = { cert: obj.fileLoad('swarmserverroot-cert-public.crt', 'utf8') };
990 if (obj.checkCertificate(r.swarmserverroot.cert, null) == false) { delete r.swarmserverroot; }
991 }
992
993 // If CA certificates are present, load them
994 do {
995 caok = false;
996 if (obj.fileExists('webserver-cert-chain' + caindex + '.crt')) {
997 calist.push(obj.fileLoad('webserver-cert-chain' + caindex + '.crt', 'utf8'));
998 caok = true;
999 }
1000 caindex++;
1001 } while (caok === true);
1002 if (r.web != null) { r.web.ca = calist; }
1003
1004 // Decode certificate arguments
1005 var commonName = 'un-configured';
1006 var country = null;
1007 var organization = null;
1008 var forceWebCertGen = 0;
1009 var forceMpsCertGen = 0;
1010 var forceCodeCertGen = 0;
1011 if (certargs != undefined) {
1012 var xargs = certargs.split(',');
1013 if (xargs.length > 0) { commonName = xargs[0]; }
1014 if (xargs.length > 1) { country = xargs[1]; }
1015 if (xargs.length > 2) { organization = xargs[2]; }
1016 }
1017
1018 // Decode MPS certificate arguments, this is for the Intel AMT CIRA server
1019 var mpsCommonName = ((config.settings != null) && (typeof config.settings.mpsaliashost == 'string')) ? config.settings.mpsaliashost : commonName;
1020 var mpsCountry = country;
1021 var mpsOrganization = organization;
1022 if (mpscertargs !== undefined) {
1023 var xxargs = mpscertargs.split(',');
1024 if (xxargs.length > 0) { mpsCommonName = xxargs[0]; }
1025 if (xxargs.length > 1) { mpsCountry = xxargs[1]; }
1026 if (xxargs.length > 2) { mpsOrganization = xxargs[2]; }
1027 }
1028
1029 if (rcount === rcountmax) {
1030 // Fetch the certificates names for the main certificate
1031 r.AmtMpsName = obj.getCertificateCommonName(r.mps.cert);
1032 r.WebIssuer = obj.getCertificateIssuerCommonName(r.web.cert);
1033 r.CommonName = obj.getCertificateCommonName(r.web.cert);
1034 r.CommonNames = obj.getCertificateAltNames(r.web.cert);
1035 r.RootName = obj.getCertificateCommonName(r.root.cert);
1036 r.CodeCertName = obj.getCertificateCommonName(r.codesign.cert);
1037
1038 // If the "cert" name is not set, try to use the certificate CN instead (ok if the certificate is not wildcard).
1039 if (commonName == 'un-configured') {
1040 if (r.CommonName.startsWith('*.')) { console.log("ERROR: Must specify a server full domain name in Config.json->Settings->Cert when using a wildcard certificate."); process.exit(0); return; }
1041 commonName = r.CommonName;
1042 }
1043 }
1044
1045 // Look for domains that have DNS names and load their certificates
1046 r.dns = {};
1047 for (i in config.domains) {
1048 if ((i != '') && (config.domains[i] != null) && (config.domains[i].dns != null)) {
1049 dnsname = config.domains[i].dns;
1050 // Check if this domain matches a parent wildcard cert, if so, use the parent cert.
1051 if (obj.compareCertificateNames(r.CommonNames, dnsname) == true) {
1052 r.dns[i] = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('webserver-cert-private.key', 'utf8')) };
1053 } else {
1054 if (args.tlsoffload) {
1055 // If the web certificate already exist, load it. Load just the certificate since we are in TLS offload situation
1056 if (obj.fileExists('webserver-' + i + '-cert-public.crt')) {
1057 r.dns[i] = { cert: obj.fileLoad('webserver-' + i + '-cert-public.crt', 'utf8') };
1058 config.domains[i].certs = r.dns[i];
1059 } else {
1060 console.log("WARNING: File \"webserver-" + i + "-cert-public.crt\" missing, domain \"" + i + "\" will not work correctly.");
1061 rcountmax++;
1062 }
1063 } else {
1064 // If the web certificate already exist, load it. Load both certificate and private key
1065 if (obj.fileExists('webserver-' + i + '-cert-public.crt') && obj.fileExists('webserver-' + i + '-cert-private.key')) {
1066 r.dns[i] = { cert: obj.fileLoad('webserver-' + i + '-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('webserver-' + i + '-cert-private.key', 'utf8')) };
1067 config.domains[i].certs = r.dns[i];
1068 // If CA certificates are present, load them
1069 caindex = 1;
1070 r.dns[i].ca = [];
1071 do {
1072 caok = false;
1073 if (obj.fileExists('webserver-' + i + '-cert-chain' + caindex + '.crt')) {
1074 r.dns[i].ca.push(obj.fileLoad('webserver-' + i + '-cert-chain' + caindex + '.crt', 'utf8'));
1075 caok = true;
1076 }
1077 caindex++;
1078 } while (caok === true);
1079 } else {
1080 rcountmax++; // This certificate must be generated
1081 }
1082 }
1083 }
1084 }
1085 }
1086
1087 // If we have all the certificates we need, stop here.
1088 if (rcount === rcountmax) {
1089 if ((certargs == null) && (mpscertargs == null)) { if (func != undefined) { func(r); } return r; } // If no certificate arguments are given, keep the certificate
1090 const xcountry = obj.getCertificateCommonName(r.web.cert, 'C');
1091 const xorganization = obj.getCertificateCommonName(r.web.cert, 'O');
1092 if (certargs == null) { commonName = r.CommonName; country = xcountry; organization = xorganization; }
1093
1094 // Check if we have correct certificates.
1095 if (obj.compareCertificateNames(r.CommonNames, commonName) == false) { console.log("Error: " + commonName + " does not match name in TLS certificate: " + r.CommonNames.join(', ')); forceWebCertGen = 1; } else { r.CommonName = commonName; }
1096 if (r.AmtMpsName != mpsCommonName) { forceMpsCertGen = 1; }
1097 if (r.CodeCertName.startsWith(commonName) === false) { forceCodeCertGen = 1; }
1098 if (args.keepcerts == true) { forceWebCertGen = 0; forceMpsCertGen = 0; forceCodeCertGen = 0; r.CommonName = commonName; }
1099
1100 // If the certificates matches what we want, use them.
1101 if ((forceWebCertGen == 0) && (forceMpsCertGen == 0) && (forceCodeCertGen == 0)) {
1102 if (func !== null) { func(r); }
1103 return r;
1104 }
1105 }
1106
1107 if (parent.configurationFiles != null) {
1108 console.log("Error: Vault/Database missing some certificates.");
1109 if (r.root == null) { console.log(' Code signing certificate is missing.'); }
1110 if (r.web == null) { console.log(' HTTPS web certificate is missing.'); }
1111 if (r.mps == null) { console.log(' Intel AMT MPS certificate is missing.'); }
1112 if (r.agent == null) { console.log(' Server agent authentication certificate is missing.'); }
1113 if (r.codesign == null) { console.log(' Agent code signing certificate is missing.'); }
1114 process.exit(0);
1115 return null;
1116 }
1117
1118 console.log("Generating certificates, may take a few minutes...");
1119 parent.updateServerState('state', 'generatingcertificates');
1120
1121 // If a certificate is missing, but web certificate is present and --cert is not used, set the names to be the same as the web certificate
1122 if ((certargs == null) && (r.web != null)) {
1123 var webCertificate = obj.pki.certificateFromPem(r.web.cert);
1124 commonName = webCertificate.subject.getField('CN').value;
1125 var xcountryField = webCertificate.subject.getField('C');
1126 if (xcountryField != null) { country = xcountryField.value; }
1127 var xorganizationField = webCertificate.subject.getField('O');
1128 if (xorganizationField != null) { organization = xorganizationField.value; }
1129 }
1130
1131 var rootCertAndKey, rootCertificate, rootPrivateKey, rootName;
1132 if (r.root == null) {
1133 // If the root certificate does not exist, create one
1134 console.log("Generating root certificate...");
1135 if (typeof args.rootcertcommonname == 'string') {
1136 // If a root certificate common name is specified, use it.
1137 rootCertAndKey = obj.GenerateRootCertificate(false, args.rootcertcommonname, null, null, strongCertificate);
1138 } else {
1139 // A root certificate common name is not specified, use the default one.
1140 rootCertAndKey = obj.GenerateRootCertificate(true, 'MeshCentralRoot', null, null, strongCertificate);
1141 }
1142 rootCertificate = obj.pki.certificateToPem(rootCertAndKey.cert);
1143 rootPrivateKey = obj.pki.privateKeyToPem(rootCertAndKey.key);
1144 parent.common.moveOldFiles([parent.getConfigFilePath('root-cert-public.crt'), parent.getConfigFilePath('root-cert-private.key')]);
1145 obj.fs.writeFileSync(parent.getConfigFilePath('root-cert-public.crt'), rootCertificate);
1146 obj.fs.writeFileSync(parent.getConfigFilePath('root-cert-private.key'), rootPrivateKey);
1147 } else {
1148 // Keep the root certificate we have
1149 rootCertAndKey = { cert: obj.pki.certificateFromPem(r.root.cert), key: obj.pki.privateKeyFromPem(r.root.key) };
1150 rootCertificate = r.root.cert;
1151 rootPrivateKey = r.root.key;
1152 }
1153 var rootName = rootCertAndKey.cert.subject.getField('CN').value;
1154
1155 // If the web certificate does not exist, create one
1156 var webCertAndKey, webCertificate, webPrivateKey;
1157 if ((r.web == null) || (forceWebCertGen === 1)) {
1158 console.log("Generating HTTPS certificate...");
1159 webCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, commonName, country, organization, null, strongCertificate);
1160 webCertificate = obj.pki.certificateToPem(webCertAndKey.cert);
1161 webPrivateKey = obj.pki.privateKeyToPem(webCertAndKey.key);
1162 parent.common.moveOldFiles([parent.getConfigFilePath('webserver-cert-public.crt'), parent.getConfigFilePath('webserver-cert-private.key')]);
1163 obj.fs.writeFileSync(parent.getConfigFilePath('webserver-cert-public.crt'), webCertificate);
1164 obj.fs.writeFileSync(parent.getConfigFilePath('webserver-cert-private.key'), webPrivateKey);
1165 } else {
1166 // Keep the console certificate we have
1167 if (args.tlsoffload) {
1168 webCertAndKey = { cert: obj.pki.certificateFromPem(r.web.cert) };
1169 webCertificate = r.web.cert;
1170 } else {
1171 webCertAndKey = { cert: obj.pki.certificateFromPem(r.web.cert), key: obj.pki.privateKeyFromPem(r.web.key) };
1172 webCertificate = r.web.cert;
1173 webPrivateKey = r.web.key;
1174 }
1175 }
1176 var webIssuer = null;
1177 if (webCertAndKey.cert.issuer.getField('CN') != null) { webIssuer = webCertAndKey.cert.issuer.getField('CN').value; }
1178
1179 // If the mesh agent server certificate does not exist, create one
1180 var agentCertAndKey, agentCertificate, agentPrivateKey;
1181 if (r.agent == null) {
1182 console.log("Generating MeshAgent certificate...");
1183 agentCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, true, 'MeshCentralAgentServer', country, organization, { }, strongCertificate);
1184 agentCertificate = obj.pki.certificateToPem(agentCertAndKey.cert);
1185 agentPrivateKey = obj.pki.privateKeyToPem(agentCertAndKey.key);
1186 parent.common.moveOldFiles([parent.getConfigFilePath('agentserver-cert-public.crt'), parent.getConfigFilePath('agentserver-cert-private.key')]);
1187 obj.fs.writeFileSync(parent.getConfigFilePath('agentserver-cert-public.crt'), agentCertificate);
1188 obj.fs.writeFileSync(parent.getConfigFilePath('agentserver-cert-private.key'), agentPrivateKey);
1189 } else {
1190 // Keep the mesh agent server certificate we have
1191 agentCertAndKey = { cert: obj.pki.certificateFromPem(r.agent.cert), key: obj.pki.privateKeyFromPem(r.agent.key) };
1192 agentCertificate = r.agent.cert;
1193 agentPrivateKey = r.agent.key;
1194 }
1195
1196 // If the code signing certificate does not exist, create one
1197 var codesignCertAndKey, codesignCertificate, codesignPrivateKey;
1198 if ((r.codesign == null) || (forceCodeCertGen === 1)) {
1199 console.log("Generating code signing certificate...");
1200 codesignCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, true, commonName, country, organization, { codeSign: true }, strongCertificate);
1201 codesignCertificate = obj.pki.certificateToPem(codesignCertAndKey.cert);
1202 codesignPrivateKey = obj.pki.privateKeyToPem(codesignCertAndKey.key);
1203 parent.common.moveOldFiles([parent.getConfigFilePath('codesign-cert-public.crt'), parent.getConfigFilePath('codesign-cert-private.key')]);
1204 obj.fs.writeFileSync(parent.getConfigFilePath('codesign-cert-public.crt'), codesignCertificate);
1205 obj.fs.writeFileSync(parent.getConfigFilePath('codesign-cert-private.key'), codesignPrivateKey);
1206 } else {
1207 // Keep the code signing certificate we have
1208 codesignCertAndKey = { cert: obj.pki.certificateFromPem(r.codesign.cert), key: obj.pki.privateKeyFromPem(r.codesign.key) };
1209 codesignCertificate = r.codesign.cert;
1210 codesignPrivateKey = r.codesign.key;
1211 }
1212
1213 // If the Intel AMT MPS certificate does not exist, create one
1214 var mpsCertAndKey, mpsCertificate, mpsPrivateKey;
1215 if ((r.mps == null) || (forceMpsCertGen === 1)) {
1216 console.log("Generating Intel AMT MPS certificate...");
1217 mpsCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, mpsCommonName, mpsCountry, mpsOrganization, null, false);
1218 mpsCertificate = obj.pki.certificateToPem(mpsCertAndKey.cert);
1219 mpsPrivateKey = obj.pki.privateKeyToPem(mpsCertAndKey.key);
1220 parent.common.moveOldFiles([parent.getConfigFilePath('mpsserver-cert-public.crt'), parent.getConfigFilePath('mpsserver-cert-private.key')]);
1221 obj.fs.writeFileSync(parent.getConfigFilePath('mpsserver-cert-public.crt'), mpsCertificate);
1222 obj.fs.writeFileSync(parent.getConfigFilePath('mpsserver-cert-private.key'), mpsPrivateKey);
1223 } else {
1224 // Keep the console certificate we have
1225 mpsCertAndKey = { cert: obj.pki.certificateFromPem(r.mps.cert), key: obj.pki.privateKeyFromPem(r.mps.key) };
1226 mpsCertificate = r.mps.cert;
1227 mpsPrivateKey = r.mps.key;
1228 }
1229
1230 r = { root: { cert: rootCertificate, key: rootPrivateKey }, web: { cert: webCertificate, key: webPrivateKey, ca: [] }, webdefault: { cert: webCertificate, key: webPrivateKey, ca: [] }, mps: { cert: mpsCertificate, key: mpsPrivateKey }, agent: { cert: agentCertificate, key: agentPrivateKey }, codesign: { cert: codesignCertificate, key: codesignPrivateKey }, ca: calist, CommonName: commonName, RootName: rootName, AmtMpsName: mpsCommonName, dns: {}, WebIssuer: webIssuer };
1231
1232 // Fetch the certificates names for the main certificate
1233 var webCertificate = obj.pki.certificateFromPem(r.web.cert);
1234 if (webCertificate.issuer.getField('CN') != null) { r.WebIssuer = webCertificate.issuer.getField('CN').value; } else { r.WebIssuer = null; }
1235 r.CommonName = webCertificate.subject.getField('CN').value;
1236 if (r.CommonName.startsWith('*.')) {
1237 if (commonName.indexOf('.') == -1) { console.log("ERROR: Must specify a server full domain name in Config.json->Settings->Cert when using a wildcard certificate."); process.exit(0); return; }
1238 if (commonName.startsWith('*.')) { console.log("ERROR: Server can't use a wildcard name: " + commonName); process.exit(0); return; }
1239 r.CommonName = commonName;
1240 }
1241 r.CommonNames = [r.CommonName.toLowerCase()];
1242 var altNames = webCertificate.getExtension('subjectAltName');
1243 if (altNames) {
1244 for (i = 0; i < altNames.altNames.length; i++) {
1245 if ((altNames.altNames[i] != null) && (altNames.altNames[i].type === 2) && (typeof altNames.altNames[i].value === 'string')) {
1246 var acn = altNames.altNames[i].value.toLowerCase();
1247 if (r.CommonNames.indexOf(acn) == -1) { r.CommonNames.push(acn); }
1248 }
1249 }
1250 }
1251 var rootCertificate = obj.pki.certificateFromPem(r.root.cert);
1252 r.RootName = rootCertificate.subject.getField('CN').value;
1253
1254 // Look for domains with DNS names that have no certificates and generated them.
1255 for (i in config.domains) {
1256 if ((i != '') && (config.domains[i] != null) && (config.domains[i].dns != null)) {
1257 dnsname = config.domains[i].dns;
1258 // Check if this domain matches a parent wildcard cert, if so, use the parent cert.
1259 if (obj.compareCertificateNames(r.CommonNames, dnsname) == true) {
1260 r.dns[i] = { cert: obj.fileLoad('webserver-cert-public.crt', 'utf8'), key: obj.decryptPrivateKey(obj.fileLoad('webserver-cert-private.key', 'utf8')) };
1261 } else {
1262 if (!args.tlsoffload) {
1263 // If the web certificate does not exist, create it
1264 if ((obj.fileExists('webserver-' + i + '-cert-public.crt') === false) || (obj.fileExists('webserver-' + i + '-cert-private.key') === false)) {
1265 console.log('Generating HTTPS certificate for ' + i + '...');
1266 var xwebCertAndKey = obj.IssueWebServerCertificate(rootCertAndKey, false, dnsname, country, organization, null, strongCertificate);
1267 var xwebCertificate = obj.pki.certificateToPem(xwebCertAndKey.cert);
1268 var xwebPrivateKey = obj.pki.privateKeyToPem(xwebCertAndKey.key);
1269 parent.common.moveOldFiles([ parent.getConfigFilePath('webserver-' + i + '-cert-public.crt'), parent.getConfigFilePath('webserver-' + i + '-cert-private.key') ]);
1270 obj.fs.writeFileSync(parent.getConfigFilePath('webserver-' + i + '-cert-public.crt'), xwebCertificate);
1271 obj.fs.writeFileSync(parent.getConfigFilePath('webserver-' + i + '-cert-private.key'), xwebPrivateKey);
1272 r.dns[i] = { cert: xwebCertificate, key: xwebPrivateKey };
1273 config.domains[i].certs = r.dns[i];
1274
1275 // If CA certificates are present, load them
1276 caindex = 1;
1277 r.dns[i].ca = [];
1278 do {
1279 caok = false;
1280 if (obj.fileExists('webserver-' + i + '-cert-chain' + caindex + '.crt')) {
1281 r.dns[i].ca.push(fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath('webserver-' + i + '-cert-chain' + caindex + '.crt'), 'utf8')));
1282 caok = true;
1283 }
1284 caindex++;
1285 } while (caok === true);
1286 }
1287 }
1288 }
1289 }
1290 }
1291
1292 // If the swarm server certificate exist, load it (This is an optional certificate)
1293 if (obj.fileExists('swarmserver-cert-public.crt') && obj.fileExists('swarmserver-cert-private.key')) {
1294 r.swarmserver = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath('swarmserver-cert-public.crt'), 'utf8')), key: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath("swarmserver-cert-private.key"), 'utf8')) };
1295 }
1296
1297 // If the swarm server root certificate exist, load it (This is an optional certificate)
1298 if (obj.fileExists('swarmserverroot-cert-public.crt')) {
1299 r.swarmserverroot = { cert: fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath('swarmserverroot-cert-public.crt'), 'utf8')) };
1300 }
1301
1302 // If CA certificates are present, load them
1303 if (r.web != null) {
1304 caindex = 1;
1305 r.web.ca = [];
1306 do {
1307 caok = false;
1308 if (obj.fileExists('webserver-cert-chain' + caindex + '.crt')) {
1309 r.web.ca.push(fixEndOfLines(obj.fs.readFileSync(parent.getConfigFilePath('webserver-cert-chain' + caindex + '.crt'), 'utf8')));
1310 caok = true;
1311 }
1312 caindex++;
1313 } while (caok === true);
1314 }
1315
1316 if (func != undefined) { func(r); }
1317 return r;
1318 };
1319
1320 // Accelerators, used to dispatch work to other processes
1321 const fork = require('child_process').fork;
1322 const program = require('path').join(__dirname, 'meshaccelerator.js');
1323 const acceleratorTotalCount = require('os').cpus().length; // TODO: Check if this accelerator can scale.
1324 var acceleratorCreateCount = acceleratorTotalCount;
1325 var freeAccelerators = [];
1326 var pendingAccelerator = [];
1327 obj.acceleratorCertStore = null;
1328
1329 // Accelerator Stats
1330 var getAcceleratorFuncCalls = 0;
1331 var acceleratorStartFuncCall = 0;
1332 var acceleratorPerformSignatureFuncCall = 0;
1333 var acceleratorPerformSignaturePushFuncCall = 0;
1334 var acceleratorPerformSignatureRunFuncCall = 0;
1335 var acceleratorMessage = 0;
1336 var acceleratorMessageException = 0;
1337 var acceleratorMessageLastException = null;
1338 var acceleratorException = 0;
1339 var acceleratorLastException = null;
1340
1341 // Get stats about the accelerators
1342 obj.getAcceleratorStats = function () {
1343 return {
1344 acceleratorTotalCount: acceleratorTotalCount,
1345 acceleratorCreateCount: acceleratorCreateCount,
1346 freeAccelerators: freeAccelerators.length,
1347 pendingAccelerator: pendingAccelerator.length,
1348 getAcceleratorFuncCalls: getAcceleratorFuncCalls,
1349 startFuncCall: acceleratorStartFuncCall,
1350 performSignatureFuncCall: acceleratorPerformSignatureFuncCall,
1351 performSignaturePushFuncCall: acceleratorPerformSignaturePushFuncCall,
1352 performSignatureRunFuncCall: acceleratorPerformSignatureRunFuncCall,
1353 message: acceleratorMessage,
1354 messageException: acceleratorMessageException,
1355 messageLastException: acceleratorMessageLastException,
1356 exception: acceleratorException,
1357 lastException: acceleratorLastException
1358 };
1359 }
1360
1361 // Create a new accelerator module
1362 obj.getAccelerator = function () {
1363 getAcceleratorFuncCalls++;
1364 if (obj.acceleratorCertStore == null) { return null; }
1365 if (freeAccelerators.length > 0) { return freeAccelerators.pop(); }
1366 if (acceleratorCreateCount > 0) {
1367 acceleratorCreateCount--;
1368 var accelerator = fork(program, [], { stdio: ['pipe', 'pipe', 'pipe', 'ipc'] });
1369 accelerator.accid = acceleratorCreateCount;
1370 accelerator.on('message', function (message) {
1371 acceleratorMessage++;
1372 if (this.x.func) { this.x.func(this.x.tag, message); }
1373 delete this.x;
1374 if (pendingAccelerator.length > 0) { this.send(this.x = pendingAccelerator.shift()); } else { freeAccelerators.push(this); }
1375 });
1376 accelerator.on('exit', function (code) {
1377 if (this.x) { pendingAccelerator.push(this.x); delete this.x; }
1378 acceleratorCreateCount++;
1379 if (pendingAccelerator.length > 0) { var acc = obj.getAccelerator(); acc.send(acc.x = pendingAccelerator.shift()); }
1380 });
1381 accelerator.on('error', function (code) { }); // Not sure if somethign should be done here to help kill the process.
1382 accelerator.send({ action: 'setState', certs: obj.acceleratorCertStore });
1383 return accelerator;
1384 }
1385 return null;
1386 };
1387
1388 // Set the state of the accelerators. This way, we don"t have to send certificate & keys to them each time.
1389 obj.acceleratorStart = function (certificates) {
1390 acceleratorStartFuncCall++;
1391 if (obj.acceleratorCertStore != null) { console.error("ERROR: Accelerators can only be started once."); return; }
1392 obj.acceleratorCertStore = [{ cert: certificates.agent.cert, key: certificates.agent.key }];
1393 if (certificates.swarmserver != null) { obj.acceleratorCertStore.push({ cert: certificates.swarmserver.cert, key: certificates.swarmserver.key }); }
1394 };
1395
1396 // Perform any RSA signature, just pass in the private key and data.
1397 obj.acceleratorPerformSignature = function (privatekey, data, tag, func) {
1398 acceleratorPerformSignatureFuncCall++;
1399 if (acceleratorTotalCount <= 1) {
1400 // No accelerators available
1401 if (typeof privatekey == 'number') { privatekey = obj.acceleratorCertStore[privatekey].key; }
1402 const sign = obj.crypto.createSign('SHA384');
1403 sign.end(Buffer.from(data, 'binary'));
1404 try { func(tag, sign.sign(privatekey).toString('binary')); } catch (ex) { acceleratorMessageException++; acceleratorMessageLastException = ex; }
1405 } else {
1406 var acc = obj.getAccelerator();
1407 if (acc == null) {
1408 // Add to pending accelerator workload
1409 acceleratorPerformSignaturePushFuncCall++;
1410 pendingAccelerator.push({ action: 'sign', key: privatekey, data: data, tag: tag, func: func });
1411 } else {
1412 // Send to accelerator now
1413 acceleratorPerformSignatureRunFuncCall++;
1414 acc.send(acc.x = { action: 'sign', key: privatekey, data: data, tag: tag, func: func });
1415 }
1416 }
1417 };
1418
1419 // Perform any general operation
1420 obj.acceleratorPerformOperation = function (operation, data, tag, func) {
1421 var acc = obj.getAccelerator();
1422 if (acc == null) {
1423 // Add to pending accelerator workload
1424 acceleratorPerformSignaturePushFuncCall++;
1425 pendingAccelerator.push({ action: operation, data: data, tag: tag, func: func });
1426 } else {
1427 // Send to accelerator now
1428 acceleratorPerformSignatureRunFuncCall++;
1429 acc.send(acc.x = { action: operation, data: data, tag: tag, func: func });
1430 }
1431 };
1432
1433 return obj;
1434 };