certoperations.js 83 KB

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