| 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 | }; |