| 1 | /** |
| 2 | * @description MeshCentral Intel AMT Hello server |
| 3 | * @author 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 | // Construct the Intel AMT hello server. This is used for Intel AMT bare-metal activation on the local LAN. |
| 18 | // This server can receive a notification from Intel AMT and attempt activation. |
| 19 | // In Intel documentation, this is called the Setup and Configuration Application (SCA) |
| 20 | module.exports.CreateAmtProvisioningServer = function (parent, config) { |
| 21 | var obj = {}; |
| 22 | obj.meshid = config.devicegroup; // This is the device group identifier that all activated devices will be added to. |
| 23 | |
| 24 | // WSMAN stack |
| 25 | const CreateWsmanComm = require('./amt/amt-wsman-comm'); |
| 26 | const WsmanStackCreateService = require('./amt/amt-wsman'); |
| 27 | const AmtStackCreateService = require('./amt/amt'); |
| 28 | |
| 29 | // Check configuration |
| 30 | if (checkAmtPassword(config.newmebxpassword) == false) { console.log('Invalid MEBx password, must have 1 lower, 1 upper, 1 numeric, 1 non-alpha and be 8 or more in length.'); return null; } |
| 31 | |
| 32 | // Start the Intel AMT hello server |
| 33 | var port = 9971; |
| 34 | if (typeof config.port == 'number') { port = config.port; } |
| 35 | const net = require('net'); |
| 36 | obj.server = net.createServer(function (socket) { |
| 37 | socket.ra = socket.remoteAddress; |
| 38 | socket.data = null; |
| 39 | socket.on('error', function (err) { }) |
| 40 | socket.on('close', function () { if (this.data != null) { processHelloData(this.data, this.ra); } delete this.ra; this.removeAllListeners(); }) |
| 41 | socket.on('data', function (data) { |
| 42 | if (this.data == null) { this.data = data; } else { Buffer.concat([this.data, data]); } |
| 43 | var str = this.data.toString(); |
| 44 | if (str.startsWith('GET ') && (str.indexOf('\r\n\r\n') >= 0)) { |
| 45 | this.data = null; |
| 46 | var content = "<!DOCTYPE html><html><head><meta charset=\"UTF-8\"><title>Intel® AMT Hello Server</title></head><body>Intel AMT hello server.<br />Intel® AMT devices should send notification to this port for activation.</body></html>"; |
| 47 | try { socket.end('HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nContent-Length: ' + content.length + '\r\nConnection: close\r\n\r\n' + content); } catch (ex) {} |
| 48 | } else if (this.data.length > 16000) { |
| 49 | try { this.end(); } catch (ex) { }; |
| 50 | } |
| 51 | }) |
| 52 | }); |
| 53 | obj.server.listen(port); |
| 54 | console.log('MeshCentral Intel(R) AMT provisioning server running on port ' + port + '.'); |
| 55 | obj.parent = parent; |
| 56 | obj.rootCertCN = obj.parent.certificateOperations.forge.pki.certificateFromPem(obj.parent.certificates.root.cert).subject.getField('CN').value; |
| 57 | |
| 58 | // Devices activaly being configured |
| 59 | obj.devices = {} // Address -> Device |
| 60 | |
| 61 | // Example hello data for testing |
| 62 | //setTimeout(function () { processHelloData(Buffer.from('01000300000000004b529b93d413181de4871c697a6b7a2b170220c3846bf24b9e93ca64274c0ec67c1ecc5e024ffcacd2d74019350e81fe546ae4022045140b3247eb9cc8c5b4f0d7b53091f73292089e6e5a63e2749dd3aca9198eda0220d7a7a0fb5d7e2731d771e9484ebcdef71d5f0c3e0a2948782bc83ee0ea699ef402201465fa205397b876faa6f0a9958e5590e40fcc7faa4fb7c2c8677521fb5fb65802202ce1cb0bf9d2f9e102993fbe215152c3b2dd0cabde1c68e5319b839154dbb7f502209acfab7e43c8d880d06b262a94deeee4b4659989c3d0caf19baf6405e41ab7df022016af57a9f676b0ab126095aa5ebadef22ab31119d644ac95cd4b93dbf3f26aeb0220960adf0063e96356750c2965dd0a0867da0b9cbd6e77714aeafb2349ab393da3022068ad50909b04363c605ef13581a939ff2c96372e3f12325b0a6861e1d59f660302206dc47172e01cbcb0bf62580d895fe2b8ac9ad4f873801e0c10b9c837d21eb177022073c176434f1bc6d5adf45b0e76e727287c8de57616c1e6e6141a2b2cbc7d8e4c022043df5774b03e7fef5fe40d931a7bedf1bb2e6b42738c4e6d3841103d3aa7f33902202399561127a57125de8cefea610ddf2fa078b5c8067f4e828290bfb860e84b3c022070a73f7f376b60074248904534b11482d5bf0e698ecc498df52577ebf2e93b9a02204348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c701610220cb3ccbb76031e5e0138f8dd39a23f9de47ffc35e43c1144cea27d46a5ab1cb5f022031ad6648f8104138c738f39ea4320133393e3a18cc02296ef97c2ac9ef6731d00220552f7bdcf1a7af9e6ce672017f4f12abf77240c78e761ac203d1d9d20ac89988022067540a47aa5b9f34570a99723cfefa96a96ee3f0d9b8bf4def9440b8065d665d02207224395222cd588c4f2683716922addb41e39b581ac34fa87b39efa896fbb39e0220cbb522d7b7f127ad6a0113865bdf1cd4102e7d0759af635a7cf4720dc963c53b0220179fbc148a3dd00fd24ea13458cc43bfa7f59c8182d783a513f6ebec100c892402202cabeafe37d06ca22aba7391c0033d25982952c453647349763a3ab5ad6ccf69', 'hex'), '192.168.2.148'); }, 500); |
| 63 | //setTimeout(function () { processHelloData(Buffer.from('01000300000000004b529b93d413181de4871c697a6b7a2b180220c3846bf24b9e93ca64274c0ec67c1ecc5e024ffcacd2d74019350e81fe546ae4022045140b3247eb9cc8c5b4f0d7b53091f73292089e6e5a63e2749dd3aca9198eda0220d7a7a0fb5d7e2731d771e9484ebcdef71d5f0c3e0a2948782bc83ee0ea699ef402201465fa205397b876faa6f0a9958e5590e40fcc7faa4fb7c2c8677521fb5fb65802202ce1cb0bf9d2f9e102993fbe215152c3b2dd0cabde1c68e5319b839154dbb7f502209acfab7e43c8d880d06b262a94deeee4b4659989c3d0caf19baf6405e41ab7df022016af57a9f676b0ab126095aa5ebadef22ab31119d644ac95cd4b93dbf3f26aeb0220960adf0063e96356750c2965dd0a0867da0b9cbd6e77714aeafb2349ab393da3022068ad50909b04363c605ef13581a939ff2c96372e3f12325b0a6861e1d59f660302206dc47172e01cbcb0bf62580d895fe2b8ac9ad4f873801e0c10b9c837d21eb177022073c176434f1bc6d5adf45b0e76e727287c8de57616c1e6e6141a2b2cbc7d8e4c022043df5774b03e7fef5fe40d931a7bedf1bb2e6b42738c4e6d3841103d3aa7f33902202399561127a57125de8cefea610ddf2fa078b5c8067f4e828290bfb860e84b3c022070a73f7f376b60074248904534b11482d5bf0e698ecc498df52577ebf2e93b9a02204348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c701610220cb3ccbb76031e5e0138f8dd39a23f9de47ffc35e43c1144cea27d46a5ab1cb5f022031ad6648f8104138c738f39ea4320133393e3a18cc02296ef97c2ac9ef6731d00220552f7bdcf1a7af9e6ce672017f4f12abf77240c78e761ac203d1d9d20ac89988022067540a47aa5b9f34570a99723cfefa96a96ee3f0d9b8bf4def9440b8065d665d0220a267c480b0b29056eb5e8aa7c93add804f5a7df516e969e77bcacafe8d45607902207224395222cd588c4f2683716922addb41e39b581ac34fa87b39efa896fbb39e0220cbb522d7b7f127ad6a0113865bdf1cd4102e7d0759af635a7cf4720dc963c53b0220179fbc148a3dd00fd24ea13458cc43bfa7f59c8182d783a513f6ebec100c892402202cabeafe37d06ca22aba7391c0033d25982952c453647349763a3ab5ad6ccf69', 'hex'), '192.168.2.148'); }, 5000); |
| 64 | //setTimeout(function () { processHelloData(Buffer.from('01000300010000003ec2ffd2d19d2d41860a54b2039b72ff180220c3846bf24b9e93ca64274c0ec67c1ecc5e024ffcacd2d74019350e81fe546ae4022045140b3247eb9cc8c5b4f0d7b53091f73292089e6e5a63e2749dd3aca9198eda0220d7a7a0fb5d7e2731d771e9484ebcdef71d5f0c3e0a2948782bc83ee0ea699ef402201465fa205397b876faa6f0a9958e5590e40fcc7faa4fb7c2c8677521fb5fb65802202ce1cb0bf9d2f9e102993fbe215152c3b2dd0cabde1c68e5319b839154dbb7f502209acfab7e43c8d880d06b262a94deeee4b4659989c3d0caf19baf6405e41ab7df022016af57a9f676b0ab126095aa5ebadef22ab31119d644ac95cd4b93dbf3f26aeb0220960adf0063e96356750c2965dd0a0867da0b9cbd6e77714aeafb2349ab393da3022068ad50909b04363c605ef13581a939ff2c96372e3f12325b0a6861e1d59f660302206dc47172e01cbcb0bf62580d895fe2b8ac9ad4f873801e0c10b9c837d21eb177022073c176434f1bc6d5adf45b0e76e727287c8de57616c1e6e6141a2b2cbc7d8e4c022043df5774b03e7fef5fe40d931a7bedf1bb2e6b42738c4e6d3841103d3aa7f33902202399561127a57125de8cefea610ddf2fa078b5c8067f4e828290bfb860e84b3c022070a73f7f376b60074248904534b11482d5bf0e698ecc498df52577ebf2e93b9a02204348a0e9444c78cb265e058d5e8944b4d84f9662bd26db257f8934a443c701610220cb3ccbb76031e5e0138f8dd39a23f9de47ffc35e43c1144cea27d46a5ab1cb5f022031ad6648f8104138c738f39ea4320133393e3a18cc02296ef97c2ac9ef6731d00220552f7bdcf1a7af9e6ce672017f4f12abf77240c78e761ac203d1d9d20ac89988022067540a47aa5b9f34570a99723cfefa96a96ee3f0d9b8bf4def9440b8065d665d0220a267c480b0b29056eb5e8aa7c93add804f5a7df516e969e77bcacafe8d45607902207224395222cd588c4f2683716922addb41e39b581ac34fa87b39efa896fbb39e0220cbb522d7b7f127ad6a0113865bdf1cd4102e7d0759af635a7cf4720dc963c53b0220179fbc148a3dd00fd24ea13458cc43bfa7f59c8182d783a513f6ebec100c892402202cabeafe37d06ca22aba7391c0033d25982952c453647349763a3ab5ad6ccf69', 'hex'), '192.168.2.134'); }, 5000); |
| 65 | |
| 66 | // Parse Intel AMT hello data |
| 67 | function parseHelloData(data, addr) { |
| 68 | try { |
| 69 | var amtHello = { time: Date.now(), addr: addr }; |
| 70 | |
| 71 | // Decode header |
| 72 | if (data.length < 25) return; // Invalid data |
| 73 | const firstBytes = data.readInt16LE(0); |
| 74 | if (firstBytes > 1) return; // Invalid data |
| 75 | amtHello.adminCredentialsSet = (firstBytes != 0); |
| 76 | amtHello.helloversion = data.readInt16LE(2); |
| 77 | if (amtHello.helloversion != 3) return null; // One touch PID not supported, only version 3 supported. |
| 78 | amtHello.retryCount = data.readInt32LE(4); |
| 79 | amtHello.guidhex = data.slice(8, 24).toString('hex'); |
| 80 | amtHello.guid = guidToStr(amtHello.guidhex); |
| 81 | |
| 82 | // Get the list of hashes |
| 83 | const hashCount = data[24]; |
| 84 | amtHello.hashes = []; |
| 85 | var ptr = 25; |
| 86 | for (var i = 0; i < hashCount; i++) |
| 87 | { |
| 88 | const hashType = data[ptr]; // 1=SHA1 (20 byte hash); 2 = SHA256 (32 byte hash); 3 = SHA384 (48 byte hash) |
| 89 | const hashSize = data[ptr + 1]; |
| 90 | if ((hashType < 1) || (hashType > 3)) return null; // Unexpected hash type |
| 91 | if ((hashType == 1) && (hashSize != 20)) return null; // Unexpected SHA1 hash size |
| 92 | if ((hashType == 2) && (hashSize != 32)) return null; // Unexpected SHA256 hash size |
| 93 | if ((hashType == 3) && (hashSize != 48)) return null; // Unexpected SHA384 hash size |
| 94 | const hash = data.slice(ptr + 2, ptr + 2 + hashSize); |
| 95 | amtHello.hashes.push(hash.toString('hex')); |
| 96 | ptr += (hashSize + 2); |
| 97 | } |
| 98 | if (amtHello.hashes.length != hashCount) return null; // Unexpected number of hashes |
| 99 | return amtHello; // Everything looks good. |
| 100 | } catch (ex) { return null; } |
| 101 | } |
| 102 | |
| 103 | function guidToStr(g) { return g.substring(6, 8) + g.substring(4, 6) + g.substring(2, 4) + g.substring(0, 2) + "-" + g.substring(10, 12) + g.substring(8, 10) + "-" + g.substring(14, 16) + g.substring(12, 14) + "-" + g.substring(16, 20) + "-" + g.substring(20); } |
| 104 | function strToGuid(s) { s = s.replace(/-/g, ''); var ret = s.substring(6, 8) + s.substring(4, 6) + s.substring(2, 4) + s.substring(0, 2) + s.substring(10, 12) + s.substring(8, 10) + s.substring(14, 16) + s.substring(12, 14) + s.substring(16, 20) + s.substring(20); return ret; } |
| 105 | |
| 106 | // Process incoming Intel AMT hello data |
| 107 | function processHelloData(data, addr) { |
| 108 | // Check if we can parse the incoming data |
| 109 | if (addr.startsWith('::ffff:')) { addr = addr.substring(7); } |
| 110 | if (obj.devices[addr] != null) return; // Device on this address already being activated. |
| 111 | const dev = parseHelloData(data, addr); |
| 112 | if (dev == null) { parent.debug('amtsca', addr, 'Got invalid hello from: ' + addr); return; } // Invalid Intel AMT hello |
| 113 | parent.debug('amtsca', 'Got hello from ' + addr); |
| 114 | obj.devices[addr] = dev; |
| 115 | dev.aquired = {}; |
| 116 | |
| 117 | // Set device messages |
| 118 | dev.consoleMsg = function deviceConsoleMsg(msg) { parent.debug('amtsca', deviceConsoleMsg.dev.aquired.host ? deviceConsoleMsg.dev.aquired.host : deviceConsoleMsg.dev.addr, msg); return; } |
| 119 | dev.consoleMsg.dev = dev; |
| 120 | |
| 121 | // Get assumed trusted FQDN and device group |
| 122 | dev.trustedFqdn = config.trustedfqdn; |
| 123 | var mesh = parent.webserver.meshes[config.devicegroup]; |
| 124 | if ((mesh == null) || (mesh.mtype !== 1) || (typeof mesh.amt !== 'object') || (typeof mesh.amt.type !== 'number')) { dev.consoleMsg('Invalid device group for Intel AMT activation.'); return; } |
| 125 | if ((mesh.amt.type != 3) && (mesh.amt.type != 4)) { dev.consoleMsg('Device group does not have ACM activation policy.'); return; } |
| 126 | dev.mesh = mesh; |
| 127 | dev.meshid = mesh._id; |
| 128 | dev.domainid = mesh.domain; |
| 129 | |
| 130 | // Compute the nodeid for this device using the device GUID |
| 131 | const g = dev.guid.split('-').join(''); |
| 132 | const id = Buffer.from(g + g + g, 'hex').toString('base64').replace(/\+/g, '@').replace(/\//g, '$'); |
| 133 | dev.nodeid = 'node/' + mesh.domain + '/' + id; |
| 134 | |
| 135 | // Attempts reverse DNS loopup on the device IP address |
| 136 | const func = function dnsReverseLoopup(err, hostnames) { |
| 137 | var hostname = dnsReverseLoopup.addr; |
| 138 | if ((err == null) && (hostnames != null) && (hostnames.length > 0)) { hostname = hostnames[0]; } |
| 139 | dnsReverseLoopup.dev.aquired.host = hostname; |
| 140 | processHelloDataEx1(dnsReverseLoopup.dev); |
| 141 | } |
| 142 | func.addr = addr; |
| 143 | func.dev = dev; |
| 144 | require('dns').reverse(addr, func); |
| 145 | } |
| 146 | |
| 147 | // Check if this device has any way to be activated in ACM using our server certificates. |
| 148 | function checkAcmActivation(hello) { |
| 149 | var domain = parent.config.domains[hello.domainid]; |
| 150 | if ((domain == null) || (domain.amtacmactivation == null) || (domain.amtacmactivation.certs == null) || (domain.amtacmactivation.certs.length == 0)) return null; |
| 151 | const activationCerts = domain.amtacmactivation.certs; |
| 152 | |
| 153 | // Get the trusted FQDN of the device |
| 154 | var trustedFqdn = hello.trustedFqdn; |
| 155 | |
| 156 | // Find a matching certificate |
| 157 | for (var i in activationCerts) { |
| 158 | var cert = activationCerts[i]; |
| 159 | if ((cert.cn == '*') || (cert.cn == trustedFqdn)) { |
| 160 | for (var j in hello.hashes) { |
| 161 | var hash = hello.hashes[j]; |
| 162 | if (hash == cert.sha256) { return { cert: cert, fqdn: trustedFqdn, hash: cert.sha256 }; } // Found a match |
| 163 | else if (hash == cert.sha1) { return { cert: cert, fqdn: trustedFqdn, hash: cert.sha1 }; } // Found a match |
| 164 | } |
| 165 | } |
| 166 | } |
| 167 | return null; // Did not find a match |
| 168 | } |
| 169 | |
| 170 | function processHelloDataEx1(dev) { |
| 171 | // Get an activation certificate chain |
| 172 | const certinfo = checkAcmActivation(dev); |
| 173 | if (certinfo == null) { dev.consoleMsg('Unable to find a matching ACM activation certificate.'); destroyDevice(dev); return; } |
| 174 | var certchain = parent.certificateOperations.getAcmCertChain(parent.config.domains[dev.domainid], dev.trustedFqdn, certinfo.cert.sha256); |
| 175 | if (certchain == null) { dev.consoleMsg('Unable to create TLS certificate chain.'); destroyDevice(dev); return; } |
| 176 | dev.certchain = certchain; |
| 177 | |
| 178 | // Setup a connection to the Intel AMT device |
| 179 | dev.consoleMsg('Launching TLS connection...'); |
| 180 | var comm = CreateWsmanComm(dev.aquired.host, 16993, 'admin', '', 1, { cert: dev.certchain.certs.reverse().join(''), key: dev.certchain.signkey }); // Perform TLS connection |
| 181 | comm.xtlsFingerprint = 0; // No Intel AMT certificate checking. |
| 182 | var wsstack = WsmanStackCreateService(comm); |
| 183 | dev.amtstack = AmtStackCreateService(wsstack); |
| 184 | dev.amtstack.dev = dev; |
| 185 | dev.amtstack.BatchEnum(null, ['*AMT_GeneralSettings', 'CIM_SoftwareIdentity', '*AMT_SetupAndConfigurationService'], processHelloDataEx2); |
| 186 | } |
| 187 | |
| 188 | function processHelloDataEx2(stack, name, responses, status) { |
| 189 | const dev = stack.dev; |
| 190 | if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. |
| 191 | if (status != 200) { dev.consoleMsg('Failed TLS connection, status=' + status + '.'); destroyDevice(dev); return; } |
| 192 | |
| 193 | // Fetch the Intel AMT version from WSMAN |
| 194 | if ((responses != null) && (responses['CIM_SoftwareIdentity'] != null) && (responses['CIM_SoftwareIdentity'].responses != null)) { |
| 195 | var amtlogicalelements = []; |
| 196 | amtlogicalelements = responses['CIM_SoftwareIdentity'].responses; |
| 197 | if (responses['AMT_SetupAndConfigurationService'] != null && responses['AMT_SetupAndConfigurationService'].response != null) { |
| 198 | amtlogicalelements.push(responses['AMT_SetupAndConfigurationService'].response); |
| 199 | } |
| 200 | if (amtlogicalelements.length > 0) { |
| 201 | var vs = getInstance(amtlogicalelements, 'AMT')['VersionString']; |
| 202 | if (vs != null) { |
| 203 | dev.aquired.version = vs; |
| 204 | const versionSplit = parseInt(dev.aquired.version.split('.')); |
| 205 | dev.aquired.versionmajor = parseInt(versionSplit[0]); |
| 206 | dev.aquired.versionminor = parseInt(versionSplit[1]); |
| 207 | if (versionSplit.length >= 3) { dev.aquired.versionmaintenance = parseInt(versionSplit[2]); } |
| 208 | } |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | // Fetch the Intel AMT version from HTTP stack |
| 213 | if ((dev.amtversionstr == null) && (stack.wsman.comm.amtVersion != null)) { |
| 214 | var s = stack.wsman.comm.amtVersion.split('.'); |
| 215 | if (s.length >= 2) { |
| 216 | dev.aquired.version = s[0] + '.' + s[1]; |
| 217 | dev.aquired.versionmajor = parseInt(s[0]); |
| 218 | dev.aquired.versionminor = parseInt(s[1]); |
| 219 | if (s.length >= 3) { |
| 220 | dev.aquired.version = s[0] + '.' + s[1] + '.' + s[2]; |
| 221 | dev.aquired.versionmaintenance = parseInt(s[2]); |
| 222 | } |
| 223 | } |
| 224 | } |
| 225 | |
| 226 | // If we can't get the Intel AMT version, stop here. |
| 227 | if (dev.aquired.version == null) { dev.consoleMsg('Could not get Intel AMT version.'); destroyDevice(dev); return; } // Could not get Intel AMT version, disconnect(); |
| 228 | |
| 229 | // Get the digest realm |
| 230 | if (responses['AMT_GeneralSettings'] && responses['AMT_GeneralSettings'].response && (typeof responses['AMT_GeneralSettings'].response['DigestRealm'] == 'string')) { |
| 231 | dev.aquired.realm = responses['AMT_GeneralSettings'].response['DigestRealm']; |
| 232 | } else { |
| 233 | dev.consoleMsg('Could not get Intel AMT digest realm.'); destroyDevice(dev); return; |
| 234 | } |
| 235 | |
| 236 | // Looks like we are doing well. |
| 237 | dev.consoleMsg('Succesful TLS connection, Intel AMT v' + dev.aquired.version); |
| 238 | |
| 239 | // Set the new MEBx password |
| 240 | dev.consoleMsg('Setting MEBx password...'); |
| 241 | dev.amtstack.AMT_SetupAndConfigurationService_SetMEBxPassword(config.newmebxpassword, processHelloDataEx3); |
| 242 | } |
| 243 | |
| 244 | // Response from setting MEBx password |
| 245 | function processHelloDataEx3(stack, name, responses, status) { |
| 246 | const dev = stack.dev; |
| 247 | if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. |
| 248 | if (status != 200) { dev.consoleMsg('Failed to set MEBx password, status=' + status + '.'); destroyDevice(dev); return; } |
| 249 | dev.consoleMsg('MEBx password set. Setting admin password...'); |
| 250 | |
| 251 | // See what admin password to use |
| 252 | dev.aquired.user = 'admin'; |
| 253 | dev.aquired.pass = dev.mesh.amt.password; |
| 254 | if (dev.aquired.pass == null) { dev.aquired.pass = getRandomAmtPassword(); } |
| 255 | |
| 256 | // Set the admin password |
| 257 | dev.amtstack.AMT_AuthorizationService_SetAdminAclEntryEx(dev.aquired.user, hex_md5(dev.aquired.user + ':' + dev.aquired.realm + ':' + dev.aquired.pass), processHelloDataEx4); |
| 258 | } |
| 259 | |
| 260 | // Response from setting admin password |
| 261 | function processHelloDataEx4(stack, name, responses, status) { |
| 262 | const dev = stack.dev; |
| 263 | if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. |
| 264 | if (status != 200) { dev.consoleMsg('Failed to set admin password, status=' + status + '.'); destroyDevice(dev); return; } |
| 265 | dev.consoleMsg('Admin password set.'); |
| 266 | |
| 267 | // Perform Intel AMT clock sync |
| 268 | attemptSyncClock(dev, function (dev) { |
| 269 | // Setup TLS and commit. |
| 270 | attemptTlsSync(dev, function (dev) { |
| 271 | dev.consoleMsg('Intel AMT ACM activation completed.'); |
| 272 | parent.SetConnectivityState(dev.meshid, dev.nodeid, Date.now(), 4, 7, null, { name: dev.name }); // Report power state as "present" (7). |
| 273 | if (obj.parent.amtManager != null) { obj.parent.amtManager.startAmtManagement(dev.nodeid, 3, dev.aquired.host); } // Request that Intel AMT manager take a look at this device. |
| 274 | destroyDevice(dev); // We are done, clean up. |
| 275 | }); |
| 276 | }); |
| 277 | } |
| 278 | |
| 279 | |
| 280 | // |
| 281 | // Intel AMT TLS setup |
| 282 | // |
| 283 | |
| 284 | // Check if Intel AMT TLS state is correct |
| 285 | function attemptTlsSync(dev, func) { |
| 286 | dev.taskCount = 1; |
| 287 | dev.taskCompleted = func; |
| 288 | // TODO: We only deal with certificates starting with Intel AMT 6 and beyond |
| 289 | dev.amtstack.BatchEnum(null, ['AMT_PublicKeyCertificate', 'AMT_PublicPrivateKeyPair', 'AMT_TLSSettingData', 'AMT_TLSCredentialContext'], attemptTlsSyncEx); |
| 290 | } |
| 291 | |
| 292 | // Intel AMT is not always in a good spot to generate a key pair. This will retry at 10 second interval. |
| 293 | function generateKeyPairWithRetry(dev, func) { |
| 294 | if (isAmtDeviceValid(dev) == false) return; |
| 295 | if (dev.keyPairAttempts == null) { dev.keyPairAttempts = 1; } else { dev.keyPairAttempts++; } |
| 296 | dev.amtstack.AMT_PublicKeyManagementService_GenerateKeyPair(0, 2048, function (stack, name, responses, status) { |
| 297 | if (isAmtDeviceValid(dev) == false) { delete dev.keyPairAttempts; return; } |
| 298 | if ((status == 200) || (dev.keyPairAttempts > 19)) { |
| 299 | delete dev.keyPairAttempts; |
| 300 | func(stack, name, responses, status); |
| 301 | } else { |
| 302 | if ((responses.Body != null) && (responses.Body.ReturnValue != null) && (responses.Body.ReturnValueStr != null)) { |
| 303 | dev.consoleMsg("Failed to generate a key pair (" + status + ", " + responses.Body.ReturnValue + ", \"" + responses.Body.ReturnValueStr + "\"), attempt " + dev.keyPairAttempts + ", trying again in 10 seconds..."); |
| 304 | } else { |
| 305 | dev.consoleMsg("Failed to generate a key pair (" + status + "), attempt " + dev.keyPairAttempts + ", trying again in 10 seconds..."); |
| 306 | } |
| 307 | |
| 308 | // Wait 10 seconds before attempting again |
| 309 | var f = function doManage() { generateKeyPairWithRetry(doManage.dev, doManage.func); } |
| 310 | f.dev = dev; |
| 311 | f.func = func; |
| 312 | setTimeout(f, 10000); |
| 313 | } |
| 314 | }); |
| 315 | } |
| 316 | |
| 317 | function attemptTlsSyncEx(stack, name, responses, status) { |
| 318 | const dev = stack.dev; |
| 319 | if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. |
| 320 | if (status != 200) { dev.consoleMsg("Failed to get security information (" + status + ")."); destroyDevice(dev); return; } |
| 321 | |
| 322 | // Setup the certificates |
| 323 | dev.policy = {}; |
| 324 | dev.policy.certPrivateKeys = responses['AMT_PublicPrivateKeyPair'].responses; |
| 325 | dev.policy.tlsSettings = responses['AMT_TLSSettingData'].responses; |
| 326 | dev.policy.tlsCredentialContext = responses['AMT_TLSCredentialContext'].responses; |
| 327 | var xxCertificates = responses['AMT_PublicKeyCertificate'].responses; |
| 328 | for (var i in xxCertificates) { |
| 329 | xxCertificates[i].TrustedRootCertficate = (xxCertificates[i]['TrustedRootCertficate'] == true); |
| 330 | xxCertificates[i].X509CertificateBin = Buffer.from(xxCertificates[i]['X509Certificate'], 'base64').toString('binary'); |
| 331 | xxCertificates[i].XIssuer = parseCertName(xxCertificates[i]['Issuer']); |
| 332 | xxCertificates[i].XSubject = parseCertName(xxCertificates[i]['Subject']); |
| 333 | } |
| 334 | amtcert_linkCertPrivateKey(xxCertificates, dev.policy.certPrivateKeys); |
| 335 | dev.policy.certificates = xxCertificates; |
| 336 | dev.consoleMsg("Intel AMT has " + xxCertificates.length + " certificate(s) and " + dev.policy.certPrivateKeys.length + " private keys(s)."); |
| 337 | |
| 338 | // Find the current TLS certificate & MeshCentral root certificate |
| 339 | var xxTlsCurrentCert = null; |
| 340 | if (dev.policy.tlsCredentialContext.length > 0) { |
| 341 | var certInstanceId = dev.policy.tlsCredentialContext[0]['ElementInContext']['ReferenceParameters']['SelectorSet']['Selector']['Value']; |
| 342 | for (var i in dev.policy.certificates) { if (dev.policy.certificates[i]['InstanceID'] == certInstanceId) { xxTlsCurrentCert = i; } } |
| 343 | } |
| 344 | |
| 345 | // This is a managed device and TLS is not enabled, turn it on. |
| 346 | if (xxTlsCurrentCert === null) { |
| 347 | // Start by generating a key pair |
| 348 | dev.consoleMsg("No TLS certificate. Generating key pair..."); |
| 349 | generateKeyPairWithRetry(dev, function (stack, name, responses, status) { |
| 350 | const dev = stack.dev; |
| 351 | if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. |
| 352 | if (status != 200) { dev.consoleMsg("Failed to generate a key pair (" + status + ")."); removeAmtDevice(dev, 20); return; } |
| 353 | |
| 354 | // Check that we get a key pair reference |
| 355 | var x = null; |
| 356 | try { x = responses.Body['KeyPair']['ReferenceParameters']['SelectorSet']['Selector']['Value']; } catch (ex) { } |
| 357 | if (x == null) { dev.consoleMsg("Unable to get key pair reference."); removeAmtDevice(dev, 21); return; } |
| 358 | |
| 359 | // Get the new key pair |
| 360 | dev.consoleMsg("Fetching key pair..."); |
| 361 | dev.amtstack.Enum('AMT_PublicPrivateKeyPair', function (stack, name, responses, status, tag) { |
| 362 | const dev = stack.dev; |
| 363 | if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. |
| 364 | if (status != 200) { dev.consoleMsg("Failed to get a key pair list (" + status + ")."); removeAmtDevice(dev, 22); return; } |
| 365 | |
| 366 | // Get the new DER key |
| 367 | var DERKey = null; |
| 368 | for (var i in responses) { if (responses[i]['InstanceID'] == tag) { DERKey = responses[i]['DERKey']; } } |
| 369 | |
| 370 | // Get certificate values |
| 371 | const commonName = 'IntelAMT-' + Buffer.from(parent.crypto.randomBytes(6), 'binary').toString('hex'); |
| 372 | const domain = parent.config.domains[dev.domainid]; |
| 373 | var serverName = 'MeshCentral'; |
| 374 | if ((domain != null) && (domain.title != null)) { serverName = domain.title; } |
| 375 | const certattributes = { 'CN': commonName, 'O': serverName, 'ST': 'MC', 'C': 'MC' }; |
| 376 | |
| 377 | // See what root certificate to use to sign the TLS cert |
| 378 | var xxCaPrivateKey = parent.webserver.certificates.root.key; // Use our own root by default |
| 379 | var issuerattributes = { 'CN': obj.rootCertCN }; |
| 380 | if (domain.amtmanager.tlsrootcert2 != null) { |
| 381 | xxCaPrivateKey = domain.amtmanager.tlsrootcert2.key; |
| 382 | issuerattributes = domain.amtmanager.tlsrootcert2.attributes; |
| 383 | // TODO: We should change the start and end dates of our issued certificate to at least match the root. |
| 384 | // TODO: We could do one better and auto-renew TLS certificates as needed. |
| 385 | } |
| 386 | |
| 387 | // Set the extended key usages |
| 388 | var extKeyUsage = { name: 'extKeyUsage', serverAuth: true, clientAuth: true } |
| 389 | |
| 390 | // Sign the key pair using the CA certifiate |
| 391 | dev.consoleMsg("Signing certificate..."); |
| 392 | const cert = parent.amtManager.amtcert_createCertificate(certattributes, xxCaPrivateKey, DERKey, issuerattributes, extKeyUsage); |
| 393 | if (cert == null) { dev.consoleMsg("Failed to sign the TLS certificate."); removeAmtDevice(dev, 23); return; } |
| 394 | |
| 395 | // Place the resulting signed certificate back into AMT |
| 396 | var pem = obj.parent.certificateOperations.forge.pki.certificateToPem(cert).replace(/(\r\n|\n|\r)/gm, ''); |
| 397 | |
| 398 | // Set the certificate finderprint (SHA1) |
| 399 | var md = obj.parent.certificateOperations.forge.md.sha1.create(); |
| 400 | md.update(obj.parent.certificateOperations.forge.asn1.toDer(obj.parent.certificateOperations.forge.pki.certificateToAsn1(cert)).getBytes()); |
| 401 | dev.aquired.hash = md.digest().toHex(); |
| 402 | |
| 403 | dev.consoleMsg("Adding certificate, hash: " + dev.aquired.hash); |
| 404 | dev.amtstack.AMT_PublicKeyManagementService_AddCertificate(pem.substring(27, pem.length - 25), function (stack, name, responses, status) { |
| 405 | const dev = stack.dev; |
| 406 | if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. |
| 407 | if (status != 200) { dev.consoleMsg("Failed to add TLS certificate (" + status + ")."); removeAmtDevice(dev, 24); return; } |
| 408 | var certInstanceId = null; |
| 409 | try { certInstanceId = responses.Body['CreatedCertificate']['ReferenceParameters']['SelectorSet']['Selector']['Value']; } catch (ex) { } |
| 410 | if (certInstanceId == null) { dev.consoleMsg("Failed to get TLS certificate identifier."); removeAmtDevice(dev, 25); return; } |
| 411 | |
| 412 | // Set the TLS certificate |
| 413 | dev.setTlsSecurityPendingCalls = 2; |
| 414 | if (dev.policy.tlsCredentialContext.length > 0) { |
| 415 | // Modify the current context |
| 416 | var newTLSCredentialContext = Clone(dev.policy.tlsCredentialContext[0]); |
| 417 | newTLSCredentialContext['ElementInContext']['ReferenceParameters']['SelectorSet']['Selector']['Value'] = certInstanceId; |
| 418 | dev.amtstack.Put('AMT_TLSCredentialContext', newTLSCredentialContext, amtSwitchToTls, 0, 1); |
| 419 | } else { |
| 420 | // Add a new security context |
| 421 | dev.amtstack.Create('AMT_TLSCredentialContext', { |
| 422 | 'ElementInContext': '<a:Address>/wsman</a:Address><a:ReferenceParameters><w:ResourceURI>' + dev.amtstack.CompleteName('AMT_PublicKeyCertificate') + '</w:ResourceURI><w:SelectorSet><w:Selector Name="InstanceID">' + certInstanceId + '</w:Selector></w:SelectorSet></a:ReferenceParameters>', |
| 423 | 'ElementProvidingContext': '<a:Address>/wsman</a:Address><a:ReferenceParameters><w:ResourceURI>' + dev.amtstack.CompleteName('AMT_TLSProtocolEndpointCollection') + '</w:ResourceURI><w:SelectorSet><w:Selector Name="ElementName">TLSProtocolEndpointInstances Collection</w:Selector></w:SelectorSet></a:ReferenceParameters>' |
| 424 | }, amtSwitchToTls); |
| 425 | } |
| 426 | |
| 427 | // Figure out what index is local & remote |
| 428 | var localNdx = ((dev.policy.tlsSettings[0]['InstanceID'] == 'Intel(r) AMT LMS TLS Settings')) ? 0 : 1, remoteNdx = (1 - localNdx); |
| 429 | |
| 430 | // Remote TLS settings |
| 431 | var xxTlsSettings2 = Clone(dev.policy.tlsSettings); |
| 432 | xxTlsSettings2[remoteNdx]['Enabled'] = true; |
| 433 | xxTlsSettings2[remoteNdx]['MutualAuthentication'] = false; |
| 434 | xxTlsSettings2[remoteNdx]['AcceptNonSecureConnections'] = true; |
| 435 | delete xxTlsSettings2[remoteNdx]['TrustedCN']; |
| 436 | |
| 437 | // Update TLS settings. Enable on remote port only. If you enable on local port, the commit() will succeed but be ignored. |
| 438 | dev.consoleMsg("Enabling TLS on remote port..."); |
| 439 | if (remoteNdx == 0) { dev.amtstack.Put('AMT_TLSSettingData', xxTlsSettings2[0], amtSwitchToTls, 0, 1, xxTlsSettings2[0]); } |
| 440 | else { dev.amtstack.Put('AMT_TLSSettingData', xxTlsSettings2[1], amtSwitchToTls, 0, 1, xxTlsSettings2[1]); } |
| 441 | }); |
| 442 | |
| 443 | }, responses.Body['KeyPair']['ReferenceParameters']['SelectorSet']['Selector']['Value']); |
| 444 | }); |
| 445 | } else { |
| 446 | // TLS already enabled, update device in the database |
| 447 | dev.consoleMsg("Intel AMT has TLS already enabled."); |
| 448 | |
| 449 | // Perform commit |
| 450 | dev.taskCount = 1; |
| 451 | amtPerformCommit(dev); |
| 452 | } |
| 453 | } |
| 454 | |
| 455 | function amtSwitchToTls(stack, name, responses, status) { |
| 456 | const dev = stack.dev; |
| 457 | if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. |
| 458 | if (status != 200) { dev.consoleMsg("Failed setup TLS (" + status + ")."); removeAmtDevice(dev, 26); return; } |
| 459 | |
| 460 | // Check if all the calls are done & perform a commit |
| 461 | if ((--dev.setTlsSecurityPendingCalls) == 0) { |
| 462 | dev.consoleMsg("Calling Commit..."); |
| 463 | amtPerformCommit(dev); |
| 464 | } |
| 465 | } |
| 466 | |
| 467 | function amtPerformCommit(dev) { |
| 468 | dev.consoleMsg("Performing commit..."); |
| 469 | dev.amtstack.AMT_SetupAndConfigurationService_CommitChanges(null, function (stack, name, responses, status) { |
| 470 | const dev = stack.dev; |
| 471 | if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. |
| 472 | if (status != 200) { dev.consoleMsg("Failed perform commit (" + status + ")."); removeAmtDevice(dev, 27); return; } |
| 473 | dev.consoleMsg("Commited, holding 5 seconds..."); |
| 474 | |
| 475 | // Update the device state |
| 476 | dev.aquired.tls = 1; |
| 477 | dev.aquired.state = 2; // Activated |
| 478 | dev.aquired.controlMode = 2; // Activated in ACM |
| 479 | |
| 480 | // Save activation data to amtactivation.log |
| 481 | var domain = parent.config.domains[dev.domainid]; |
| 482 | obj.logAmtActivation(domain, { |
| 483 | time: new Date(), |
| 484 | action: 'acmactivate-bare-metal', |
| 485 | domain: dev.domainid, |
| 486 | amtUuid: dev.guid, |
| 487 | newmebx: config.newmebxpassword, |
| 488 | mesh: dev.meshid, |
| 489 | amtRealm: dev.aquired.realm, |
| 490 | amtver: dev.aquired.version, |
| 491 | host: dev.aquired.host, |
| 492 | ip: dev.addr, |
| 493 | user: dev.aquired.user, |
| 494 | password: dev.aquired.pass, |
| 495 | tls: dev.aquired.tls, |
| 496 | tlshash: dev.aquired.hash |
| 497 | }); |
| 498 | |
| 499 | // Update device in the database |
| 500 | if (UpdateDevice(dev) == false) return; |
| 501 | |
| 502 | // Switch our communications to TLS (Restart our management of this node) |
| 503 | dev.switchToTls = 1; |
| 504 | delete dev.tlsfail; |
| 505 | |
| 506 | // Wait 5 seconds before attempting to manage this device some more |
| 507 | var f = function doManage() { if (isAmtDeviceValid(dev)) { devTaskCompleted(doManage.dev); } } |
| 508 | f.dev = dev; |
| 509 | setTimeout(f, 5000); |
| 510 | }); |
| 511 | } |
| 512 | |
| 513 | |
| 514 | // |
| 515 | // Intel AMT Clock Syncronization |
| 516 | // |
| 517 | |
| 518 | // Attempt to sync the Intel AMT clock if needed, call func back when done. |
| 519 | // Care should be take not to have many pending WSMAN called when performing clock sync. |
| 520 | function attemptSyncClock(dev, func) { |
| 521 | if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. |
| 522 | dev.taskCount = 1; |
| 523 | dev.taskCompleted = func; |
| 524 | dev.amtstack.AMT_TimeSynchronizationService_GetLowAccuracyTimeSynch(attemptSyncClockEx); |
| 525 | } |
| 526 | |
| 527 | // Intel AMT clock query response |
| 528 | function attemptSyncClockEx(stack, name, response, status) { |
| 529 | const dev = stack.dev; |
| 530 | if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. |
| 531 | if (status != 200) { dev.consoleMsg("Failed to get clock (" + status + ")."); removeAmtDevice(dev, 17); return; } |
| 532 | |
| 533 | // Compute how much drift between Intel AMT and our clock. |
| 534 | var t = new Date(), now = new Date(); |
| 535 | t.setTime(response.Body['Ta0'] * 1000); |
| 536 | if (Math.abs(t - now) > 10000) { // If the Intel AMT clock is more than 10 seconds off, set it. |
| 537 | dev.consoleMsg("Performing clock sync."); |
| 538 | var Tm1 = Math.round(now.getTime() / 1000); |
| 539 | dev.amtstack.AMT_TimeSynchronizationService_SetHighAccuracyTimeSynch(response.Body['Ta0'], Tm1, Tm1, attemptSyncClockSet); |
| 540 | } else { |
| 541 | // Clock is fine, we are done. |
| 542 | devTaskCompleted(dev) |
| 543 | } |
| 544 | } |
| 545 | |
| 546 | // Intel AMT clock set response |
| 547 | function attemptSyncClockSet(stack, name, responses, status) { |
| 548 | const dev = stack.dev; |
| 549 | if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. |
| 550 | if (status != 200) { dev.consoleMsg("Failed to sync clock (" + status + ")."); removeAmtDevice(dev, 18); } |
| 551 | devTaskCompleted(dev) |
| 552 | } |
| 553 | |
| 554 | |
| 555 | // |
| 556 | // Device Management Methods |
| 557 | // |
| 558 | |
| 559 | // Do aggressive cleanup on the device |
| 560 | function destroyDevice(dev) { |
| 561 | delete obj.devices[dev.addr]; // Remove the device from the list of currently active devices. |
| 562 | if (dev.amtstack != null) { delete dev.amtstack.dev; delete dev.amtstack; } // Clean up the AMT stack. |
| 563 | for (var i in dev) { delete dev[i]; } // Aggressive cleanup or everything else. |
| 564 | } |
| 565 | |
| 566 | // Update the device in the database and event any changes |
| 567 | function UpdateDevice(dev) { |
| 568 | // Check that the mesh exists |
| 569 | const mesh = parent.webserver.meshes[dev.meshid]; |
| 570 | if (mesh == null) { destroyDevice(dev); return false; } |
| 571 | |
| 572 | // Get the node and change it if needed |
| 573 | parent.db.Get(dev.nodeid, function (err, nodes) { |
| 574 | if ((nodes == null) || (nodes.length == 0)) { |
| 575 | // Add a new device |
| 576 | var devicename = dev.guid; |
| 577 | if (dev.addr != dev.aquired.host) { devicename = dev.aquired.host.split('.')[0]; } |
| 578 | var device = { type: 'node', _id: dev.nodeid, meshid: dev.meshid, name: devicename, host: dev.aquired.host, domain: dev.domainid, intelamt: { ver: dev.aquired.version, user: dev.aquired.user, pass: dev.aquired.pass, tls: dev.aquired.tls, state: 2, realm: dev.aquired.realm } }; |
| 579 | if (dev.aquired.hash != null) { device.intelamt.hash = dev.aquired.hash; } |
| 580 | |
| 581 | // Set Intel AMT flags |
| 582 | // dev.aquired.controlMode // 1 = CCM, 2 = ACM |
| 583 | // (node.intelamt.flags & 2) == CCM, (node.intelamt.flags & 4) == ACM |
| 584 | if (dev.aquired.controlMode == 1) { device.intelamt.flags = 2; } // CCM |
| 585 | if (dev.aquired.controlMode == 2) { device.intelamt.flags = 4; } // ACM |
| 586 | |
| 587 | parent.db.Set(device); |
| 588 | |
| 589 | // Event the new node |
| 590 | parent.DispatchEvent(parent.webserver.CreateMeshDispatchTargets(dev.meshid, [dev.nodeid]), obj, { etype: 'node', action: 'addnode', node: parent.webserver.CloneSafeNode(device), msgid: 84, msgArgs: [devicename, mesh.name], msg: 'Added device ' + devicename + ' to device group ' + mesh.name, domain: dev.domainid }); |
| 591 | } else { |
| 592 | // Update an existing device |
| 593 | const device = nodes[0]; |
| 594 | var changes = [], change = 0, log = 0; |
| 595 | var domain = parent.config.domains[device.domain]; |
| 596 | if (domain == null) return false; |
| 597 | |
| 598 | // Check if anything changes |
| 599 | if (device.intelamt == null) { device.intelamt = {}; } |
| 600 | if ((typeof dev.aquired.version == 'string') && (dev.aquired.version != device.intelamt.ver)) { change = 1; log = 1; device.intelamt.ver = dev.aquired.version; changes.push('AMT version'); } |
| 601 | if ((typeof dev.aquired.user == 'string') && (dev.aquired.user != device.intelamt.user)) { change = 1; log = 1; device.intelamt.user = dev.aquired.user; changes.push('AMT user'); } |
| 602 | if ((typeof dev.aquired.pass == 'string') && (dev.aquired.pass != device.intelamt.pass)) { change = 1; log = 1; device.intelamt.pass = dev.aquired.pass; changes.push('AMT pass'); } |
| 603 | if ((typeof dev.aquired.mpspass == 'string') && (dev.aquired.mpspass != device.intelamt.mpspass)) { change = 1; log = 1; device.intelamt.mpspass = dev.aquired.mpspass; changes.push('AMT MPS pass'); } |
| 604 | if ((typeof dev.aquired.host == 'string') && (dev.aquired.host != device.intelamt.host)) { change = 1; log = 1; device.intelamt.host = dev.aquired.host; changes.push('AMT host'); } |
| 605 | if ((typeof dev.aquired.realm == 'string') && (dev.aquired.realm != device.intelamt.realm)) { change = 1; log = 1; device.intelamt.realm = dev.aquired.realm; changes.push('AMT realm'); } |
| 606 | if ((typeof dev.aquired.hash == 'string') && (dev.aquired.hash != device.intelamt.hash)) { change = 1; log = 1; device.intelamt.hash = dev.aquired.hash; changes.push('AMT hash'); } |
| 607 | if ((typeof dev.aquired.tls == 'number') && (dev.aquired.tls != device.intelamt.tls)) { change = 1; log = 1; device.intelamt.tls = dev.aquired.tls; /*changes.push('AMT TLS');*/ } |
| 608 | if ((typeof dev.aquired.state == 'number') && (dev.aquired.state != device.intelamt.state)) { change = 1; log = 1; device.intelamt.state = dev.aquired.state; changes.push('AMT state'); } |
| 609 | |
| 610 | // Intel AMT Warning Flags: 1 = Unknown credentials, 2 = Realm Mismatch, 4 = TLS Cert Mismatch, 8 = Trying credentials |
| 611 | if ((typeof dev.aquired.warn == 'number')) { if ((dev.aquired.warn == 0) && (device.intelamt.warn != null)) { delete device.intelamt.warn; change = 1; } else if (dev.aquired.warn != device.intelamt.warn) { device.intelamt.warn = dev.aquired.warn; change = 1; } } |
| 612 | |
| 613 | // Update Intel AMT flags if needed |
| 614 | // dev.aquired.controlMode // 1 = CCM, 2 = ACM |
| 615 | // (node.intelamt.flags & 2) == CCM, (node.intelamt.flags & 4) == ACM |
| 616 | var flags = 0; |
| 617 | if (typeof device.intelamt.flags == 'number') { flags = device.intelamt.flags; } |
| 618 | if (dev.aquired.controlMode == 1) { if ((flags & 4) != 0) { flags -= 4; } if ((flags & 2) == 0) { flags += 2; } } // CCM |
| 619 | if (dev.aquired.controlMode == 2) { if ((flags & 4) == 0) { flags += 4; } if ((flags & 2) != 0) { flags -= 2; } } // ACM |
| 620 | if (device.intelamt.flags != flags) { change = 1; log = 1; device.intelamt.flags = flags; changes.push('AMT flags'); } |
| 621 | |
| 622 | // If there are changes, event the new device |
| 623 | if (change == 1) { |
| 624 | // Save to the database |
| 625 | parent.db.Set(device); |
| 626 | |
| 627 | // Event the node change |
| 628 | var event = { etype: 'node', action: 'changenode', nodeid: device._id, domain: domain.id, node: parent.webserver.CloneSafeNode(device) }; |
| 629 | if (changes.length > 0) { event.msg = 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', '); } |
| 630 | if ((log == 0) || ((obj.agentInfo) && (obj.agentInfo.capabilities) && (obj.agentInfo.capabilities & 0x20)) || (changes.length == 0)) { event.nolog = 1; } // If this is a temporary device, don't log changes |
| 631 | if (parent.db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come. |
| 632 | parent.DispatchEvent(parent.webserver.CreateMeshDispatchTargets(device.meshid, [device._id]), obj, event); |
| 633 | } |
| 634 | } |
| 635 | }); |
| 636 | |
| 637 | return true; |
| 638 | } |
| 639 | |
| 640 | |
| 641 | // |
| 642 | // General Methods |
| 643 | // |
| 644 | |
| 645 | // Log the Intel AMT activation operation in the domain log |
| 646 | obj.logAmtActivation = function (domain, x) { |
| 647 | if (x == null) return true; |
| 648 | |
| 649 | // Add the password to the Intel AMT list of UUID to passwords |
| 650 | if ((typeof x.amtUuid == 'string') && (typeof x.password == 'string')) { |
| 651 | if (parent.amtPasswords == null) { parent.amtPasswords = {}; } |
| 652 | if (parent.amtPasswords[x.amtUuid] == null) { |
| 653 | parent.amtPasswords[x.amtUuid] = [x.password]; // Add password to array |
| 654 | parent.amtPasswords = parent.common.sortObj(parent.amtPasswords); |
| 655 | } else { |
| 656 | if (parent.amtPasswords[x.amtUuid].indexOf(x.password) == -1) { |
| 657 | parent.amtPasswords[x.amtUuid].unshift(x.password); // Add password at the start of the array |
| 658 | while (parent.amtPasswords[x.amtUuid].length > 3) { parent.amtPasswords[x.amtUuid].pop(); } // Only keep the 3 last passwords for any given device |
| 659 | } |
| 660 | } |
| 661 | } |
| 662 | |
| 663 | // Append to the log file |
| 664 | var logpath = null; |
| 665 | if ((domain.amtacmactivation == null) || (domain.amtacmactivation.log == null) || (typeof domain.amtacmactivation.log != 'string')) { |
| 666 | if (domain.id == '') { logpath = parent.path.join(obj.parent.datapath, 'amtactivation.log'); } else { logpath = parent.path.join(obj.parent.datapath, 'amtactivation-' + domain.id + '.log'); } |
| 667 | } else { |
| 668 | logpath = parent.common.joinPath(obj.parent.datapath, domain.amtacmactivation.log); |
| 669 | } |
| 670 | try { parent.fs.appendFileSync(logpath, JSON.stringify(x) + '\r\n'); } catch (ex) { console.log(ex); return false; } |
| 671 | return true; |
| 672 | } |
| 673 | |
| 674 | // Called this when a task is completed, when all tasks are completed the call back function will be called. |
| 675 | function devTaskCompleted(dev) { |
| 676 | dev.taskCount--; |
| 677 | if (dev.taskCount == 0) { var f = dev.taskCompleted; delete dev.taskCount; delete dev.taskCompleted; if (f != null) { f(dev); } } |
| 678 | } |
| 679 | |
| 680 | // Check which key pair matches the public key in the certificate |
| 681 | function amtcert_linkCertPrivateKey(certs, keys) { |
| 682 | for (var i in certs) { |
| 683 | var cert = certs[i]; |
| 684 | try { |
| 685 | if (keys.length == 0) return; |
| 686 | var b = obj.parent.certificateOperations.forge.asn1.fromDer(cert.X509CertificateBin); |
| 687 | var a = obj.parent.certificateOperations.forge.pki.certificateFromAsn1(b).publicKey; |
| 688 | var publicKeyPEM = obj.parent.certificateOperations.forge.pki.publicKeyToPem(a).substring(28 + 32).replace(/(\r\n|\n|\r)/gm, ""); |
| 689 | for (var j = 0; j < keys.length; j++) { |
| 690 | if (publicKeyPEM === (keys[j]['DERKey'] + '-----END PUBLIC KEY-----')) { |
| 691 | keys[j].XCert = cert; // Link the key pair to the certificate |
| 692 | cert.XPrivateKey = keys[j]; // Link the certificate to the key pair |
| 693 | } |
| 694 | } |
| 695 | } catch (e) { console.log(e); } |
| 696 | } |
| 697 | } |
| 698 | |
| 699 | function isAmtDeviceValid(dev) { return (obj.devices[dev.addr] != null); } |
| 700 | function getInstance(x, y) { for (var i in x) { if (x[i]['InstanceID'] == y) return x[i]; } return null; } |
| 701 | function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); } |
| 702 | function getRandomAmtPassword() { var p; do { p = Buffer.from(obj.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; } |
| 703 | function hex_md5(str) { return parent.crypto.createHash('md5').update(str).digest('hex'); } |
| 704 | function Clone(v) { return JSON.parse(JSON.stringify(v)); } |
| 705 | |
| 706 | function parseCertName(x) { |
| 707 | var j, r = {}, xx = x.split(','); |
| 708 | for (var i in xx) { j = xx[i].indexOf('='); r[xx[i].substring(0, j)] = xx[i].substring(j + 1); } |
| 709 | return r; |
| 710 | } |
| 711 | |
| 712 | return obj; |
| 713 | }; |