More work on AMT 802.1x support.

Ylian Saint-Hilaire committed Apr 6, 2022 at 15:41 UTC b910cde272d25b5e029bc685d669360700dc7876
1 file changed +134 -39
amtmanager.js
+134 -39
@@ -438,34 +438,7 @@ module.exports.CreateAmtManager = function (parent) {
438
439 // We got a new 802.1x profile
440 devFound.netAuthCredentials = event.response;
441 - console.log('devFound.netAuthCredentials', devFound.netAuthCredentials);
442 - if (devFound.netAuthCredentials.certificate) {
443 - // The new 802.1x profile includes a new certificate, add it now before adding the 802.1x profiles
444 - // devFound.netAuthCredentials.certificate must be in DER encoded format
445 - devFound.consoleMsg("Setting up new 802.1x certificate...");
446 -
447 - const f = function AddCertificateResponse(stack, name, response, status) {
448 - if ((status != 200) || (response.Body['ReturnValue'] != 0)) {
449 - AddCertificateResponse.dev.consoleMsg("Unable to set 802.1x certificate.");
450 - } else {
451 - console.log('AddCertificate - TODO', response);
452 - // TODO: Keep the certificate reference since we need it to add 802.1x profiles
453 -
454 - // Set the 802.1x wired profile in the device
455 - AddCertificateResponse.dev.consoleMsg("Setting MeshCentral Satellite 802.1x profile...");
456 - const netAuthSatReqData = AddCertificateResponse.dev.netAuthSatReqData;
457 - attempt8021xSyncEx(AddCertificateResponse.dev, netAuthSatReqData);
458 - }
459 - }
460 - f.dev = devFound;
461 - devFound.amtstack.AMT_PublicKeyManagementService_AddCertificate(devFound.netAuthCredentials.certificate, f);
462 - } else {
463 - // No 802.1x certificate, set the 802.1x wired profile in the device
464 - devFound.consoleMsg("Setting MeshCentral Satellite 802.1x profile...");
465 - const netAuthSatReqData = devFound.netAuthSatReqData;
466 - delete devFound.netAuthSatReqData;
467 - attempt8021xSyncEx(devFound, netAuthSatReqData);
468 - }
441 + perform8021xRootCertCheck(devFound);
442 break;
443 }
444 }
@@ -1363,6 +1336,23 @@ module.exports.CreateAmtManager = function (parent) {
1336 // Intel AMT WIFI
1337 //
1338
1339 + // Check which key pair matches the public key in the certificate
1340 + function amtcert_linkCertPrivateKey(certs, keys) {
1341 + for (var i in certs) {
1342 + var cert = certs[i];
1343 + try {
1344 + if (keys.length == 0) return;
1345 + var publicKeyPEM = forge.pki.publicKeyToPem(forge.pki.certificateFromAsn1(forge.asn1.fromDer(cert.X509Certificate)).publicKey).substring(28 + 32).replace(/(\r\n|\n|\r)/gm, "");
1346 + for (var j = 0; j < keys.length; j++) {
1347 + if (publicKeyPEM === (keys[j]['DERKey'] + '-----END PUBLIC KEY-----')) {
1348 + keys[j].XCert = cert; // Link the key pair to the certificate
1349 + cert.XPrivateKey = keys[j]; // Link the certificate to the key pair
1350 + }
1351 + }
1352 + } catch (e) { console.log(e); }
1353 + }
1354 + }
1355 +
1356 // This method will sync the WIFI profiles from the device and the server, but does not care about profile priority.
1357 // We also sync wired 802.1x at the same time since we only allow a single 802.1x profile per device shared between wired and wireless
1358 // We may want to work on an alternate version that does do priority if requested.
@@ -1377,17 +1367,41 @@ module.exports.CreateAmtManager = function (parent) {
1367 dev.taskCount = 1;
1368 dev.taskCompleted = func;
1369
1380 - const objQuery = ['CIM_WiFiEndpointSettings', '*CIM_WiFiPort', '*AMT_WiFiPortConfigurationService', 'CIM_IEEE8021xSettings'];
1370 + const objQuery = ['CIM_WiFiEndpointSettings', '*CIM_WiFiPort', '*AMT_WiFiPortConfigurationService', 'CIM_IEEE8021xSettings', 'AMT_PublicKeyCertificate', 'AMT_PublicPrivateKeyPair'];
1371 if (parent.config.domains[dev.domainid].amtmanager['802.1x'] != null) { objQuery.push('*AMT_8021XProfile'); }
1372 dev.amtstack.BatchEnum(null, objQuery, function (stack, name, responses, status) {
1373 const dev = stack.dev;
1374 if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request.
1375 const domain = parent.config.domains[dev.domainid];
1376 + if ((responses['AMT_PublicKeyCertificate'].status != 200) || (responses['AMT_PublicKeyCertificate'].status != 200)) { devTaskCompleted(dev); return; } // We can't get the certificate list, fail and carry on.
1377
1378 + // See if we need to perform wired or wireless 802.1x configuration
1379 const wiredConfig = ((parent.config.domains[dev.domainid].amtmanager['802.1x'] != null) && (responses['AMT_8021XProfile'].status == 200));
1380 const wirelessConfig = ((responses['CIM_WiFiEndpointSettings'].status == 200) && (responses['AMT_WiFiPortConfigurationService'].status == 200) && (responses['CIM_WiFiPort'].status == 200) && (responses['CIM_IEEE8021xSettings'].status == 200));
1381 if (!wiredConfig && !wirelessConfig) { devTaskCompleted(dev); return; } // We can't get wired or wireless settings, ignore and carry on.
1382
1383 + // Sort out the certificates
1384 + var xxCertificates = responses['AMT_PublicKeyCertificate'].responses;
1385 + var xxCertPrivateKeys = responses['AMT_PublicPrivateKeyPair'].responses;
1386 + for (var i in xxCertificates) {
1387 + xxCertificates[i].TrustedRootCertficate = (xxCertificates[i]['TrustedRootCertficate'] == true);
1388 + xxCertificates[i].X509CertificateBin = Buffer.from(xxCertificates[i]['X509Certificate'], 'base64').toString('binary');
1389 + xxCertificates[i].XIssuer = parseCertName(xxCertificates[i]['Issuer']);
1390 + xxCertificates[i].XSubject = parseCertName(xxCertificates[i]['Subject']);
1391 + }
1392 + amtcert_linkCertPrivateKey(xxCertificates, xxCertPrivateKeys); // This links all certificates and private keys
1393 +
1394 + // Remove any unlinked private keys
1395 + for (var i in xxCertPrivateKeys) {
1396 + if (!xxCertPrivateKeys[i].XCert) {
1397 + console.log('PrivateKey-Removing');
1398 + dev.amtstack.Delete('AMT_PublicPrivateKeyPair', { 'InstanceID': xxCertPrivateKeys[i]['InstanceID'] }, function (stack, name, response, status) {
1399 + console.log('PrivateKey-Removed');
1400 + //if (status == 200) { dev.consoleMsg("Removed unassigned private key pair."); }
1401 + });
1402 + }
1403 + }
1404 +
1405 // Check if wired 802.1x needs updating
1406 var newNetAuthProfileRequested = false;
1407 var srvNetAuthProfile = domain.amtmanager['802.1x'];
@@ -1509,7 +1523,7 @@ module.exports.CreateAmtManager = function (parent) {
1523 // Send a message to Satellite requesting a 802.1x profile for this device
1524 dev.consoleMsg("Requesting 802.1x credentials for " + netAuthStrings[srvNetAuthProfile.authenticationprotocol] + " from MeshCentral Satellite...");
1525 dev.netAuthSatReqId = Buffer.from(parent.crypto.randomBytes(16), 'binary').toString('base64'); // Generate a crypto-secure request id.
1512 - dev.netAuthSatReqData = { domain: domain, wiredConfig: wiredConfig, wirelessConfig: wirelessConfig, devNetAuthProfile: devNetAuthProfile, srvNetAuthProfile: srvNetAuthProfile, profilesToAdd: profilesToAdd, prioritiesInUse: prioritiesInUse, responses: responses }
1526 + dev.netAuthSatReqData = { domain: domain, wiredConfig: wiredConfig, wirelessConfig: wirelessConfig, devNetAuthProfile: devNetAuthProfile, srvNetAuthProfile: srvNetAuthProfile, profilesToAdd: profilesToAdd, prioritiesInUse: prioritiesInUse, responses: responses, xxCertificates: xxCertificates, xxCertPrivateKeys: xxCertPrivateKeys }
1527 parent.DispatchEvent([srvNetAuthProfile.satellitecredentials], obj, { action: 'satellite', subaction: '802.1x-ProFile-Request', satelliteFlags: 2, nodeid: dev.nodeid, icon: dev.icon, domain: dev.nodeid.split('/')[1], nolog: 1, reqid: dev.netAuthSatReqId, authProtocol: srvNetAuthProfile.authenticationprotocol, devname: dev.name, osname: dev.rname });
1528
1529 // Set a response timeout
@@ -1527,12 +1541,81 @@ module.exports.CreateAmtManager = function (parent) {
1541 return;
1542 } else {
1543 // No need to call MeshCentral Satellite for a 802.1x profile, so configure everything now.
1530 - attempt8021xSyncEx(dev, { domain: domain, wiredConfig: wiredConfig, wirelessConfig: wirelessConfig, devNetAuthProfile: devNetAuthProfile, srvNetAuthProfile: srvNetAuthProfile, profilesToAdd: profilesToAdd, prioritiesInUse: prioritiesInUse, responses: responses });
1544 + attempt8021xSyncEx(dev, { domain: domain, wiredConfig: wiredConfig, wirelessConfig: wirelessConfig, devNetAuthProfile: devNetAuthProfile, srvNetAuthProfile: srvNetAuthProfile, profilesToAdd: profilesToAdd, prioritiesInUse: prioritiesInUse, responses: responses, xxCertificates: xxCertificates, xxCertPrivateKeys: xxCertPrivateKeys });
1545 }
1546 }
1547 });
1548 }
1549
1550 +
1551 + // Check 802.1x root certificate
1552 + function perform8021xRootCertCheck(dev) {
1553 + // Check if there is a root certificate to add, if we already have it, get the instance id.
1554 + if (dev.netAuthCredentials.rootcert) {
1555 + var matchingRootCertId = null;
1556 + for (var i in dev.netAuthSatReqData.xxCertificates) {
1557 + if ((dev.netAuthSatReqData.xxCertificates[i].X509Certificate == dev.netAuthCredentials.rootcert) && (dev.netAuthSatReqData.xxCertificates[i].TrustedRootCertficate)) {
1558 + matchingRootCertId = dev.netAuthSatReqData.xxCertificates[i].InstanceID;
1559 + }
1560 + }
1561 + if (matchingRootCertId == null) {
1562 + // Root certificate not found, add it
1563 + dev.consoleMsg("Setting up new 802.1x root certificate...");
1564 + const f = function perform8021xRootCertCheckResponse(stack, name, response, status) {
1565 + if ((status != 200) || (response.Body['ReturnValue'] != 0)) {
1566 + // Failed to add the root certificate
1567 + dev.consoleMsg("Failed to sign the certificate request.");
1568 + } else {
1569 + // Root certificate added, move on to client certificate checking
1570 + perform8021xRootCertCheckResponse.dev.netAuthSatReqData.rootCertInstanceId = response.Body.CreatedCertificate.ReferenceParameters.SelectorSet.Selector.Value;
1571 + perform8021xClientCertCheck(perform8021xRootCertCheckResponse.dev);
1572 + }
1573 + }
1574 + f.dev = dev;
1575 + dev.amtstack.AMT_PublicKeyManagementService_AddTrustedRootCertificate(dev.netAuthCredentials.rootcert, f);
1576 + } else {
1577 + // Root certificate already present, move on to client certificate checking
1578 + dev.netAuthSatReqData.rootCertInstanceId = matchingRootCertId;
1579 + perform8021xClientCertCheck(dev);
1580 + }
1581 + } else {
1582 + // No root certificate to check, move on to client certificate checking
1583 + perform8021xClientCertCheck(dev);
1584 + }
1585 + }
1586 +
1587 + // Check 802.1x client certificate
1588 + function perform8021xClientCertCheck(dev) {
1589 + if (dev.netAuthCredentials.certificate) {
1590 + // The new 802.1x profile includes a new certificate, add it now before adding the 802.1x profiles
1591 + // dev.netAuthCredentials.certificate must be in DER encoded format
1592 + dev.consoleMsg("Setting up new 802.1x client certificate...");
1593 + const f = function AddCertificateResponse(stack, name, response, status) {
1594 + if ((status != 200) || (response.Body['ReturnValue'] != 0)) {
1595 + AddCertificateResponse.dev.consoleMsg("Unable to set 802.1x certificate.");
1596 + } else {
1597 + // Keep the certificate reference since we need it to add 802.1x profiles
1598 + const certInstanceId = response.Body.CreatedCertificate.ReferenceParameters.SelectorSet.Selector.Value;
1599 +
1600 + // Set the 802.1x wired profile in the device
1601 + AddCertificateResponse.dev.consoleMsg("Setting MeshCentral Satellite 802.1x profile...");
1602 + const netAuthSatReqData = AddCertificateResponse.dev.netAuthSatReqData;
1603 + delete dev.netAuthSatReqData;
1604 + netAuthSatReqData.certInstanceId = certInstanceId;
1605 + attempt8021xSyncEx(AddCertificateResponse.dev, netAuthSatReqData);
1606 + }
1607 + }
1608 + f.dev = dev;
1609 + dev.amtstack.AMT_PublicKeyManagementService_AddCertificate(dev.netAuthCredentials.certificate, f);
1610 + } else {
1611 + // No 802.1x certificate, set the 802.1x wired profile in the device
1612 + dev.consoleMsg("Setting MeshCentral Satellite 802.1x profile...");
1613 + const netAuthSatReqData = dev.netAuthSatReqData;
1614 + delete dev.netAuthSatReqData;
1615 + attempt8021xSyncEx(dev, netAuthSatReqData);
1616 + }
1617 + }
1618 +
1619 // Set the 802.1x wired profile
1620 function attempt8021xSyncEx(dev, devNetAuthData) {
1621 // Unpack
@@ -1568,8 +1651,21 @@ module.exports.CreateAmtManager = function (parent) {
1651 delete netAuthProfile['ProtectedAccessCredential'];
1652 delete netAuthProfile['PACPassword'];
1653 }
1571 - //if (parseInt(Q('idx_d27clientcert').value) >= 0) { netAuthProfile['ClientCertificate'] = '<a:Address>/wsman</a:Address><a:ReferenceParameters><w:ResourceURI>' + amtstack.CompleteName('AMT_PublicKeyCertificate') + '</w:ResourceURI><w:SelectorSet><w:Selector Name="InstanceID">' + xxCertificates[parseInt(Q('idx_d27clientcert').value)]['InstanceID'] + '</w:Selector></w:SelectorSet></a:ReferenceParameters>'; } else { delete sc['ClientCertificate']; }
1572 - //if (parseInt(Q('idx_d27servercert').value) >= 0) { netAuthProfile['ServerCertificateIssuer'] = '<a:Address>/wsman</a:Address><a:ReferenceParameters><w:ResourceURI>' + amtstack.CompleteName('AMT_PublicKeyCertificate') + '</w:ResourceURI><w:SelectorSet><w:Selector Name="InstanceID">' + xxCertificates[parseInt(Q('idx_d27servercert').value)]['InstanceID'] + '</w:Selector></w:SelectorSet></a:ReferenceParameters>'; } else { delete sc['ServerCertificateIssuer']; }
1654 +
1655 + // Setup Client Certificate
1656 + if (devNetAuthData.certInstanceId) {
1657 + netAuthProfile['ClientCertificate'] = '<a:Address>/wsman</a:Address><a:ReferenceParameters><w:ResourceURI>' + dev.amtstack.CompleteName('AMT_PublicKeyCertificate') + '</w:ResourceURI><w:SelectorSet><w:Selector Name="InstanceID">' + devNetAuthData.certInstanceId + '</w:Selector></w:SelectorSet></a:ReferenceParameters>';
1658 + } else {
1659 + delete netAuthProfile['ClientCertificate'];
1660 + }
1661 +
1662 + // Setup Server Certificate
1663 + if (devNetAuthData.rootCertInstanceId) {
1664 + netAuthProfile['ServerCertificateIssuer'] = '<a:Address>/wsman</a:Address><a:ReferenceParameters><w:ResourceURI>' + dev.amtstack.CompleteName('AMT_PublicKeyCertificate') + '</w:ResourceURI><w:SelectorSet><w:Selector Name="InstanceID">' + devNetAuthData.rootCertInstanceId + '</w:Selector></w:SelectorSet></a:ReferenceParameters>';
1665 + } else {
1666 + delete netAuthProfile['ServerCertificateIssuer'];
1667 + }
1668 +
1669 netAuthProfile['PxeTimeout'] = (typeof srvNetAuthProfile.pxetimeoutinseconds == 'number') ? srvNetAuthProfile.pxetimeoutinseconds : 120;
1670
1671 // If we have a MeshCentral Satellite profile, use that
@@ -1580,10 +1676,14 @@ module.exports.CreateAmtManager = function (parent) {
1676 if (srvNetAuthProfile2.domain && (srvNetAuthProfile2.domain != '')) { netAuthProfile['Domain'] = srvNetAuthProfile2.domain; }
1677 }
1678 }
1679 +
1680 + console.log('netAuthProfile', netAuthProfile);
1681 +
1682 dev.amtstack.Put('AMT_8021XProfile', netAuthProfile, function (stack, name, responses, status) {
1683 const dev = stack.dev;
1684 if (isAmtDeviceValid(dev) == false) return; // Device no longer exists, ignore this request.
1586 - if (status == 200) { dev.consoleMsg("802.1x wired profile set."); }
1685 + console.log('AMT_8021XProfile-PUT', responses);
1686 + if (status == 200) { dev.consoleMsg("802.1x wired profile set."); } else { dev.consoleMsg("Unable to set 802.1x wired profile."); }
1687 attemptWifiSyncEx(dev, devNetAuthData);
1688 });
1689 } else {
@@ -1706,11 +1806,6 @@ module.exports.CreateAmtManager = function (parent) {
1806 if (xresponse[i]['InstanceID'] == keyInstanceId) {
1807 // We found our matching DER key
1808 DERKey = xresponse[i]['DERKey'];
1709 - } else {
1710 - // This is not a matching key, since we are here, clean it up.
1711 - dev.amtstack.Delete('AMT_PublicPrivateKeyPair', { 'InstanceID': xresponse[i]['InstanceID'] }, function (stack, name, response, status) {
1712 - //if (status == 200) { dev.consoleMsg("Removed unassigned private key pair."); }
1713 - });
1809 }
1810 }
1811 if (DERKey == null) { dev.consoleMsg("Failed to match the generated RSA key pair."); return; }