From a3f9ffc68b1559982762dfb5cdc6aba11cfa6e98 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Mon, 22 Mar 2021 23:36:05 -0700 Subject: [PATCH] More work on Intel AMT One Click Recovery. --- amtmanager.js | 53 +++++++++++++++++++++++++++++++--------- meshuser.js | 6 ++--- views/default.handlebars | 12 ++++----- webserver.js | 8 +++--- 4 files changed, 55 insertions(+), 24 deletions(-) diff --git a/amtmanager.js b/amtmanager.js index 34887acf..cb9bde8b 100644 --- a/amtmanager.js +++ b/amtmanager.js @@ -295,9 +295,9 @@ module.exports.CreateAmtManager = function (parent) { if (Array.isArray(event.nodeids)) { for (var i in event.nodeids) { performPowerAction(event.nodeids[i], 2); } } break; } - case 'clickoncerecovery': { // React to Intel AMT Click Once Recovery command + case 'oneclickrecovery': { // React to Intel AMT One Click Recovery command if (event.noact == 1) return; // Take no action on these events. We are likely in peering mode and need to only act when the database signals the change in state. - if (Array.isArray(event.nodeids)) { for (var i in event.nodeids) { performClickOnceRecoveryAction(event.nodeids[i], event.file); } } + if (Array.isArray(event.nodeids)) { for (var i in event.nodeids) { performOneClickRecoveryAction(event.nodeids[i], event.file); } } break; } case 'changenode': { // React to changes in a device @@ -810,23 +810,53 @@ module.exports.CreateAmtManager = function (parent) { } - // Perform Intel AMT Click Once Recovery on a device - function performClickOnceRecoveryAction(nodeid, file) { + // + // Intel AMT One Click Recovery + // + + // Perform Intel AMT One Click Recovery on a device + function performOneClickRecoveryAction(nodeid, file) { var devices = obj.amtDevices[nodeid]; if (devices == null) return; for (var i in devices) { var dev = devices[i]; // If not LMS, has a AMT stack present and is in connected state, perform operation. if ((dev.connType != 2) && (dev.state == 1) && (dev.amtstack != null)) { - console.log('Perform Click Once Recovery', nodeid, file); - - // TODO: Make sure the MPS server root certificate is present. - // TODO: Generate the one-time URL. - // TODO: Issue the WSMAN command. + // Make sure the MPS server root certificate is present. + // Start by looking at existing certificates. + dev.ocrfile = file; + dev.amtstack.BatchEnum(null, ['AMT_PublicKeyCertificate'], performOneClickRecoveryActionEx); } } } + // Response with list of certificates in Intel AMT + function performOneClickRecoveryActionEx(stack, name, responses, status) { + const dev = stack.dev; + if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. + if (status != 200) { dev.consoleMsg("Failed to get security information (" + status + ")."); removeAmtDevice(dev, 19); return; } + + // Organize the certificates + var xxCertificates = responses['AMT_PublicKeyCertificate'].responses; + for (var i in xxCertificates) { + xxCertificates[i].TrustedRootCertficate = (xxCertificates[i]['TrustedRootCertficate'] == true); + xxCertificates[i].X509CertificateBin = Buffer.from(xxCertificates[i]['X509Certificate'], 'base64').toString('binary'); + xxCertificates[i].XIssuer = parseCertName(xxCertificates[i]['Issuer']); + xxCertificates[i].XSubject = parseCertName(xxCertificates[i]['Subject']); + } + dev.policy.certificates = xxCertificates; + attemptRootCertSync(dev, performOneClickRecoveryActionEx2, true); + } + + function performOneClickRecoveryActionEx2(dev) { + // Generate the one-time URL. + var cookie = obj.parent.encodeCookie({ a: 'ocr', f: dev.ocrfile }, obj.parent.loginCookieEncryptionKey) + var url = 'https://' + parent.webserver.certificates.AmtMpsName + ':' + ((parent.args.mpsaliasport != null) ? parent.args.mpsaliasport : parent.args.mpsport) + '/ocr/' + cookie + '.iso'; + + // TODO: Issue the WSMAN command. + console.log('Perform One Click Recovery', url); + } + // // Intel AMT Clock Syncronization // @@ -1188,10 +1218,11 @@ module.exports.CreateAmtManager = function (parent) { // // Check if Intel AMT has the server root certificate - function attemptRootCertSync(dev, func) { + function attemptRootCertSync(dev, func, forced) { if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request. if (dev.policy.amtPolicy == 0) { func(dev); return; } // If there is no Intel AMT policy, skip this operation. - if ((dev.connType != 2) || (dev.policy.ciraPolicy != 2) || (parent.mpsserver.server == null)) { func(dev); return; } // Server root certificate does not need to be present is CIRA is not needed + if (forced !== true) { if ((dev.connType != 2) || (dev.policy.ciraPolicy != 2)) { func(dev); return; } } // Server root certificate does not need to be present if CIRA is not needed and "forced" is false + if (parent.mpsserver.server == null) { func(dev); return; } // Root cert not needed if MPS is not active. // Find the current TLS certificate & MeshCentral root certificate var xxMeshCentralRoot = null; diff --git a/meshuser.js b/meshuser.js index 97f2869f..ece7d48e 100644 --- a/meshuser.js +++ b/meshuser.js @@ -5492,7 +5492,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use } break; } - case 'clickoncerecovery': { // Intel(R) AMT Click once recovery + case 'oneclickrecovery': { // Intel(R) AMT One Click Recovery (OCR) if (common.validateStrArray(command.nodeids, 1) == false) break; // Check nodeids if (common.validateString(command.path, 1, 2048) == false) break; // Check file path if (command.type != 'diskimage') break; // Make sure type is correct @@ -5510,8 +5510,8 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use // Check if we found this device and if we have full rights if ((node == null) || (rights != 0xFFFFFFFF)) return; - // Event Intel AMT Click Once Recovery, this will cause Intel AMT wake operations on this and other servers. - parent.parent.DispatchEvent('*', obj, { action: 'clickoncerecovery', userid: user._id, username: user.name, nodeids: [node._id], domain: domain.id, nolog: 1, file: file.fullpath }); + // Event Intel AMT One Click Recovery, this will cause Intel AMT wake operations on this and other servers. + parent.parent.DispatchEvent('*', obj, { action: 'oneclickrecovery', userid: user._id, username: user.name, nodeids: [node._id], domain: domain.id, nolog: 1, file: file.fullpath }); }); } diff --git a/views/default.handlebars b/views/default.handlebars index 9cce2911..30440150 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -6613,7 +6613,7 @@ if (((currentNode.conn & 1) != 0) && ((rights & 131072) != 0)) { count++; y += ''; } // Remote command permission if ((currentNode.conn != 0) && ((rights & 262144) != 0)) { count++; y += ''; } if ((currentNode.conn & 16) != 0) { count++; y += ''; } - if ((getNodeAmtVersion(currentNode) >= 15) && (currentNode.intelamt.state == 2) && ((currentNode.conn & 6) != 0) && (rights == 0xFFFFFFFF) && ((features & 0x00000400) == 0)) { count++; y += ''; } // CIRA (2) or AMT (4) connected + if ((getNodeAmtVersion(currentNode) >= 15) && (currentNode.intelamt.state == 2) && ((currentNode.conn & 6) != 0) && (rights == 0xFFFFFFFF) && ((features & 0x00000400) == 0)) { count++; y += ''; } // CIRA (2) or AMT (4) connected if (((currentNode.conn & 1) != 0) && ((rights & 32768) != 0)) { count++; y += ''; } y += ''; x += addHtmlValue("Operation", y); @@ -6651,12 +6651,12 @@ //QE('idx_dlgOkButton', true); } } else if (op == 107) { - // Intel AMT Click Once Recovery (OCR) - Q('d3localmodeform').action = 'clickoncerecovery.ashx'; + // Intel AMT One Click Recovery (OCR) + Q('d3localmodeform').action = 'oneclickrecovery.ashx'; Q('d3auth').value = authCookie; Q('d3filter').value = '.iso'; Q('d3attrib').value = currentNode._id; - setDialogMode(3, "Intel® AMT Click Once Recovery", 3, deviceActionClickOnceRecovery); + setDialogMode(3, "Intel® AMT One Click Recovery", 3, deviceActionOneClickRecovery); d3init(); } else { // Power operation @@ -6664,7 +6664,7 @@ } } - function deviceActionClickOnceRecovery() { + function deviceActionOneClickRecovery() { var mode = Q('d3uploadMode').value; if (mode == 1) { // Boot using local disk image file @@ -6672,7 +6672,7 @@ } else { // Boot using server image file var files = d3getFileSel(); - if (files.length == 1) { meshserver.send({ action: 'clickoncerecovery', nodeids: [ Q('d3attrib').value ], type: 'diskimage', path: d3filetreelocation.join('/') + '/' + files[0] }); } + if (files.length == 1) { meshserver.send({ action: 'oneclickrecovery', nodeids: [ Q('d3attrib').value ], type: 'diskimage', path: d3filetreelocation.join('/') + '/' + files[0] }); } } } diff --git a/webserver.js b/webserver.js index 1d308e2a..8236c88c 100644 --- a/webserver.js +++ b/webserver.js @@ -3408,7 +3408,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { } // Upload a MeshCore.js file to the server - function handleClickOnceRecoveryFile(req, res) { + function handleOneClickRecoveryFile(req, res) { const domain = checkUserIpAddress(req, res); if (domain == null) { return; } if (domain.id !== '') { res.sendStatus(401); return; } @@ -3439,8 +3439,8 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { for (var i in files.files) { var file = files.files[i]; - // Event Intel AMT Click Once Recovery, this will cause Intel AMT wake operations on this and other servers. - parent.DispatchEvent('*', obj, { action: 'clickoncerecovery', userid: user._id, username: user.name, nodeids: [node._id], domain: domain.id, nolog: 1, file: file.path }); + // Event Intel AMT One Click Recovery, this will cause Intel AMT wake operations on this and other servers. + parent.DispatchEvent('*', obj, { action: 'oneclickrecovery', userid: user._id, username: user.name, nodeids: [node._id], domain: domain.id, nolog: 1, file: file.path }); try { obj.fs.unlinkSync(file.path); } catch (e) { } // TODO: Remove this file after 30 minutes. } @@ -5250,7 +5250,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) { obj.app.post(url + 'uploadfile.ashx', handleUploadFile); obj.app.post(url + 'uploadfilebatch.ashx', handleUploadFileBatch); obj.app.post(url + 'uploadmeshcorefile.ashx', handleUploadMeshCoreFile); - obj.app.post(url + 'clickoncerecovery.ashx', handleClickOnceRecoveryFile); + obj.app.post(url + 'oneclickrecovery.ashx', handleOneClickRecoveryFile); obj.app.get(url + 'userfiles/*', handleDownloadUserFiles); obj.app.ws(url + 'echo.ashx', handleEchoWebSocket); obj.app.ws(url + 'apf.ashx', function (ws, req) { obj.parent.mpsserver.onWebSocketConnection(ws, req); })