/**
* @description MeshCentral Intel AMT manager
* @author Ylian Saint-Hilaire
* @copyright Intel Corporation 2018-2021
* @license Apache-2.0
* @version v0.0.1
*/
/*jslint node: true */
/*jshint node: true */
/*jshint strict:false */
/*jshint -W097 */
/*jshint esversion: 6 */
'use strict';
module.exports.CreateAmtManager = function (parent) {
    var obj = {};
    obj.parent = parent;
    obj.amtDevices = {};             // Nodeid --> [ dev ]
    obj.activeLocalConnections = {}; // Host --> dev
    obj.amtAdminAccounts = {};       // DomainId -> [ { user, pass } ]
    obj.rootCertBase64 = obj.parent.certificates.root.cert.split('-----BEGIN CERTIFICATE-----').join('').split('-----END CERTIFICATE-----').join('').split('\r').join('').split('\n').join('')
    obj.rootCertCN = obj.parent.certificateOperations.forge.pki.certificateFromPem(obj.parent.certificates.root.cert).subject.getField('CN').value;
    // WSMAN stack
    const CreateWsmanComm = require('./amt/amt-wsman-comm');
    const WsmanStackCreateService = require('./amt/amt-wsman');
    const AmtStackCreateService = require('./amt/amt');
    const ConnectionTypeStrings = { 0: "CIRA", 1: "Relay", 2: "LMS", 3: "Local" };
    // Check that each domain configuration is correct because we are not going to be checking this later.
    if (parent.config == null) parent.config = {};
    if (parent.config.domains == null) parent.config.domains = {};
    for (var domainid in parent.config.domains) {
        var domain = parent.config.domains[domainid];
        if (typeof domain.amtmanager != 'object') { domain.amtmanager = {}; }
        // Load administrator accounts
        if (Array.isArray(domain.amtmanager.adminaccounts) == true) {
            for (var i = 0; i < domain.amtmanager.adminaccounts.length; i++) {
                var c = domain.amtmanager.adminaccounts[i], c2 = {};
                if (typeof c.user == 'string') { c2.user = c.user; } else { c2.user = 'admin'; }
                if (typeof c.pass == 'string') {
                    c2.pass = c.pass;
                    if (obj.amtAdminAccounts[domainid] == null) { obj.amtAdminAccounts[domainid] = []; }
                    obj.amtAdminAccounts[domainid].push(c2);
                }
            }
        } else {
            delete domain.amtmanager.adminaccounts;
        }
        // Check environment detection
        if (Array.isArray(domain.amtmanager.environmentdetection) == true) {
            var envDetect = [];
            for (var i = 0; i < domain.amtmanager.environmentdetection.length; i++) {
                var x = domain.amtmanager.environmentdetection[i].toLowerCase();
                if ((typeof x == 'string') && (x != '') && (x.length < 64) && (envDetect.indexOf(x) == -1)) { envDetect.push(x); }
                if (envDetect.length >= 4) break; // Maximum of 4 DNS suffix
            }
            if (envDetect.length > 0) { domain.amtmanager.environmentdetection = envDetect; } else { delete domain.amtmanager.environmentdetection; }
        } else {
            delete domain.amtmanager.environmentdetection;
        }
        // Check WIFI profiles
        //var wifiAuthMethod = { 1: "Other", 2: "Open", 3: "Shared Key", 4: "WPA PSK", 5: "WPA 802.1x", 6: "WPA2 PSK", 7: "WPA2 802.1x", 32768: "WPA3 802.1x" };
        //var wifiEncMethod = { 1: "Other", 2: "WEP", 3: "TKIP", 4: "CCMP", 5: "None" }
        if (Array.isArray(domain.amtmanager.wifiprofiles) == true) {
            var goodWifiProfiles = [];
            for (var i = 0; i < domain.amtmanager.wifiprofiles.length; i++) {
                var wifiProfile = domain.amtmanager.wifiprofiles[i];
                if ((typeof wifiProfile.ssid == 'string') && (wifiProfile.ssid != '') && (typeof wifiProfile.password == 'string') && (wifiProfile.password != '')) {
                    if ((wifiProfile.name == null) || (wifiProfile.name == '')) { wifiProfile.name = wifiProfile.ssid; }
                    if (typeof wifiProfile.authentication == 'string') {
                        // Authentication
                        if (typeof wifiProfile.authentication == 'string') { wifiProfile.authentication = wifiProfile.authentication.toLowerCase(); }
                        if (wifiProfile.authentication == 'wpa-psk') { wifiProfile.authentication = 4; }
                        if (wifiProfile.authentication == 'wpa2-psk') { wifiProfile.authentication = 6; }
                        if (typeof wifiProfile.authentication != 'number') { wifiProfile.authentication = 6; } // Default to WPA2-PSK
                        // Encyption
                        if (typeof wifiProfile.encryption == 'string') { wifiProfile.encryption = wifiProfile.encryption.toLowerCase(); }
                        if ((wifiProfile.encryption == 'ccmp-aes') || (wifiProfile.encryption == 'ccmp')) { wifiProfile.encryption = 4; }
                        if ((wifiProfile.encryption == 'tkip-rc4') || (wifiProfile.encryption == 'tkip')) { wifiProfile.encryption = 3; }
                        if (typeof wifiProfile.encryption != 'number') { wifiProfile.encryption = 4; } // Default to CCMP-AES
                        // Type
                        wifiProfile.type = 3; // Infrastructure
                    }
                    goodWifiProfiles.push(wifiProfile);
                }
            }
            domain.amtmanager.wifiprofiles = goodWifiProfiles;
        } else {
            delete domain.amtmanager.wifiprofiles;
        }
    }
    // Check if an Intel AMT device is being managed
    function isAmtDeviceValid(dev) {
        var devices = obj.amtDevices[dev.nodeid];
        if (devices == null) return false;
        return (devices.indexOf(dev) >= 0)
    }
    // Add an Intel AMT managed device
    function addAmtDevice(dev) {
        var devices = obj.amtDevices[dev.nodeid];
        if (devices == null) { obj.amtDevices[dev.nodeid] = [dev]; return true; }
        if (devices.indexOf(dev) >= 0) { return false; } // This device is already in the list
        devices.push(dev); // Add the device to the list
        return true;
    }
    // Remove an Intel AMT managed device
    function removeAmtDevice(dev) {
        parent.debug('amt', dev.name, "Remove device", dev.nodeid, dev.connType);
        // Find the device in the list
        var devices = obj.amtDevices[dev.nodeid];
        if (devices == null) return false;
        var i = devices.indexOf(dev);
        if (i == -1) return false;
        // Remove from task limiter if needed
        if (dev.taskid != null) { obj.parent.taskLimiter.completed(dev.taskid); delete dev.taskLimiter; }
        // Clean up this device
        if (dev.amtstack != null) { dev.amtstack.wsman.comm.FailAllError = 999; delete dev.amtstack; } // Disconnect any active connections.
        if (dev.polltimer != null) { clearInterval(dev.polltimer); delete dev.polltimer; }
        // Remove the device from the list
        devices.splice(i, 1);
        if (devices.length == 0) { delete obj.amtDevices[dev.nodeid]; } else { obj.amtDevices[dev.nodeid] = devices; }
        // Notify connection closure if this is a LMS connection
        if (dev.connType == 2) { dev.controlMsg({ action: 'close' }); }
        return true;
    }
    // Remove all Intel AMT devices for a given nodeid
    function removeDevice(nodeid) {
        parent.debug('amt', "Remove nodeid", nodeid);
        // Find the devices in the list
        var devices = obj.amtDevices[nodeid];
        if (devices == null) return false;
        for (var i in devices) {
            var dev = devices[i];
            // Remove from task limiter if needed
            if (dev.taskid != null) { obj.parent.taskLimiter.completed(dev.taskid); delete dev.taskLimiter; }
            // Clean up this device
            if (dev.amtstack != null) { dev.amtstack.wsman.comm.FailAllError = 999; delete dev.amtstack; } // Disconnect any active connections.
            if (dev.polltimer != null) { clearInterval(dev.polltimer); delete dev.polltimer; }
            // Notify connection closure if this is a LMS connection
            if (dev.connType == 2) { dev.controlMsg({ action: 'close' }); }
        }
        // Remove all Intel AMT management sessions for this nodeid
        delete obj.amtDevices[nodeid];
        return true;
    }
    // Start Intel AMT management
    obj.startAmtManagement = function (nodeid, connType, connection) {
        //if (connType == 3) return; // DEBUG
        var devices = obj.amtDevices[nodeid], dev = null;
        if (devices != null) { for (var i in devices) { if ((devices[i].mpsConnection == connection) || (devices[i].host == connection)) { dev = devices[i]; } } }
        if (dev != null) return false; // We are already managing this device on this connection
        dev = { nodeid: nodeid, connType: connType, domainid: nodeid.split('/')[1] };
        if (typeof connection == 'string') { dev.host = connection; }
        if (typeof connection == 'object') { dev.mpsConnection = connection; }
        dev.consoleMsg = function deviceConsoleMsg(msg) { parent.debug('amt', deviceConsoleMsg.dev.name, msg); if (typeof deviceConsoleMsg.conn == 'object') { deviceConsoleMsg.conn.ControlMsg({ action: 'console', msg: msg }); } }
        dev.consoleMsg.conn = connection;
        dev.consoleMsg.dev = dev;
        dev.controlMsg = function deviceControlMsg(msg) { if (typeof deviceControlMsg.conn == 'object') { deviceControlMsg.conn.ControlMsg(msg); } }
        dev.controlMsg.conn = connection;
        parent.debug('amt', "Start Management", nodeid, connType);
        addAmtDevice(dev);
        // Start the device manager in the task limiter so not to flood the server. Low priority task
        obj.parent.taskLimiter.launch(function (dev, taskid, taskLimiterQueue) {
            if (isAmtDeviceValid(dev)) {
                // Start managing this device
                dev.taskid = taskid;
                fetchIntelAmtInformation(dev);
            } else {
                // Device is not valid anymore, do nothing
                obj.parent.taskLimiter.completed(taskid);
            }
        }, dev, 2);
    }
    // Stop Intel AMT management
    obj.stopAmtManagement = function (nodeid, connType, connection) {
        var devices = obj.amtDevices[nodeid], dev = null;
        if (devices != null) { for (var i in devices) { if ((devices[i].mpsConnection == connection) || (devices[i].host == connection)) { dev = devices[i]; } } }
        if (dev == null) return false; // We are not managing this device on this connection
        parent.debug('amt', dev.name, "Stop Management", nodeid, connType);
        return removeAmtDevice(dev);
    }
    // Get a string status of the managed devices
    obj.getStatusString = function () {
        var r = '';
        for (var nodeid in obj.amtDevices) {
            var devices = obj.amtDevices[nodeid];
            r += devices[0].nodeid + ', ' + devices[0].name + '\r\n';
            for (var i in devices) {
                var dev = devices[i];
                var items = [];
                if (dev.state == 1) { items.push('Connected'); } else { items.push('Trying'); }
                items.push(ConnectionTypeStrings[dev.connType]);
                if (dev.connType == 3) { items.push(dev.host); }
                if (dev.polltimer != null) { items.push('Polling Power'); }
                r += '  ' + items.join(', ') + '\r\n';
            }
        }
        if (r == '') { r = "No managed Intel AMT devices"; }
        return r;
    }
    // Receive a JSON control message from the MPS server
    obj.mpsControlMessage = function (nodeid, conn, connType, jsondata) {
        // Find the devices in the list
        var dev = null;
        var devices = obj.amtDevices[nodeid];
        if (devices == null) return;
        for (var i in devices) { if (devices[i].mpsConnection === conn) { dev = devices[i]; } }
        if (dev == null) return;
        // Process the message
        switch (jsondata.action) {
            case 'deactivate':
                if ((dev.connType != 2) || (dev.deactivateCcmPending != 1)) break; // Only accept MEI state on CIRA-LMS connection
                delete dev.deactivateCcmPending;
                deactivateIntelAmtCCMEx(dev, jsondata.value);
                break;
            case 'meiState':
                if (dev.pendingUpdatedMeiState != 1) break;
                delete dev.pendingUpdatedMeiState;
                attemptInitialContact(dev);
                break;
        }
    }
    // Subscribe to server events
    parent.AddEventDispatch(['*'], obj);
    // Handle server events
    // Make sure to only manage devices with connections to this server. In a multi-server setup, we don't want multiple managers talking to the same device.
    obj.HandleEvent = function (source, event, ids, id) {
        switch (event.action) {
            case 'removenode': { // React to node being removed
                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.
                removeDevice(event.nodeid);
                break;
            }
            case 'wakedevices': { // React to node wakeup command, perform Intel AMT wake if possible
                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) { performPowerAction(event.nodeids[i], 2); } }
                break;
            }
            case 'changenode': { // React to changes in a device
                var devices = obj.amtDevices[event.nodeid], rescan = false;
                if (devices != null) {
                    for (var i in devices) {
                        var dev = devices[i];
                        dev.name = event.node.name;
                        if (event.node.intelamt != null) { dev.intelamt = event.node.intelamt; }
                        if ((dev.connType == 3) && (dev.host != event.node.host)) {
                            dev.host = event.node.host; // The host has changed, if we are connected to this device locally, we need to reset.
                            removeAmtDevice(dev); // We are going to wait for the AMT scanned to find this device again.
                            rescan = true;
                        }
                    }
                } else {
                    // If this event provides a hint that something changed with AMT and we are not managing this device, let's rescan the local network now.
                    if (event.amtchange == 1) { rescan = true; }
                }
                // If there is a significant change to the device AMT settings and this server manages local devices, perform a re-scan of the device now.
                if (rescan && (parent.amtScanner != null)) { parent.amtScanner.performSpecificScan(event.node); }
                break;
            }
            case 'meshchange': {
                // TODO
                break;
            }
        }
    }
    //
    // Intel AMT Connection Setup
    //
    // Update information about a device
    function fetchIntelAmtInformation(dev) {
        parent.db.Get(dev.nodeid, function (err, nodes) {
            if ((nodes == null) || (nodes.length != 1)) { removeAmtDevice(dev); return; }
            const node = nodes[0];
            if ((node.intelamt == null) || (node.meshid == null)) { removeAmtDevice(dev); return; }
            const mesh = parent.webserver.meshes[node.meshid];
            if (mesh == null) { removeAmtDevice(dev); return; }
            if (dev == null) { return; }
            // Fetch Intel AMT setup policy
            // mesh.amt.type: 0 = No Policy, 1 = Deactivate CCM, 2 = Manage in CCM, 3 = Manage in ACM
            // mesh.amt.cirasetup: 0 = No Change, 1 = Remove CIRA, 2 = Setup CIRA
            var amtPolicy = 0, ciraPolicy = 0, badPass = 0, password = null;
            if (mesh.amt != null) {
                if (mesh.amt.type) { amtPolicy = mesh.amt.type; }
                if (mesh.amt.type == 4) {
                    // Fully automatic policy
                    ciraPolicy = 2; // CIRA will be setup
                    badPass = 1; // Automatically re-active CCM
                    password = null; // Randomize the password.
                } else {
                    if (mesh.amt.cirasetup) { ciraPolicy = mesh.amt.cirasetup; }
                    if (mesh.amt.badpass) { badPass = mesh.amt.badpass; }
                    if ((typeof mesh.amt.password == 'string') && (mesh.amt.password != '')) { password = mesh.amt.password; }
                }
            }
            if (amtPolicy == 0) { ciraPolicy = 0; } // If no policy, don't change CIRA state.
            if (amtPolicy == 1) { ciraPolicy = 1; } // If deactivation policy, clear CIRA.
            dev.policy = { amtPolicy: amtPolicy, ciraPolicy: ciraPolicy, badPass: badPass, password: password };
            // Setup the monitored device
            dev.name = node.name;
            dev.meshid = node.meshid;
            dev.intelamt = node.intelamt;
            // Check if the status of Intel AMT sent by the agents matched what we have in the database
            if ((dev.connType == 2) && (dev.mpsConnection != null) && (dev.mpsConnection.tag != null) && (dev.mpsConnection.tag.meiState != null)) {
                dev.aquired = {};
                if ((typeof dev.mpsConnection.tag.meiState.OsHostname == 'string') && (typeof dev.mpsConnection.tag.meiState.OsDnsSuffix == 'string')) {
                    dev.host = dev.aquired.host = dev.mpsConnection.tag.meiState.OsHostname + '.' + dev.mpsConnection.tag.meiState.OsDnsSuffix;
                }
                if (typeof dev.mpsConnection.tag.meiState['ProvisioningState'] == 'number') {
                    dev.intelamt.state = dev.aquired.state = dev.mpsConnection.tag.meiState['ProvisioningState'];
                }
                if (typeof dev.mpsConnection.tag.meiState['Flags'] == 'number') {
                    const flags = dev.intelamt.flags = dev.mpsConnection.tag.meiState['Flags'];
                    if (flags & 2) { dev.aquired.controlMode = 1; } // CCM
                    if (flags & 4) { dev.aquired.controlMode = 2; } // ACM
                }
                UpdateDevice(dev);
            }
            // If there is no Intel AMT policy for this device, stop here.
            //if (amtPolicy == 0) { dev.consoleMsg("Done."); removeAmtDevice(dev); return; }
            // Initiate the communication to Intel AMT
            dev.consoleMsg("Checking Intel AMT state...");
            attemptInitialContact(dev);
        });
    }
    // Attempt to perform initial contact with Intel AMT
    function attemptInitialContact(dev) {
        delete dev.amtstack; // If there is a WSMAn stack setup, clean it up now.
        parent.debug('amt', dev.name, "Attempt Initial Contact", ["CIRA", "CIRA-Relay", "CIRA-LMS", "Local"][dev.connType]);
        // Check Intel AMT policy when CIRA-LMS connection is in use.
        if ((dev.connType == 2) && (dev.mpsConnection != null) && (dev.mpsConnection.tag != null) && (dev.mpsConnection.tag.meiState != null)) {
            // Intel AMT activation policy
            if ((dev.policy.amtPolicy > 1) && (dev.mpsConnection.tag.meiState.ProvisioningState !== 2)) {
                // This Intel AMT device is not activated, we need to work on activating it.
                activateIntelAmt(dev);
                return;
            }
            // Check if we have an ACM activation policy, but the device is in CCM
            if (((dev.policy.amtPolicy == 3) || (dev.policy.amtPolicy == 4)) && ((dev.mpsConnection.tag.meiState.Flags & 2) != 0)) {
                // This device in is CCM, check if we can upgrade to ACM
                if (activateIntelAmt(dev) == false) return; // If this return true, the platform is in CCM and can't go to ACM, keep going with management.
            }
            // Intel AMT CCM deactivation policy
            if (dev.policy.amtPolicy == 1) {
                if ((dev.mpsConnection.tag.meiState.ProvisioningState == 2) && ((dev.mpsConnection.tag.meiState.Flags & 2) != 0)) {
                    // Deactivate CCM.
                    deactivateIntelAmtCCM(dev);
                    return;
                }
            }
        }
        // See if we need to try different credentials
        if ((dev.acctry == null) && ((typeof dev.intelamt.user != 'string') || (typeof dev.intelamt.pass != 'string'))) {
            if ((obj.amtAdminAccounts[dev.domainid] != null) && (obj.amtAdminAccounts[dev.domainid].length > 0)) { dev.acctry = 0; } else { removeAmtDevice(dev); return; }
        }
        switch (dev.connType) {
            case 0: // CIRA
                // Handle the case where the Intel AMT CIRA is connected (connType 0)
                // In this connection type, we look at the port bindings to see if we need to do TLS or not.
                // Check to see if CIRA is connected on this server.
                var ciraconn = dev.mpsConnection;
                if ((ciraconn == null) || (ciraconn.tag == null) || (ciraconn.tag.boundPorts == null)) { removeAmtDevice(dev); return; } // CIRA connection is not on this server, no need to deal with this device anymore.
                // See what user/pass to try.
                var user = null, pass = null;
                if (dev.acctry == null) { user = dev.intelamt.user; pass = dev.intelamt.pass; }
                else if (dev.acctry == 'policy') { user = 'admin'; pass = dev.policy.password; }
                else if (typeof dev.acctry == 'number') { user = obj.amtAdminAccounts[dev.domainid][dev.acctry].user; pass = obj.amtAdminAccounts[dev.domainid][dev.acctry].pass; }
                // See if we need to perform TLS or not. We prefer not to do TLS within CIRA.
                var dotls = -1;
                if (ciraconn.tag.boundPorts.indexOf('16992')) { dotls = 0; }
                else if (ciraconn.tag.boundPorts.indexOf('16993')) { dotls = 1; }
                if (dotls == -1) { removeAmtDevice(dev); return; } // The Intel AMT ports are not open, not a device we can deal with.
                // Connect now
                parent.debug('amt', dev.name, 'CIRA-Connect', (dotls == 1) ? "TLS" : "NoTLS", user, pass);
                var comm;
                if (dotls == 1) {
                    comm = CreateWsmanComm(dev.nodeid, 16993, user, pass, 1, null, ciraconn); // Perform TLS
                    comm.xtlsFingerprint = 0; // Perform no certificate checking
                } else {
                    comm = CreateWsmanComm(dev.nodeid, 16992, user, pass, 0, null, ciraconn); // No TLS
                }
                var wsstack = WsmanStackCreateService(comm);
                dev.amtstack = AmtStackCreateService(wsstack);
                dev.amtstack.dev = dev;
                dev.amtstack.BatchEnum(null, ['*AMT_GeneralSettings', '*IPS_HostBasedSetupService'], attemptLocalConnectResponse);
                break;
            case 1: // CIRA-Relay
            case 2: // CIRA-LMS
                // Handle the case where the Intel AMT relay or LMS is connected (connType 1 or 2)
                // Check to see if CIRA is connected on this server.
                var ciraconn = dev.mpsConnection;
                if ((ciraconn == null) || (ciraconn.tag == null) || (ciraconn.tag.boundPorts == null)) { removeAmtDevice(dev); return; } // Relay connection not valid
                // See what user/pass to try.
                var user = null, pass = null;
                if (dev.acctry == null) { user = dev.intelamt.user; pass = dev.intelamt.pass; }
                else if (dev.acctry == 'policy') { user = 'admin'; pass = dev.policy.password; }
                else if (typeof dev.acctry == 'number') { user = obj.amtAdminAccounts[dev.domainid][dev.acctry].user; pass = obj.amtAdminAccounts[dev.domainid][dev.acctry].pass; }
                // Connect now
                var comm;
                if (dev.tlsfail !== true) {
                    parent.debug('amt', dev.name, (dev.connType == 1) ? 'Relay-Connect' : 'LMS-Connect', "TLS", user, pass);
                    comm = CreateWsmanComm(dev.nodeid, 16993, user, pass, 1, null, ciraconn); // Perform TLS
                    comm.xtlsFingerprint = 0; // Perform no certificate checking
                } else {
                    parent.debug('amt', dev.name, (dev.connType == 1) ? 'Relay-Connect' : 'LMS-Connect', "NoTLS", user, pass);
                    comm = CreateWsmanComm(dev.nodeid, 16992, user, pass, 0, null, ciraconn); // No TLS
                }
                var wsstack = WsmanStackCreateService(comm);
                dev.amtstack = AmtStackCreateService(wsstack);
                dev.amtstack.dev = dev;
                dev.amtstack.BatchEnum(null, ['*AMT_GeneralSettings', '*IPS_HostBasedSetupService'], attemptLocalConnectResponse);
                break;
            case 3: // Local LAN
                // Handle the case where the Intel AMT local scanner found the device (connType 3)
                parent.debug('amt', dev.name, "Attempt Initial Local Contact", dev.connType, dev.host);
                if (typeof dev.host != 'string') { removeAmtDevice(dev); return; } // Local connection not valid
                // Since we don't allow two or more connections to the same host, check if a pending connection is active.
                if (obj.activeLocalConnections[dev.host] != null) {
                    // Active connection, hold and try later.
                    var tryAgainFunc = function tryAgainFunc() { if (obj.amtDevices[tryAgainFunc.dev.nodeid] != null) { attemptInitialContact(tryAgainFunc.dev); } }
                    tryAgainFunc.dev = dev;
                    setTimeout(tryAgainFunc, 5000);
                } else {
                    // No active connections
                    // See what user/pass to try.
                    var user = null, pass = null;
                    if (dev.acctry == null) { user = dev.intelamt.user; pass = dev.intelamt.pass; }
                    else if (dev.acctry == 'policy') { user = 'admin'; pass = dev.policy.password; }
                    else if (typeof dev.acctry == 'number') { user = obj.amtAdminAccounts[dev.domainid][dev.acctry].user; pass = obj.amtAdminAccounts[dev.domainid][dev.acctry].pass; }
                    // Connect now
                    var comm;
                    if (dev.tlsfail !== true) {
                        parent.debug('amt', dev.name, 'Direct-Connect', "TLS", dev.host, user);
                        comm = CreateWsmanComm(dev.host, 16993, user, pass, 1); // Always try with TLS first
                        comm.xtlsFingerprint = 0; // Perform no certificate checking
                    } else {
                        parent.debug('amt', dev.name, 'Direct-Connect', "NoTLS", dev.host, user);
                        comm = CreateWsmanComm(dev.host, 16992, user, pass, 0); // Try without TLS
                    }
                    var wsstack = WsmanStackCreateService(comm);
                    dev.amtstack = AmtStackCreateService(wsstack);
                    dev.amtstack.dev = dev;
                    obj.activeLocalConnections[dev.host] = dev;
                    dev.amtstack.BatchEnum(null, ['*AMT_GeneralSettings', '*IPS_HostBasedSetupService'], attemptLocalConnectResponse);
                }
                break;
        }
    }
    function attemptLocalConnectResponse(stack, name, responses, status) {
        const dev = stack.dev;
        parent.debug('amt', dev.name, "Initial Contact Response", status);
        // If this is a local connection device, release active connection to this host.
        if (dev.connType == 3) { delete obj.activeLocalConnections[dev.host]; }
        // Check if the device still exists
        if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request.
        // Check the response
        if ((status == 200) && (responses['AMT_GeneralSettings'] != null) && (responses['IPS_HostBasedSetupService'] != null) && (responses['IPS_HostBasedSetupService'].response != null) && (responses['IPS_HostBasedSetupService'].response != null) && (stack.wsman.comm.digestRealm == responses['AMT_GeneralSettings'].response.DigestRealm)) {
            // Everything looks good
            dev.consoleMsg(stack.wsman.comm.xtls ? "Intel AMT connected with TLS." : "Intel AMT connected.");
            dev.state = 1;
            if (dev.aquired == null) { dev.aquired = {}; }
            dev.aquired.controlMode = responses['IPS_HostBasedSetupService'].response.CurrentControlMode; // 1 = CCM, 2 = ACM
            if (typeof stack.wsman.comm.amtVersion == 'string') { // Set the Intel AMT version using the HTTP header if present
                var verSplit = stack.wsman.comm.amtVersion.split('.');
                if (verSplit.length >= 3) { dev.aquired.version = verSplit[0] + '.' + verSplit[1] + '.' + verSplit[2]; dev.aquired.majorver = parseInt(verSplit[0]); dev.aquired.minorver = parseInt(verSplit[1]); }
            }
            dev.aquired.realm = stack.wsman.comm.digestRealm;
            dev.aquired.user = dev.intelamt.user = stack.wsman.comm.user;
            dev.aquired.pass = dev.intelamt.pass = stack.wsman.comm.pass;
            dev.aquired.lastContact = Date.now();
            dev.aquired.warn = 0; // Clear all warnings (TODO: Check Realm and TLS cert pinning)
            if ((dev.connType == 1) || (dev.connType == 3)) { dev.aquired.tls = stack.wsman.comm.xtls; } // Only set the TLS state if in relay or local mode. When using CIRA, this is auto-detected.
            if (stack.wsman.comm.xtls == 1) { dev.aquired.hash = stack.wsman.comm.xtlsCertificate.fingerprint.split(':').join('').toLowerCase(); } else { delete dev.aquired.hash; }
            UpdateDevice(dev);
            // Perform Intel AMT clock sync
            attemptSyncClock(dev, function (dev) {
                // Check Intel AMT TLS state
                attemptTlsSync(dev, function (dev) {
                    // If we need to switch to TLS, do it now.
                    if (dev.switchToTls == 1) { delete dev.switchToTls; attemptInitialContact(dev); return; }
                    // Check Intel AMT WIFI state
                    attemptWifiSync(dev, function (dev) {
                        // Check Intel AMT root certificate state
                        attemptRootCertSync(dev, function (dev) {
                            // Check Intel AMT CIRA settings
                            attemptCiraSync(dev, function (dev) {
                                // Check Intel AMT settings
                                attemptSettingsSync(dev, function (dev) {
                                    // See if we need to get hardware inventory
                                    attemptFetchHardwareInventory(dev, function (dev) {
                                        dev.consoleMsg('Done.');
                                        // Remove from task limiter if needed
                                        if (dev.taskid != null) { obj.parent.taskLimiter.completed(dev.taskid); delete dev.taskLimiter; }
                                        if (dev.connType != 2) {
                                            // Start power polling if not connected to LMS
                                            var ppfunc = function powerPoleFunction() { fetchPowerState(powerPoleFunction.dev); }
                                            ppfunc.dev = dev;
                                            dev.polltimer = new setTimeout(ppfunc, 290000); // Poll for power state every 4 minutes 50 seconds.
                                            fetchPowerState(dev);
                                        } else {
                                            // For LMS connections, close now.
                                            dev.controlMsg({ action: 'close' });
                                        }
                                    });
                                });
                            });
                        });
                    });
                });
            });
        } else {
            // We got a bad response
            if ((dev.conntype != 0) && (dev.tlsfail !== true) && (status == 408)) { // If not using CIRA and we get a 408 error while using TLS, try non-TLS.
                // TLS error on a local connection, try again without TLS
                dev.tlsfail = true; attemptInitialContact(dev); return;
            } else if (status == 401) {
                // Authentication error, see if we can use alternative credentials
                if ((dev.acctry == null) && (typeof dev.policy.password == 'string') && (dev.policy.password != '')) { dev.acctry = 'policy'; attemptInitialContact(dev); return; }
                if (((dev.acctry == null) || (dev.acctry == 'policy')) && (obj.amtAdminAccounts[dev.domainid] != null) && (obj.amtAdminAccounts[dev.domainid].length > 0)) { dev.acctry = 0; attemptInitialContact(dev); return; }
                if ((dev.acctry != null) && (obj.amtAdminAccounts[dev.domainid] != null) && (obj.amtAdminAccounts[dev.domainid].length > (dev.acctry + 1))) { dev.acctry++; attemptInitialContact(dev); return; }
                // If this devics is in CCM mode and we have a bad password reset policy, do it now.
                if ((dev.connType == 2) && (dev.policy.badPass == 1) && (dev.mpsConnection != null) && (dev.mpsConnection.tag != null) && (dev.mpsConnection.tag.meiState != null) && (dev.mpsConnection.tag.meiState.Flags != null) && ((dev.mpsConnection.tag.meiState.Flags & 2) != 0)) {
                    deactivateIntelAmtCCM(dev);
                    return;
                }
                // We are unable to authenticate to this device
                dev.consoleMsg("Unable to connect.");
                // Set an error that we can't login to this device
                if (dev.aquired == null) { dev.aquired = {}; }
                dev.aquired.warn = 1; // Intel AMT Warning Flags: 1 = Unknown credentials, 2 = Realm Mismatch, 4 = TLS Cert Mismatch, 8 = Trying credentials
                UpdateDevice(dev);
            }
            //console.log(dev.nodeid, dev.name, dev.host, status, 'Bad response');
            removeAmtDevice(dev);
        }
    }
    //
    // Intel AMT Database Update
    //
    // Change the current core information string and event it
    function UpdateDevice(dev) {
        // Check that the mesh exists
        const mesh = parent.webserver.meshes[dev.meshid];
        if (mesh == null) { removeAmtDevice(dev); return false; }
        // Get the node and change it if needed
        parent.db.Get(dev.nodeid, function (err, nodes) {
            if ((nodes == null) || (nodes.length != 1)) return false;
            const device = nodes[0];
            var changes = [], change = 0, log = 0;
            var domain = parent.config.domains[device.domain];
            if (domain == null) return false;
            // Check if anything changes
            if (device.intelamt == null) { device.intelamt = {}; }
            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'); }
            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'); }
            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'); }
            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'); }
            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'); }
            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'); }
            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'); }
            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'); }
            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'); }
            // Intel AMT Warning Flags: 1 = Unknown credentials, 2 = Realm Mismatch, 4 = TLS Cert Mismatch, 8 = Trying credentials
            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; } }
            // Update Intel AMT flags if needed
            // dev.aquired.controlMode // 1 = CCM, 2 = ACM
            // (node.intelamt.flags & 2) == CCM, (node.intelamt.flags & 4) == ACM
            var flags = 0;
            if (typeof device.intelamt.flags == 'number') { flags = device.intelamt.flags; }
            if (dev.aquired.controlMode == 1) { if ((flags & 4) != 0) { flags -= 4; } if ((flags & 2) == 0) { flags += 2; } } // CCM
            if (dev.aquired.controlMode == 2) { if ((flags & 4) == 0) { flags += 4; } if ((flags & 2) != 0) { flags -= 2; } } // ACM
            if (device.intelamt.flags != flags) { change = 1; log = 1; device.intelamt.flags = flags; changes.push('AMT flags'); }
            // If there are changes, event the new device
            if (change == 1) {
                // Save to the database
                parent.db.Set(device);
                // Event the node change
                var event = { etype: 'node', action: 'changenode', nodeid: device._id, domain: domain.id, node: parent.webserver.CloneSafeNode(device) };
                if (changes.length > 0) { event.msg = 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', '); }
                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
                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.
                parent.DispatchEvent(parent.webserver.CreateMeshDispatchTargets(device.meshid, [device._id]), obj, event);
            }
        });
    }
    // Change the current core information string and event it
    function ClearDeviceCredentials(dev) {
        if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request.
        // Check that the mesh exists
        const mesh = parent.webserver.meshes[dev.meshid];
        if (mesh == null) { removeAmtDevice(dev); return; }
        // Get the node and change it if needed
        parent.db.Get(dev.nodeid, function (err, nodes) {
            if ((nodes == null) || (nodes.length != 1)) return;
            const device = nodes[0];
            var changes = [], change = 0, log = 0;
            var domain = parent.config.domains[device.domain];
            if (domain == null) return;
            // Check if anything changes
            if (device.intelamt == null) return;
            if (device.intelamt.user != null) { change = 1; log = 1; delete device.intelamt.user; changes.push('AMT user'); }
            if (device.intelamt.pass != null) { change = 1; log = 1; delete device.intelamt.pass; changes.push('AMT pass'); }
            // If there are changes, event the new device
            if (change == 1) {
                // Save to the database
                parent.db.Set(device);
                // Event the node change
                var event = { etype: 'node', action: 'changenode', nodeid: device._id, domain: domain.id, node: parent.webserver.CloneSafeNode(device) };
                if (changes.length > 0) { event.msg = 'Changed device ' + device.name + ' from group ' + mesh.name + ': ' + changes.join(', '); }
                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
                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.
                parent.DispatchEvent(parent.webserver.CreateMeshDispatchTargets(device.meshid, [device._id]), obj, event);
            }
        });
    }
    //
    // Intel AMT Power State
    //
    // Get the current power state of a device
    function fetchPowerState(dev) {
        if (isAmtDeviceValid(dev) == false) return;
        // Check if the agent is connected
        var constate = parent.GetConnectivityState(dev.nodeid);
        if ((constate == null) || (constate.connectivity & 1)) return; // If there is no connectivity or the agent is connected, skip trying to poll power state.
        // Fetch the power state
        dev.amtstack.BatchEnum(null, ['CIM_ServiceAvailableToElement'], function (stack, name, responses, status) {
            const dev = stack.dev;
            if (obj.amtDevices[dev.nodeid] == null) return; // Device no longer exists, ignore this response.
            if ((status != 200) || (responses['CIM_ServiceAvailableToElement'] == null) || (responses['CIM_ServiceAvailableToElement'].responses == null) || (responses['CIM_ServiceAvailableToElement'].responses.length < 1)) return; // If the polling fails, just skip it.
            var powerstate = responses['CIM_ServiceAvailableToElement'].responses[0].PowerState;
            if ((powerstate == 2) && (dev.aquired.majorver != null) && (dev.aquired.majorver > 9)) {
                // Device is powered on and Intel AMT 10+, poll the OS power state.
                dev.amtstack.Get('IPS_PowerManagementService', function (stack, name, response, status) {
                    const dev = stack.dev;
                    if (obj.amtDevices[dev.nodeid] == null) return; // Device no longer exists, ignore this response.
                    if (status != 200) return;
                    // Convert the OS power state
                    var meshPowerState = -1;
                    if (response.Body.OSPowerSavingState == 2) { meshPowerState = 1; } // Fully powered (S0);
                    else if (response.Body.OSPowerSavingState == 3) { meshPowerState = 2; } // Modern standby (We are going to call this S1);
                    // Set OS power state
                    if (meshPowerState >= 0) { parent.SetConnectivityState(dev.meshid, dev.nodeid, Date.now(), 4, meshPowerState); }
                });
            } else {
                // Convert the power state
                // AMT power: 1 = Other, 2 = On, 3 = Sleep-Light, 4 = Sleep-Deep, 5 = Power Cycle (Off-Soft), 6 = Off-Hard, 7 = Hibernate (Off-Soft), 8 = Off-Soft, 9 = Power Cycle (Off-Hard), 10 = Master Bus Reset, 11 = Diagnostic Interrupt (NMI), 12 = Off-Soft Graceful, 13 = Off-Hard Graceful, 14 = Master Bus Reset Graceful, 15 = Power Cycle (Off- oft Graceful), 16 = Power Cycle (Off - Hard Graceful), 17 = Diagnostic Interrupt (INIT)
                // Mesh power: 0 = Unknown, 1 = S0 power on, 2 = S1 Sleep, 3 = S2 Sleep, 4 = S3 Sleep, 5 = S4 Hibernate, 6 = S5 Soft-Off, 7 = Present
                var meshPowerState = -1, powerConversionTable = [-1, -1, 1, 2, 3, 6, 6, 5, 6];
                if (powerstate < powerConversionTable.length) { meshPowerState = powerConversionTable[powerstate]; } else { powerstate = 6; }
                // Set power state
                if (meshPowerState >= 0) { parent.SetConnectivityState(dev.meshid, dev.nodeid, Date.now(), 4, meshPowerState); }
            }
        });
    }
    // Perform a power action: 2 = Power up, 5 = Power cycle, 8 = Power down, 10 = Reset
    function performPowerAction(nodeid, action) {
        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 power operation.
            if ((dev.connType != 2) && (dev.state == 1) && (dev.amtstack != null)) {
                try { dev.amtstack.RequestPowerStateChange(action, performPowerActionResponse); } catch (ex) { }
            }
        }
    }
    // Response to Intel AMT power action
    function performPowerActionResponse(stack, name, responses, status) {
        //console.log('performPowerActionResponse', status);
    }
    //
    // Intel AMT Clock Syncronization
    //
    // Attempt to sync the Intel AMT clock if needed, call func back when done.
    // Care should be take not to have many pending WSMAN called when performing clock sync.
    function attemptSyncClock(dev, func) {
        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.
        dev.taskCount = 1;
        dev.taskCompleted = func;
        dev.amtstack.AMT_TimeSynchronizationService_GetLowAccuracyTimeSynch(attemptSyncClockEx);
    }
    // Intel AMT clock query response
    function attemptSyncClockEx(stack, name, response, 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 clock (" + status + ")."); removeAmtDevice(dev); return; }
        // Compute how much drift between Intel AMT and our clock.
        var t = new Date(), now = new Date();
        t.setTime(response.Body['Ta0'] * 1000);
        if (Math.abs(t - now) > 10000) { // If the Intel AMT clock is more than 10 seconds off, set it.
            dev.consoleMsg("Performing clock sync.");
            var Tm1 = Math.round(now.getTime() / 1000);
            dev.amtstack.AMT_TimeSynchronizationService_SetHighAccuracyTimeSynch(response.Body['Ta0'], Tm1, Tm1, attemptSyncClockSet);
        } else {
            // Clock is fine, we are done.
            devTaskCompleted(dev)
        }
    }
    // Intel AMT clock set response
    function attemptSyncClockSet(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 sync clock (" + status + ")."); removeAmtDevice(dev); }
        devTaskCompleted(dev)
    }
    //
    // Intel AMT TLS setup
    //
    // Check if Intel AMT TLS state is correct
    function attemptTlsSync(dev, func) {
        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.
        dev.taskCount = 1;
        dev.taskCompleted = func;
        // TODO: We only deal with certificates starting with Intel AMT 6 and beyond
        dev.amtstack.BatchEnum(null, ['AMT_PublicKeyCertificate', 'AMT_PublicPrivateKeyPair', 'AMT_TLSSettingData', 'AMT_TLSCredentialContext'], attemptTlsSyncEx);
    }
    function attemptTlsSyncEx(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); return; }
        // Setup the certificates
        dev.policy.certPrivateKeys = responses['AMT_PublicPrivateKeyPair'].responses;
        dev.policy.tlsSettings = responses['AMT_TLSSettingData'].responses;
        dev.policy.tlsCredentialContext = responses['AMT_TLSCredentialContext'].responses;
        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']);
        }
        amtcert_linkCertPrivateKey(xxCertificates, dev.policy.certPrivateKeys);
        dev.policy.certificates = xxCertificates;
        // Find the current TLS certificate & MeshCentral root certificate
        var xxTlsCurrentCert = null;
        if (dev.policy.tlsCredentialContext.length > 0) {
            var certInstanceId = dev.policy.tlsCredentialContext[0]['ElementInContext']['ReferenceParameters']['SelectorSet']['Selector']['Value'];
            for (var i in dev.policy.certificates) { if (dev.policy.certificates[i]['InstanceID'] == certInstanceId) { xxTlsCurrentCert = i; } }
        }
        // This is a managed device and TLS is not enabled, turn it on.
        if (xxTlsCurrentCert == null) {
            // Start by generating a key pair
            dev.amtstack.AMT_PublicKeyManagementService_GenerateKeyPair(0, 2048, function (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 generate a key pair (" + status + ")."); removeAmtDevice(dev); return; }
                // Check that we get a key pair reference
                var x = null;
                try { x = responses.Body['KeyPair']['ReferenceParameters']['SelectorSet']['Selector']['Value']; } catch (ex) { }
                if (x == null) { dev.consoleMsg("Unable to get key pair reference."); removeAmtDevice(dev); return; }
                // Get the new key pair
                dev.amtstack.Enum('AMT_PublicPrivateKeyPair', function (stack, name, responses, status, tag) {
                    const dev = stack.dev;
                    if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request.
                    if (status != 200) { dev.consoleMsg("Failed to get a key pair list (" + status + ")."); removeAmtDevice(dev); return; }
                    // Get the new DER key
                    var DERKey = null;
                    for (var i in responses) { if (responses[i]['InstanceID'] == tag) { DERKey = responses[i]['DERKey']; } }
                    // Get certificate values
                    const commonName = 'IntelAMT-' + Buffer.from(parent.crypto.randomBytes(6), 'binary').toString('hex');
                    const domain = parent.config.domains[dev.domainid];
                    var serverName = 'MeshCentral';
                    if ((domain != null) && (domain.title != null)) { serverName = domain.title; }
                    const certattributes = { 'CN': commonName, 'O': serverName, 'ST': 'MC', 'C': 'MC' };
                    // See what root certificate to use to sign the TLS cert
                    var xxCaPrivateKey = obj.parent.certificates.root.key; // Use our own root by default
                    var issuerattributes = { 'CN': obj.rootCertCN };
                    if (domain.amtmanager.tlsrootcert2 != null) {
                        xxCaPrivateKey = domain.amtmanager.tlsrootcert2.key;
                        issuerattributes = domain.amtmanager.tlsrootcert2.attributes;
                        // TODO: We should change the start and end dates of our issued certificate to at least match the root.
                        // TODO: We could do one better and auto-renew TLS certificates as needed.
                    }
                    // Set the extended key usages
                    var extKeyUsage = { name: 'extKeyUsage', serverAuth: true, clientAuth: true }
                    // Sign the key pair using the CA certifiate
                    const cert = amtcert_createCertificate(certattributes, xxCaPrivateKey, DERKey, issuerattributes, extKeyUsage);
                    if (cert == null) { dev.consoleMsg("Failed to sign the TLS certificate."); removeAmtDevice(dev); return; }
                    // Place the resulting signed certificate back into AMT
                    var pem = obj.parent.certificateOperations.forge.pki.certificateToPem(cert).replace(/(\r\n|\n|\r)/gm, '');
                    // Set the certificate finderprint (SHA1)
                    var md = obj.parent.certificateOperations.forge.md.sha1.create();
                    md.update(obj.parent.certificateOperations.forge.asn1.toDer(obj.parent.certificateOperations.forge.pki.certificateToAsn1(cert)).getBytes());
                    dev.aquired.xhash = md.digest().toHex();
                    dev.amtstack.AMT_PublicKeyManagementService_AddCertificate(pem.substring(27, pem.length - 25), function (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 add TLS certificate (" + status + ")."); removeAmtDevice(dev); return; }
                        var certInstanceId = null;
                        try { certInstanceId = responses.Body['CreatedCertificate']['ReferenceParameters']['SelectorSet']['Selector']['Value']; } catch (ex) { }
                        if (certInstanceId == null) { dev.consoleMsg("Failed to get TLS certificate identifier."); removeAmtDevice(dev); return; }
                        // Set the TLS certificate
                        dev.setTlsSecurityPendingCalls = 3;
                        if (dev.policy.tlsCredentialContext.length > 0) {
                            // Modify the current context
                            var newTLSCredentialContext = Clone(dev.policy.tlsCredentialContext[0]);
                            newTLSCredentialContext['ElementInContext']['ReferenceParameters']['SelectorSet']['Selector']['Value'] = certInstanceId;
                            dev.amtstack.Put('AMT_TLSCredentialContext', newTLSCredentialContext, amtSwitchToTls, 0, 1);
                        } else {
                            // Add a new security context
                            dev.amtstack.Create('AMT_TLSCredentialContext', {
                                'ElementInContext': '