diff --git a/agents/MeshCmd-signed.exe b/agents/MeshCmd-signed.exe index 2d7f3538..3e070384 100644 Binary files a/agents/MeshCmd-signed.exe and b/agents/MeshCmd-signed.exe differ diff --git a/agents/MeshCmd64-signed.exe b/agents/MeshCmd64-signed.exe index 4bf903c7..5616f0a9 100644 Binary files a/agents/MeshCmd64-signed.exe and b/agents/MeshCmd64-signed.exe differ diff --git a/agents/meshcmd.js b/agents/meshcmd.js index 45485f59..9e607fa7 100644 --- a/agents/meshcmd.js +++ b/agents/meshcmd.js @@ -498,9 +498,10 @@ function run(argv) { activeToCCM(); } else if (settings.action == 'amtacm') { // Start activation to ACM - if ((settings.wss == null) || (typeof settings.wss != 'string') || (settings.wss == '')) { console.log('No or invalid \"server name\" specified, use --wss [servername:port].'); exit(1); return; } - if ((settings.profile == null) || (typeof settings.profile != 'string') || (settings.profile == '')) { console.log('No or invalid \"profile name\" specified, use --profile [name].'); exit(1); return; } - settings.protocol = 'http:'; + if ((settings.wss == null) || (typeof settings.wss != 'string') || (settings.wss == '')) { console.log('No server URL specified, use --wss [url].'); exit(1); return; } + //if ((settings.profile == null) || (typeof settings.profile != 'string') || (settings.profile == '')) { console.log('No or invalid \"profile name\" specified, use --profile [name].'); exit(1); return; } + if ((typeof settings.profile != 'string') || (settings.profile == '')) { settings.profile = null; } + //settings.protocol = 'http:'; settings.localport = 16992; debug(1, "Settings: " + JSON.stringify(settings)); activeToACM(); @@ -848,24 +849,63 @@ function deactivateACMEx() { }); } +// +// Get Intel AMT activation hashes +// + +var trustedHashes = null; +function getTrustedHashes(amtMei, func, tag) { + console.log('getTrustedHashes'); + if (trustedHashes != null) { func(tag); } + trustedHashes = []; + amtMei.getHashHandles(function (handles) { + var exitOnCount = handles.length; + for (var i = 0; i < handles.length; ++i) { + this.getCertHashEntry(handles[i], function (result) { + if (result.isActive == 1) { trustedHashes.push(result.certificateHash.toLowerCase()); } + if (--exitOnCount == 0) { func(tag); } + }); + } + }); +} // // Activate Intel AMT to ACM // function activeToACM() { - // See if MicroLMS needs to be started and setup the $$OsAdmin wsman stack - console.log('Starting AMT Provisioning to Admin Control Mode.'); + console.log('Starting Intel AMT provisioning to Admin Control Mode (ACM) attempt...'); settings.noconsole = true; + // Display Intel AMT version and activation state mestate = {}; var amtMeiModule, amtMei; try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { console.log(ex); exit(1); return; } amtMei.on('error', function (e) { console.log('ERROR: ' + e); exit(1); return; }); - amtMei.getProvisioningState(function (result) { - if (result) { - mestate.ProvisioningState = result; - startLms(getFwNonce); // TODO: Fix this so that it works even if LMS already running. + amtMei.getProvisioningState(function (result) { if (result) { mestate.ProvisioningState = result; } }); + amtMei.getLanInterfaceSettings(0, function (result) { if (result) { mestate.net0 = result; } }); + amtMei.getUuid(function (result) { if ((result != null) && (result.uuid != null)) { mestate.uuid = result.uuid; } }); + amtMei.getDnsSuffix(function (result) { + if (mestate.ProvisioningState.state !== 0) { console.log("Intel AMT is not in pre-provisioning state: " + mestate.ProvisioningState.stateStr); exit(100); return; } + if (mestate.uuid == null) { console.log("Unable to get Intel AMT UUID."); exit(100); return; } + var fqdn = null; + if ((mestate.net0 == null) && (meinfo.net0.enabled != 0)) { console.log("No Intel AMT wired interface, can't perform ACM activation."); exit(100); return; } + if (result) { fqdn = result; } // If Intel AMT has a trusted DNS suffix set, use that one. + else { + // Look for the DNS suffix for the Intel AMT Ethernet interface + var interfaces = require('os').networkInterfaces(); + for (var i in interfaces) { + for (var j in interfaces[i]) { + if ((interfaces[i][j].mac == mestate.net0.mac) && (interfaces[i][j].fqdn != null) && (interfaces[i][j].fqdn != '')) { fqdn = interfaces[i][j].fqdn; } + } + } + } + if (fqdn != null) { + settings.fqdn = fqdn; + settings.uuid = mestate.uuid; + getTrustedHashes(amtMei, function () { startLms(getFwNonce, amtMei); }); + } else { + console.log("Trusted DNS suffix not set, can't perform ACM activation."); exit(100); return; } }); } @@ -873,164 +913,81 @@ function activeToACM() { // Gets the FWNonce from AMT and saves it to a file. function getFwNonce() { osamtstack.BatchEnum(null, ['*AMT_GeneralSettings', '*IPS_HostBasedSetupService'], function (stack, name, responses, status) { + if (status != 200) { console.log("Unable to get firmware activation nonce, status=" + status); exit(100); return; } var fwNonce = responses['IPS_HostBasedSetupService'].response['ConfigurationNonce']; var digestRealm = responses['AMT_GeneralSettings'].response['DigestRealm']; - var amtMeiModule, amtMei, str; - try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { console.log(ex); exit(1); return; } - amtMei.getLanInterfaceSettings(0, function (result) { if (result) { mestate.net0 = result; } }); - amtMei.getDnsSuffix(function (result) { - var fqdn = null; - if ((mestate.net0 == null) && (meinfo.net0.enabled != 0)) { console.log("No Intel AMT wired interface, can't perform ACM activation."); exit(100); return; } - if (result) { fqdn = result; } // If Intel AMT has a trusted DNS suffix set, use that one. - else { - // Look for the DNS suffix for the Intel AMT Ethernet interface - var interfaces = require('os').networkInterfaces(); - for (var i in interfaces) { - for (var j in interfaces[i]) { - if ((interfaces[i][j].mac == mestate.net0.mac) && (interfaces[i][j].fqdn != null) && (interfaces[i][j].fqdn != '')) { fqdn = interfaces[i][j].fqdn; } - } - } - } - if (fqdn != null) { - activeToACMEx(fwNonce, fqdn, digestRealm); - } else { - console.log("Trusted DNS suffix not set, can't perform ACM activation."); exit(100); return; - } - }); + activeToACMEx(fwNonce, settings.fqdn, digestRealm, settings.uuid); }); } -// Sends a message to RCS server using RCS Message Protocol -function sendRCSMessage(socket, status, event, message) { - //console.log('Status: ' + status + '. Event: ' + event + '. Message: ' + message); - if (socket !== null) { socket.write({ "status": status, "event": event, "data": message }); } -} +// Connect to the activation server and perform ACM activation +function activeToACMEx(fwNonce, dnsSuffix, digestRealm, uuid) { + console.log('FQDN: ' + dnsSuffix); + console.log('UUID: ' + uuid); + console.log('Realm: ' + digestRealm); + console.log('Nonce: ' + fwNonce); + console.log('Connecting to ' + settings.wss); -function activeToACMEx(fwNonce, dnsSuffix, digestRealm) { - // open connection to RCS - console.log('Initializing WebSocket...'); - // Establish WebSocket connection to RCS server - var connection = http.request(settings.wss); + // Establish WebSocket connection to activation server + var options = http.parseUri(settings.wss); + options.checkServerIdentity = function (clientName, certs) { }; // TODO + options.rejectUnauthorized = false; + var connection = http.request(options); connection.on('upgrade', function (response, socket) { - // WebSocket is up. Handle data on the duplex socket + console.log('Connected, requesting activation...'); + socket.on('end', function () { console.log('Connection closed'); exit(0); }); + socket.on('error', function () { console.log('Connection error'); exit(100); }); socket.on('data', function (data) { - // All messages from RCS are JSON.stringify format and need to be parsed - var message = JSON.parse(data); - // Check RCS Message Protocol version. Exit if version not supported - if (message.version > RCSMessageProtocolVersion) { console.log('Unsupported RCS server.'); socket.end(); exit(0) } - // Handle the AMT provisioning certificate blob (contains provisioning certificate, mcnonce, digital signature and password hash) - if (message.data.provCertObj !== undefined) { - activeToACMEx1(message.data, function (stack, name, responses, status, message) { - if (status !== 200) { - if (status == 2) { - console.log('AMT already provisioned.Exiting ' + status); - sendRCSMessage(socket, "error", "finish", "failed with status: " + status); - } else { - console.log('Failed to fetch activation status, status ' + status); - sendRCSMessage(socket, "error", "finish", "failed with status: " + status); - } - socket.end(); - exit(status); - } else if (responses['IPS_HostBasedSetupService'].response['AllowedControlModes'].length != 2) { - console.log('Admin control mode activation not allowed'); - sendRCSMessage(socket, "error", "finish", "failed with message: Admin control mode activation not allowed"); - socket.end(); - exit(status); + // Parse and check the response + var cmd = null; + try { cmd = JSON.parse(data); } catch (ex) { console.log('Unable to parse server response: ' + data); exit(100); return; } + if (typeof cmd != 'object') { console.log('Invalid server response: ' + cmd); exit(100); return; } + if (typeof cmd.errorText == 'string') { console.log('Server error: ' + cmd.errorText); exit(100); return; } + if (cmd.action != 'acmactivate') { console.log('Invalid server response, command: ' + cmd.cmd); exit(100); return; } + if (typeof cmd.signature != 'string') { console.log('Invalid server signature'); exit(100); return; } + if (typeof cmd.password != 'string') { console.log('Invalid server password'); exit(100); return; } + if (typeof cmd.nonce != 'string') { console.log('Invalid server nonce'); exit(100); return; } + if (typeof cmd.certs != 'object') { console.log('Invalid server certificates'); exit(100); return; } + + // We are ready to go, perform activation. + cmd.index = 0; + performAcmActivation(cmd, function (result) { + if (result == false) { + console.log('Intel AMT ACM activation failed.'); + } else { + if ((cmd.profileScript !== null) && (cmd.rawpassword != null)) { + console.log("Intel AMT ACM activation success, applying profile..."); + settings.scriptjson = cmd.profileScript; + settings.password = cmd.rawpassword; // TODO: This is only going to work if the server sends the raw password?? + settings.username = 'admin'; + startMeScriptEx(function () { console.log('Intel AMT profile applied.'); socket.end(); exit(0); }, stack); } else { - activeToACMEx2(message, function (stack, name, responses, status, message) { - if (status != 200) { - console.log('Failed to activate, status ' + status); - sendRCSMessage(socket, "error", "finish", "failed to activate. Status: " + status); - } else if (responses.Body.ReturnValue != 0) { - console.log('Admin control mode activation failed: ' + responses.Body.ReturnValueStr); - sendRCSMessage(socket, "error", "finish", "failed to activate: " + responses.Body.ReturnValueStr); - } else { - if (message.profileScript !== null) { - console.log("Running MEScript..."); - settings.scriptjson = message.profileScript; - settings.password = message.amtPassword - settings.username = 'admin'; - startMeScriptEx(function () { - console.log('AMT Profile applied'); - sendRCSMessage(socket, "ok", "finish", "success"); - socket.end(); - exit(0); - }, stack); - } else { - sendRCSMessage(socket, "ok", "finish", "success"); - socket.end(); - exit(0); - } - console.log('AMT Provisioning Success'); - } - //socket.end(); - //exit(status); - }); + console.log('Intel AMT ACM activation success.'); + socket.end(); + exit(0); } - }); - } - if (message.event.toString() == "cmd" && message.data.toString() == "acmready") { - sendRCSMessage(socket, "ok", "message", JSON.stringify(fwNonce)); - } - }); - socket.on('end', function () { console.log('WebSocket closed'); }); - sendRCSMessage(socket, "ok", "cmd", { "cmd": "acm", "dnssuffix": dnsSuffix, "profile": settings.profile, 'digestrealm': digestRealm, 'fwnonce': fwNonce }); - }); -} - -// Detects AMT provisioning state and injects the certificate chain into AMT firmware -function activeToACMEx1(data, callback) { - if (mestate.ProvisioningState.state == 0) { - console.log('Performing ACM provisioning...'); - // Perform full provisioning -- AMT was fully unprovisioned - injectCert(0, data, function (stack, name, responses, status, data) { - if (status !== 200) { exit(status); return; } - else if (responses['Body']['ReturnValue'] !== 0) { exit(responses['Body']['ReturnValueStr']); return; } - else if (responses['Body']['ReturnValue'] == 0) { - osamtstack.BatchEnum(null, ['*AMT_GeneralSettings', '*IPS_HostBasedSetupService'], function (stack, name, responses, status) { - callback(stack, name, responses, status, data); - }); - } - }); - } else if (mestate.ProvisioningState.state == 1) { - // Perform partial provisioning -- AMT was partial unprovisioned - // Currently not functional due to limitations in the HW. - console.log('Partial provisioning flow currently not available.'); - exit(0); - //osamtstack.BatchEnum(null, ['*AMT_GeneralSettings', '*IPS_HostBasedSetupService'], activeToACMEx2); - } else { - // AMT already provisioned - callback(null, null, null, 2, 'AMT already provisioned. Exiting') - exit(0); - } -} - -// Recursive function to inject the provisioning certificates into AMT in the proper order -function injectCert(index, cert, callback, stack, name, responses, status) { - var leaf = false; - var root = false; - if (index == 0) { leaf = true; } - if (index == cert.provCertObj.certChain.length - 1) { root = true; } - if (index < cert.provCertObj.certChain.length) { - if (cert.provCertObj.certChain[index] !== undefined) { - osamtstack.IPS_HostBasedSetupService_AddNextCertInChain(cert.provCertObj.certChain[index], leaf, root, function (stack, name, responses, status) { - if (status !== 200) { exit(status); return; } - else if (responses['Body']['ReturnValue'] !== 0) { exit(responses['Body']['ReturnValueStr']); return; } - else if (responses['Body']['ReturnValue'] == 0) { - index++; - injectCert(index, cert, callback, stack, name, responses, status); } }); - } - } else { callback(stack, name, responses, status, cert); } + }); + socket.write({ client: 'meshcmd', version: 1, action: 'acmactivate', fqdn: dnsSuffix, realm: digestRealm, nonce: fwNonce, uuid: uuid, profile: settings.profile, hashes: trustedHashes }); + }); + connection.end(); } -// Sends the password hash, mcnonce, and digital signature to complete the admin control mode provisioning -function activeToACMEx2(data, callback) { - //var passwordhash = md5hex('admin:' + responses['AMT_GeneralSettings'].response['DigestRealm'] + ':' + data.passwordHash).substring(0, 32); - //var debugreturn = {"Body": {"ReturnValue": 0}}; - //console.log("DEBUG: Everything up to activation works"); callback(null, null, debugreturn, 200, data); - osamtstack.IPS_HostBasedSetupService_AdminSetup(2, data.passwordHash, data.mcNonce, 2, data.digitalSignature, function (stack, name, responses, status) { callback(stack, name, responses, status, data); }); +// Recursive function to inject the provisioning certificates into AMT in the proper order and completes ACM activation +function performAcmActivation(acmdata, func) { + var leaf = (acmdata.index == 0), root = (acmdata.index == (acmdata.certs.length - 1)); + if ((acmdata.index < acmdata.certs.length) && (acmdata.certs[acmdata.index] != null)) { + osamtstack.IPS_HostBasedSetupService_AddNextCertInChain(acmdata.certs[acmdata.index], leaf, root, function (stack, name, responses, status) { + if (status !== 200) { debug('AddNextCertInChain status=' + status); return; } + else if (responses['Body']['ReturnValue'] !== 0) { debug('AddNextCertInChain error=' + responses['Body']['ReturnValue']); return; } + else { acmdata.index++; performAcmActivation(acmdata, func); } + }); + } else { + osamtstack.IPS_HostBasedSetupService_AdminSetup(2, acmdata.password, acmdata.nonce, 2, acmdata.signature, + function (stack, name, responses, status) { func((status == 200) && (responses['Body']['ReturnValue'] == 0)); } + ); + } } // diff --git a/agents/meshcmd.min.js b/agents/meshcmd.min.js index 45485f59..9e607fa7 100644 --- a/agents/meshcmd.min.js +++ b/agents/meshcmd.min.js @@ -498,9 +498,10 @@ function run(argv) { activeToCCM(); } else if (settings.action == 'amtacm') { // Start activation to ACM - if ((settings.wss == null) || (typeof settings.wss != 'string') || (settings.wss == '')) { console.log('No or invalid \"server name\" specified, use --wss [servername:port].'); exit(1); return; } - if ((settings.profile == null) || (typeof settings.profile != 'string') || (settings.profile == '')) { console.log('No or invalid \"profile name\" specified, use --profile [name].'); exit(1); return; } - settings.protocol = 'http:'; + if ((settings.wss == null) || (typeof settings.wss != 'string') || (settings.wss == '')) { console.log('No server URL specified, use --wss [url].'); exit(1); return; } + //if ((settings.profile == null) || (typeof settings.profile != 'string') || (settings.profile == '')) { console.log('No or invalid \"profile name\" specified, use --profile [name].'); exit(1); return; } + if ((typeof settings.profile != 'string') || (settings.profile == '')) { settings.profile = null; } + //settings.protocol = 'http:'; settings.localport = 16992; debug(1, "Settings: " + JSON.stringify(settings)); activeToACM(); @@ -848,24 +849,63 @@ function deactivateACMEx() { }); } +// +// Get Intel AMT activation hashes +// + +var trustedHashes = null; +function getTrustedHashes(amtMei, func, tag) { + console.log('getTrustedHashes'); + if (trustedHashes != null) { func(tag); } + trustedHashes = []; + amtMei.getHashHandles(function (handles) { + var exitOnCount = handles.length; + for (var i = 0; i < handles.length; ++i) { + this.getCertHashEntry(handles[i], function (result) { + if (result.isActive == 1) { trustedHashes.push(result.certificateHash.toLowerCase()); } + if (--exitOnCount == 0) { func(tag); } + }); + } + }); +} // // Activate Intel AMT to ACM // function activeToACM() { - // See if MicroLMS needs to be started and setup the $$OsAdmin wsman stack - console.log('Starting AMT Provisioning to Admin Control Mode.'); + console.log('Starting Intel AMT provisioning to Admin Control Mode (ACM) attempt...'); settings.noconsole = true; + // Display Intel AMT version and activation state mestate = {}; var amtMeiModule, amtMei; try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { console.log(ex); exit(1); return; } amtMei.on('error', function (e) { console.log('ERROR: ' + e); exit(1); return; }); - amtMei.getProvisioningState(function (result) { - if (result) { - mestate.ProvisioningState = result; - startLms(getFwNonce); // TODO: Fix this so that it works even if LMS already running. + amtMei.getProvisioningState(function (result) { if (result) { mestate.ProvisioningState = result; } }); + amtMei.getLanInterfaceSettings(0, function (result) { if (result) { mestate.net0 = result; } }); + amtMei.getUuid(function (result) { if ((result != null) && (result.uuid != null)) { mestate.uuid = result.uuid; } }); + amtMei.getDnsSuffix(function (result) { + if (mestate.ProvisioningState.state !== 0) { console.log("Intel AMT is not in pre-provisioning state: " + mestate.ProvisioningState.stateStr); exit(100); return; } + if (mestate.uuid == null) { console.log("Unable to get Intel AMT UUID."); exit(100); return; } + var fqdn = null; + if ((mestate.net0 == null) && (meinfo.net0.enabled != 0)) { console.log("No Intel AMT wired interface, can't perform ACM activation."); exit(100); return; } + if (result) { fqdn = result; } // If Intel AMT has a trusted DNS suffix set, use that one. + else { + // Look for the DNS suffix for the Intel AMT Ethernet interface + var interfaces = require('os').networkInterfaces(); + for (var i in interfaces) { + for (var j in interfaces[i]) { + if ((interfaces[i][j].mac == mestate.net0.mac) && (interfaces[i][j].fqdn != null) && (interfaces[i][j].fqdn != '')) { fqdn = interfaces[i][j].fqdn; } + } + } + } + if (fqdn != null) { + settings.fqdn = fqdn; + settings.uuid = mestate.uuid; + getTrustedHashes(amtMei, function () { startLms(getFwNonce, amtMei); }); + } else { + console.log("Trusted DNS suffix not set, can't perform ACM activation."); exit(100); return; } }); } @@ -873,164 +913,81 @@ function activeToACM() { // Gets the FWNonce from AMT and saves it to a file. function getFwNonce() { osamtstack.BatchEnum(null, ['*AMT_GeneralSettings', '*IPS_HostBasedSetupService'], function (stack, name, responses, status) { + if (status != 200) { console.log("Unable to get firmware activation nonce, status=" + status); exit(100); return; } var fwNonce = responses['IPS_HostBasedSetupService'].response['ConfigurationNonce']; var digestRealm = responses['AMT_GeneralSettings'].response['DigestRealm']; - var amtMeiModule, amtMei, str; - try { amtMeiModule = require('amt-mei'); amtMei = new amtMeiModule(); } catch (ex) { console.log(ex); exit(1); return; } - amtMei.getLanInterfaceSettings(0, function (result) { if (result) { mestate.net0 = result; } }); - amtMei.getDnsSuffix(function (result) { - var fqdn = null; - if ((mestate.net0 == null) && (meinfo.net0.enabled != 0)) { console.log("No Intel AMT wired interface, can't perform ACM activation."); exit(100); return; } - if (result) { fqdn = result; } // If Intel AMT has a trusted DNS suffix set, use that one. - else { - // Look for the DNS suffix for the Intel AMT Ethernet interface - var interfaces = require('os').networkInterfaces(); - for (var i in interfaces) { - for (var j in interfaces[i]) { - if ((interfaces[i][j].mac == mestate.net0.mac) && (interfaces[i][j].fqdn != null) && (interfaces[i][j].fqdn != '')) { fqdn = interfaces[i][j].fqdn; } - } - } - } - if (fqdn != null) { - activeToACMEx(fwNonce, fqdn, digestRealm); - } else { - console.log("Trusted DNS suffix not set, can't perform ACM activation."); exit(100); return; - } - }); + activeToACMEx(fwNonce, settings.fqdn, digestRealm, settings.uuid); }); } -// Sends a message to RCS server using RCS Message Protocol -function sendRCSMessage(socket, status, event, message) { - //console.log('Status: ' + status + '. Event: ' + event + '. Message: ' + message); - if (socket !== null) { socket.write({ "status": status, "event": event, "data": message }); } -} +// Connect to the activation server and perform ACM activation +function activeToACMEx(fwNonce, dnsSuffix, digestRealm, uuid) { + console.log('FQDN: ' + dnsSuffix); + console.log('UUID: ' + uuid); + console.log('Realm: ' + digestRealm); + console.log('Nonce: ' + fwNonce); + console.log('Connecting to ' + settings.wss); -function activeToACMEx(fwNonce, dnsSuffix, digestRealm) { - // open connection to RCS - console.log('Initializing WebSocket...'); - // Establish WebSocket connection to RCS server - var connection = http.request(settings.wss); + // Establish WebSocket connection to activation server + var options = http.parseUri(settings.wss); + options.checkServerIdentity = function (clientName, certs) { }; // TODO + options.rejectUnauthorized = false; + var connection = http.request(options); connection.on('upgrade', function (response, socket) { - // WebSocket is up. Handle data on the duplex socket + console.log('Connected, requesting activation...'); + socket.on('end', function () { console.log('Connection closed'); exit(0); }); + socket.on('error', function () { console.log('Connection error'); exit(100); }); socket.on('data', function (data) { - // All messages from RCS are JSON.stringify format and need to be parsed - var message = JSON.parse(data); - // Check RCS Message Protocol version. Exit if version not supported - if (message.version > RCSMessageProtocolVersion) { console.log('Unsupported RCS server.'); socket.end(); exit(0) } - // Handle the AMT provisioning certificate blob (contains provisioning certificate, mcnonce, digital signature and password hash) - if (message.data.provCertObj !== undefined) { - activeToACMEx1(message.data, function (stack, name, responses, status, message) { - if (status !== 200) { - if (status == 2) { - console.log('AMT already provisioned.Exiting ' + status); - sendRCSMessage(socket, "error", "finish", "failed with status: " + status); - } else { - console.log('Failed to fetch activation status, status ' + status); - sendRCSMessage(socket, "error", "finish", "failed with status: " + status); - } - socket.end(); - exit(status); - } else if (responses['IPS_HostBasedSetupService'].response['AllowedControlModes'].length != 2) { - console.log('Admin control mode activation not allowed'); - sendRCSMessage(socket, "error", "finish", "failed with message: Admin control mode activation not allowed"); - socket.end(); - exit(status); + // Parse and check the response + var cmd = null; + try { cmd = JSON.parse(data); } catch (ex) { console.log('Unable to parse server response: ' + data); exit(100); return; } + if (typeof cmd != 'object') { console.log('Invalid server response: ' + cmd); exit(100); return; } + if (typeof cmd.errorText == 'string') { console.log('Server error: ' + cmd.errorText); exit(100); return; } + if (cmd.action != 'acmactivate') { console.log('Invalid server response, command: ' + cmd.cmd); exit(100); return; } + if (typeof cmd.signature != 'string') { console.log('Invalid server signature'); exit(100); return; } + if (typeof cmd.password != 'string') { console.log('Invalid server password'); exit(100); return; } + if (typeof cmd.nonce != 'string') { console.log('Invalid server nonce'); exit(100); return; } + if (typeof cmd.certs != 'object') { console.log('Invalid server certificates'); exit(100); return; } + + // We are ready to go, perform activation. + cmd.index = 0; + performAcmActivation(cmd, function (result) { + if (result == false) { + console.log('Intel AMT ACM activation failed.'); + } else { + if ((cmd.profileScript !== null) && (cmd.rawpassword != null)) { + console.log("Intel AMT ACM activation success, applying profile..."); + settings.scriptjson = cmd.profileScript; + settings.password = cmd.rawpassword; // TODO: This is only going to work if the server sends the raw password?? + settings.username = 'admin'; + startMeScriptEx(function () { console.log('Intel AMT profile applied.'); socket.end(); exit(0); }, stack); } else { - activeToACMEx2(message, function (stack, name, responses, status, message) { - if (status != 200) { - console.log('Failed to activate, status ' + status); - sendRCSMessage(socket, "error", "finish", "failed to activate. Status: " + status); - } else if (responses.Body.ReturnValue != 0) { - console.log('Admin control mode activation failed: ' + responses.Body.ReturnValueStr); - sendRCSMessage(socket, "error", "finish", "failed to activate: " + responses.Body.ReturnValueStr); - } else { - if (message.profileScript !== null) { - console.log("Running MEScript..."); - settings.scriptjson = message.profileScript; - settings.password = message.amtPassword - settings.username = 'admin'; - startMeScriptEx(function () { - console.log('AMT Profile applied'); - sendRCSMessage(socket, "ok", "finish", "success"); - socket.end(); - exit(0); - }, stack); - } else { - sendRCSMessage(socket, "ok", "finish", "success"); - socket.end(); - exit(0); - } - console.log('AMT Provisioning Success'); - } - //socket.end(); - //exit(status); - }); + console.log('Intel AMT ACM activation success.'); + socket.end(); + exit(0); } - }); - } - if (message.event.toString() == "cmd" && message.data.toString() == "acmready") { - sendRCSMessage(socket, "ok", "message", JSON.stringify(fwNonce)); - } - }); - socket.on('end', function () { console.log('WebSocket closed'); }); - sendRCSMessage(socket, "ok", "cmd", { "cmd": "acm", "dnssuffix": dnsSuffix, "profile": settings.profile, 'digestrealm': digestRealm, 'fwnonce': fwNonce }); - }); -} - -// Detects AMT provisioning state and injects the certificate chain into AMT firmware -function activeToACMEx1(data, callback) { - if (mestate.ProvisioningState.state == 0) { - console.log('Performing ACM provisioning...'); - // Perform full provisioning -- AMT was fully unprovisioned - injectCert(0, data, function (stack, name, responses, status, data) { - if (status !== 200) { exit(status); return; } - else if (responses['Body']['ReturnValue'] !== 0) { exit(responses['Body']['ReturnValueStr']); return; } - else if (responses['Body']['ReturnValue'] == 0) { - osamtstack.BatchEnum(null, ['*AMT_GeneralSettings', '*IPS_HostBasedSetupService'], function (stack, name, responses, status) { - callback(stack, name, responses, status, data); - }); - } - }); - } else if (mestate.ProvisioningState.state == 1) { - // Perform partial provisioning -- AMT was partial unprovisioned - // Currently not functional due to limitations in the HW. - console.log('Partial provisioning flow currently not available.'); - exit(0); - //osamtstack.BatchEnum(null, ['*AMT_GeneralSettings', '*IPS_HostBasedSetupService'], activeToACMEx2); - } else { - // AMT already provisioned - callback(null, null, null, 2, 'AMT already provisioned. Exiting') - exit(0); - } -} - -// Recursive function to inject the provisioning certificates into AMT in the proper order -function injectCert(index, cert, callback, stack, name, responses, status) { - var leaf = false; - var root = false; - if (index == 0) { leaf = true; } - if (index == cert.provCertObj.certChain.length - 1) { root = true; } - if (index < cert.provCertObj.certChain.length) { - if (cert.provCertObj.certChain[index] !== undefined) { - osamtstack.IPS_HostBasedSetupService_AddNextCertInChain(cert.provCertObj.certChain[index], leaf, root, function (stack, name, responses, status) { - if (status !== 200) { exit(status); return; } - else if (responses['Body']['ReturnValue'] !== 0) { exit(responses['Body']['ReturnValueStr']); return; } - else if (responses['Body']['ReturnValue'] == 0) { - index++; - injectCert(index, cert, callback, stack, name, responses, status); } }); - } - } else { callback(stack, name, responses, status, cert); } + }); + socket.write({ client: 'meshcmd', version: 1, action: 'acmactivate', fqdn: dnsSuffix, realm: digestRealm, nonce: fwNonce, uuid: uuid, profile: settings.profile, hashes: trustedHashes }); + }); + connection.end(); } -// Sends the password hash, mcnonce, and digital signature to complete the admin control mode provisioning -function activeToACMEx2(data, callback) { - //var passwordhash = md5hex('admin:' + responses['AMT_GeneralSettings'].response['DigestRealm'] + ':' + data.passwordHash).substring(0, 32); - //var debugreturn = {"Body": {"ReturnValue": 0}}; - //console.log("DEBUG: Everything up to activation works"); callback(null, null, debugreturn, 200, data); - osamtstack.IPS_HostBasedSetupService_AdminSetup(2, data.passwordHash, data.mcNonce, 2, data.digitalSignature, function (stack, name, responses, status) { callback(stack, name, responses, status, data); }); +// Recursive function to inject the provisioning certificates into AMT in the proper order and completes ACM activation +function performAcmActivation(acmdata, func) { + var leaf = (acmdata.index == 0), root = (acmdata.index == (acmdata.certs.length - 1)); + if ((acmdata.index < acmdata.certs.length) && (acmdata.certs[acmdata.index] != null)) { + osamtstack.IPS_HostBasedSetupService_AddNextCertInChain(acmdata.certs[acmdata.index], leaf, root, function (stack, name, responses, status) { + if (status !== 200) { debug('AddNextCertInChain status=' + status); return; } + else if (responses['Body']['ReturnValue'] !== 0) { debug('AddNextCertInChain error=' + responses['Body']['ReturnValue']); return; } + else { acmdata.index++; performAcmActivation(acmdata, func); } + }); + } else { + osamtstack.IPS_HostBasedSetupService_AdminSetup(2, acmdata.password, acmdata.nonce, 2, acmdata.signature, + function (stack, name, responses, status) { func((status == 200) && (responses['Body']['ReturnValue'] == 0)); } + ); + } } // diff --git a/certoperations.js b/certoperations.js index ef38101a..123f21e6 100644 --- a/certoperations.js +++ b/certoperations.js @@ -30,12 +30,12 @@ module.exports.CertificateOperations = function (parent) { // Sign a Intel AMT ACM activation request obj.signAcmRequest = function (domain, request, user, pass, ipport, nodeid, meshid, computerName, agentId) { - 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 null; - if (parent.common.validateString(request.nonce, 16, 256) == false) return null; - if (parent.common.validateString(request.realm, 16, 256) == false) return null; - if (parent.common.validateString(request.fqdn, 4, 256) == false) return null; - if (parent.common.validateString(request.hash, 16, 256) == false) return null; - if (parent.common.validateString(request.uuid, 36, 36) == false) return null; + 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' }; + if (parent.common.validateString(request.nonce, 16, 256) == false) return { 'action': 'acmactivate', 'error': 1, 'errorText': 'Invalid nonce argument' }; + if (parent.common.validateString(request.realm, 16, 256) == false) return { 'action': 'acmactivate', 'error': 1, 'errorText': 'Invalid realm argument' }; + if (parent.common.validateString(request.fqdn, 4, 256) == false) return { 'action': 'acmactivate', 'error': 1, 'errorText': 'Invalid FQDN argument' }; + if (parent.common.validateString(request.hash, 16, 256) == false) return { 'action': 'acmactivate', 'error': 1, 'errorText': 'Invalid hash argument' }; + if (parent.common.validateString(request.uuid, 36, 36) == false) return { 'action': 'acmactivate', 'error': 1, 'errorText': 'Invalid UUID argument' }; // Look for the signing certificate var signkey = null, certChain = null, hashAlgo = null, certIndex = null; @@ -44,10 +44,10 @@ module.exports.CertificateOperations = function (parent) { if ((certEntry.sha256 == request.hash) && ((certEntry.cn == '*') || (certEntry.cn == request.fqdn))) { hashAlgo = 'sha256'; signkey = certEntry.key; certChain = certEntry.certs; certIndex = i; break; } if ((certEntry.sha1 == request.hash) && ((certEntry.cn == '*') || (certEntry.cn == request.fqdn))) { hashAlgo = 'sha1'; signkey = certEntry.key; certChain = certEntry.certs; certIndex = i; break; } } - if (signkey == null) return null; // Did not find a match. + if (signkey == null) return { 'action': 'acmactivate', 'error': 2, 'errorText': 'No signing certificate found' }; // Did not find a match. // If the matching certificate is a root cert, issue a leaf cert that matches the fqdn - if (domain.amtacmactivation.certs[certIndex].cn == '*') return; // TODO: Add support for this mode + if (domain.amtacmactivation.certs[certIndex].cn == '*') return { 'action': 'acmactivate', 'error': 3, 'errorText': 'Unsupported activation' }; // TODO: Add support for this mode // Setup both nonces, ready to be signed const mcNonce = Buffer.from(obj.crypto.randomBytes(20), 'binary'); @@ -59,10 +59,10 @@ module.exports.CertificateOperations = function (parent) { var signer = obj.crypto.createSign(hashAlgo); signer.update(Buffer.concat([fwNonce, mcNonce])); signature = signer.sign(signkey, 'base64'); - } catch (ex) { return null; } + } catch (ex) { return { 'action': 'acmactivate', 'error': 4, 'errorText': 'Unable to perform signature' }; } // Log the activation request, logging is a required step for activation. - if (obj.logAmtActivation(domain, { time: new Date(), 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 }) == false) return null; + if (obj.logAmtActivation(domain, { time: new Date(), 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 }) == false) return { 'action': 'acmactivate', 'error': 5, 'errorText': 'Unable to log operation' }; // Return the signature with the computed account password hash return { 'action': 'acmactivate', 'signature': signature, 'password': obj.crypto.createHash('md5').update(user + ':' + request.realm + ':' + pass).digest('hex'), 'nonce': mcNonce.toString('base64'), 'certs': certChain }; diff --git a/meshagent.js b/meshagent.js index 7295baab..5161295c 100644 --- a/meshagent.js +++ b/meshagent.js @@ -1222,7 +1222,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { // Agent is asking the server to sign an Intel AMT ACM activation request var signResponse = parent.parent.certificateOperations.signAcmRequest(domain, command, 'admin', amtpassword, obj.remoteaddrport, obj.dbNodeKey, obj.dbMeshKey, obj.agentInfo.computerName, obj.agentInfo.agentId); // TODO: Place account credentials!!! - if (signResponse != null) { + if ((signResponse != null) && (signResponse.error == null)) { // Log this activation event var event = { etype: 'node', action: 'amtactivate', nodeid: obj.dbNodeKey, domain: domain.id, msg: 'Device requested Intel AMT ACM activation, FQDN: ' + command.fqdn, ip: obj.remoteaddrport }; if (db.changeStream) { event.noact = 1; } // If DB change stream is active, don't use this event to change the node. Another event will come. @@ -1232,7 +1232,7 @@ module.exports.CreateMeshAgent = function (parent, db, ws, req, args, domain) { ChangeAgentCoreInfo({ "intelamt": { user: 'admin', pass: amtpassword, uuid: command.uuid, realm: command.realm } }); // Send the activation response - obj.send(JSON.stringify(signResponse)); + //obj.send(JSON.stringify(signResponse)); // DEBUG**************************** } break; } diff --git a/views/default.handlebars b/views/default.handlebars index 152ba3e5..e5f1c76e 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -2766,8 +2766,8 @@ // Windows agent install //x += "
To add a new computer to device group \"" + EscapeHtml(mesh.name) + "\", download the mesh agent and configuration file and install the agent on the computer to manage.

"; x += "
To add a new computer to device group \"" + EscapeHtml(mesh.name) + "\", download the mesh agent and install it the computer to manage. This agent has server and device group information embedded within it.

"; - x += addHtmlValue('Mesh Agent', 'Windows (.exe)'); - x += addHtmlValue('Mesh Agent', 'Windows x64 (.exe)'); + x += addHtmlValue('Mesh Agent', 'Windows (.exe)'); + x += addHtmlValue('Mesh Agent', 'Windows x64 (.exe)'); if (debugmode > 0) { x += addHtmlValue('Settings File', '' + EscapeHtml(mesh.name) + ' settings (.msh)'); } x += "
"; @@ -2783,8 +2783,8 @@ // Windows agent uninstall x += ""; // Linux agent uninstall @@ -4106,7 +4106,7 @@ ++count; date = new Date(date.getTime() - (1000 * 60 * 60 * 24)); // Substract one day } - QH('p10html2', '' + x + '
Day7 Day Power State
'); + QH('p10html2', '' + x + '
Day7 Day Power State
'); } // Return a color for the given power state @@ -4261,7 +4261,7 @@ function p10showMeshRouterDialog() { if (xxdialogMode) return; var x = "
MeshCentral Router is a Windows tool for TCP port mapping. You can, for example, RDP into a remote device thru this server.

"; - x += addHtmlValue('Win32 Executable', 'MeshCentralRouter.exe'); + x += addHtmlValue('Win32 Executable', 'MeshCentralRouter.exe'); setDialogMode(2, "MeshCentral Router", 1, null, x, "fileDownload"); } diff --git a/webserver.js b/webserver.js index 77e27fc0..dfc2112b 100644 --- a/webserver.js +++ b/webserver.js @@ -2153,6 +2153,83 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { }); } + // Handle a Intel AMT activation request + function handleAmtActivateWebSocket(ws, req) { + const domain = checkUserIpAddress(ws, req); + if (domain == null) { ws.send(JSON.stringify({ errorText: 'Invalid domain' })); ws.close(); return; } + if (req.query.id == null) { ws.send(JSON.stringify({ errorText: 'Missing group identifier' })); ws.close(); return; } + + // Fetch the mesh object + ws.meshid = 'mesh/' + domain.id + '/' + req.query.id; + const mesh = obj.meshes[ws.meshid]; + if (mesh == null) { delete ws.meshid; ws.send(JSON.stringify({ errorText: 'Invalid device group' })); ws.close(); return; } + if (mesh.mtype != 1) { ws.send(JSON.stringify({ errorText: 'Invalid device group type' })); ws.close(); return; } + + // Fetch the remote IP:Port for logging + const remoteaddr = (req.ip.startsWith('::ffff:')) ? (req.ip.substring(7)) : req.ip; + ws.remoteaddrport = remoteaddr + ':' + ws._socket.remotePort; + + // When data is received from the web socket, echo it back + ws.on('message', function (data) { + // Parse the incoming command + var cmd = null; + try { cmd = JSON.parse(data); } catch (ex) { }; + if (cmd == null) return; + + // Process the command + switch (cmd.action) { + case 'acmactivate': { + // Check the command + if (cmd.version != 1) { ws.send(JSON.stringify({ errorText: 'Unsupported version' })); ws.close(); return; } + if (typeof cmd.hashes != 'object') { ws.send(JSON.stringify({ errorText: 'Invalid hashes' })); ws.close(); return; } + if (typeof cmd.fqdn != 'string') { ws.send(JSON.stringify({ errorText: 'Invalid FQDN' })); ws.close(); return; } + + // Get the current Intel AMT policy + var mesh = obj.meshes[ws.meshid]; + if ((mesh == null) || (mesh.amt == null) || (mesh.amt.type != 3) || (domain.amtacmactivation == null) || (domain.amtacmactivation.acmmatch == null) || (mesh.amt.password == null)) { ws.send(JSON.stringify({ errorText: 'Unable to activate' })); ws.close(); return; } + + // Check if we have a FQDN/Hash match + var matchingHash = null, matchingCN = null; + for (var i in domain.amtacmactivation.acmmatch) { + // Check for a matching FQDN + if ((domain.amtacmactivation.acmmatch[i].cn == '*') || (domain.amtacmactivation.acmmatch[i].cn.toLowerCase() == cmd.fqdn)) { + // Check for a matching certificate + if (cmd.hashes.indexOf(domain.amtacmactivation.acmmatch[i].sha256) >= 0) { + matchingCN = domain.amtacmactivation.acmmatch[i].cn; + matchingHash = domain.amtacmactivation.acmmatch[i].sha256; + continue; + } else if (cmd.hashes.indexOf(domain.amtacmactivation.acmmatch[i].sha1) >= 0) { + matchingCN = domain.amtacmactivation.acmmatch[i].cn; + matchingHash = domain.amtacmactivation.acmmatch[i].sha1; + continue; + } + } + } + if (matchingHash == null) { ws.send(JSON.stringify({ errorText: 'No matching activation certificates' })); ws.close(); return; } + if (matchingCN == '*') { ws.send(JSON.stringify({ errorText: 'Wildcard certificate activation not yet supported' })); ws.close(); return; } + cmd.hash = matchingHash; + + // Get the Intel AMT admin password, randomize if needed. + var amtpassword = ((mesh.amt.password == '') ? getRandomAmtPassword() : mesh.amt.password); + if (checkAmtPassword(amtpassword) == false) { ws.send(JSON.stringify({ errorText: 'Invalid Intel AMT password' })); ws.close(); return; } // Invalid Intel AMT password, this should never happen. + + // Agent is asking the server to sign an Intel AMT ACM activation request + var signResponse = parent.certificateOperations.signAcmRequest(domain, cmd, 'admin', amtpassword, ws.remoteaddrport, null, ws.meshid, null, null); + ws.send(JSON.stringify(signResponse)); + break; + } + default: { + // This is not a known command + ws.send(JSON.stringify({ errorText: 'Invalid command' })); ws.close(); return; + } + } + }); + + // If close or error, do nothing. + ws.on('error', function (err) { }); + ws.on('close', function (req) { }); + } + // Handle the web socket echo request, just echo back the data sent function handleEchoWebSocket(ws, req) { const domain = checkUserIpAddress(ws, req); @@ -2790,6 +2867,11 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { }); } + // Intel AMT ACM activation + if ((parent.config.domains[i].amtacmactivation != null) && (parent.config.domains[i].amtacmactivation.acmmatch != null)) { + obj.app.ws(url + 'amtactivate', handleAmtActivateWebSocket); + } + // Creates a login token using the user/pass that is passed in as URL arguments. // For example: https://localhost/createLoginToken.ashx?user=admin&pass=admin&a=3 // It's not advised to use this to create login tokens since the URL is often logged and you got credentials in the URL. @@ -3165,5 +3247,9 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { // Return the query string portion of the URL, the ? and anything after. function getQueryPortion(req) { var s = req.url.indexOf('?'); if (s == -1) { if (req.body && req.body.urlargs) { return req.body.urlargs; } return ''; } return req.url.substring(s); } + // Generate a random Intel AMT password + function checkAmtPassword(p) { return (p.length > 7) && (/\d/.test(p)) && (/[a-z]/.test(p)) && (/[A-Z]/.test(p)) && (/\W/.test(p)); } + function getRandomAmtPassword() { var p; do { p = Buffer.from(obj.crypto.randomBytes(9), 'binary').toString('base64').split('/').join('@'); } while (checkAmtPassword(p) == false); return p; } + return obj; }; \ No newline at end of file