Started work on adding FIDO2 support.

This commit is contained in:
Ylian Saint-Hilaire 2019-03-22 22:33:53 -07:00
parent 7687c62100
commit 06f2911ddc
7 changed files with 140 additions and 9 deletions

View File

@ -1978,6 +1978,60 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
}); });
break; break;
} }
case 'webauthn-startregister':
{
// Check is 2-step login is supported
const twoStepLoginSupported = ((domain.auth != 'sspi') && (parent.parent.certificates.CommonName.indexOf('.') != -1) && (args.lanonly !== true) && (args.nousers !== true));
if ((twoStepLoginSupported == false) || (command.name == null) || (parent.f2l == null)) break;
parent.f2l.attestationOptions().then(function (registrationOptions) {
// Convert the challenge to base64 and add user information
registrationOptions.challenge = Buffer(registrationOptions.challenge).toString('base64');
registrationOptions.user.id = Buffer(parent.crypto.randomBytes(16)).toString('base64');
registrationOptions.user.name = user._id;
registrationOptions.user.displayName = user._id.split('/')[2];
// Send the registration request
obj.webAuthnReqistrationRequest = { action: 'webauthn-startregister', keyname: command.name, request: registrationOptions };
ws.send(JSON.stringify(obj.webAuthnReqistrationRequest));
//console.log(obj.webAuthnReqistrationRequest);
}, function (error) {
console.log('webauthn-startregister-error', error);
});
break;
}
case 'webauthn-endregister':
{
if ((obj.webAuthnReqistrationRequest == null) || (parent.f2l == null)) return;
var attestationExpectations = {
challenge: obj.webAuthnReqistrationRequest.request.challenge.split('+').join('-').split('/').join('_').split('=').join(''), // Convert to Base64URL
origin: "https://devbox.mesh.meshcentral.com",
factor: "either"
};
var clientAttestationResponse = command.response;
clientAttestationResponse.id = clientAttestationResponse.rawId;
clientAttestationResponse.rawId = new Uint8Array(Buffer.from(clientAttestationResponse.rawId, 'base64')).buffer;
clientAttestationResponse.response.attestationObject = new Uint8Array(Buffer.from(clientAttestationResponse.response.attestationObject, 'base64')).buffer;
clientAttestationResponse.response.clientDataJSON = new Uint8Array(Buffer.from(clientAttestationResponse.response.clientDataJSON, 'base64')).buffer;
parent.f2l.attestationResult(clientAttestationResponse, attestationExpectations).then(function (regResult) {
var keyIndex = parent.crypto.randomBytes(4).readUInt32BE(0);
if (user.otphkeys == null) { user.otphkeys = []; }
user.otphkeys.push({ name: obj.webAuthnReqistrationRequest.keyname, type: 3, publicKey: regResult.authnrData.get('credentialPublicKeyPem'), counter: regResult.authnrData.get('counter'), keyIndex: keyIndex, keyId: clientAttestationResponse.id });
parent.db.SetUser(user);
ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: true, name: command.name, index: keyIndex }));
// Notify change
parent.parent.DispatchEvent(['*', 'server-users', user._id], obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added security key.', domain: domain.id });
}, function (error) {
console.log('webauthn-endregister-error', error);
ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: false, error: error, name: command.name, index: keyIndex }));
});
delete obj.hardwareKeyRegistrationRequest;
break;
}
case 'getClip': { case 'getClip': {
if (common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid if (common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid

View File

@ -1,6 +1,6 @@
{ {
"name": "meshcentral", "name": "meshcentral",
"version": "0.3.0-t", "version": "0.3.0-u",
"keywords": [ "keywords": [
"Remote Management", "Remote Management",
"Intel AMT", "Intel AMT",

Binary file not shown.

After

Width:  |  Height:  |  Size: 976 B

View File

@ -3,13 +3,11 @@ MeshCentral
For more information, [visit MeshCommander.com/MeshCentral2](http://www.meshcommander.com/meshcentral2). For more information, [visit MeshCommander.com/MeshCentral2](http://www.meshcommander.com/meshcentral2).
Download the [full PDF user's guide](http://info.meshcentral.com/downloads/meshcentral2/MeshCentral2UserGuide.pdf) with more information on configuring and running MeshCentral2. In addition, the [installation guide](http://info.meshcentral.com/downloads/meshcentral2/MeshCentral2InstallGuide.pdf) can help get MeshCentral installed on Amazon AWS, Microsoft Azure, Ubuntu and the Raspberry Pi. Download the [full PDF user's guide](http://info.meshcentral.com/downloads/MeshCentral2/MeshCentral2UserGuide.pdf) with more information on configuring and running MeshCentral2. In addition, the [installation guide](http://info.meshcentral.com/downloads/MeshCentral2/MeshCentral2InstallGuide.pdf) can help get MeshCentral installed on Amazon AWS, Microsoft Azure, Ubuntu and the Raspberry Pi.
This is a full computer management web site. With MeshCentral, you can run your own web server to remotely manage and control computers on a local network or anywhere on the internet. Once you get the server started, create a mesh (a group of computers) and then download and install a mesh agent on each computer you want to manage. A minute later, the new computer will show up on the web site and you can take control of it. MeshCentral includes full web-based remote desktop, terminal and file management capability. This is a full computer management web site. With MeshCentral, you can run your own web server to remotely manage and control computers on a local network or anywhere on the internet. Once you get the server started, create a mesh (a group of computers) and then download and install a mesh agent on each computer you want to manage. A minute later, the new computer will show up on the web site and you can take control of it. MeshCentral includes full web-based remote desktop, terminal and file management capability.
This version of MeshCentral that is completely rebuild of the original MeshCentral coded in C#. It's simpler and includes many other design improvements over the original. At some point in the future, [MeshCentral.com](http://meshcentral.com) that is still running the older code will switch to using this code base. To test this server, feel free to try [MeshCentral.com](http://meshcentral.com).
This version is BETA and should not be used in production.
Installation Installation
@ -37,7 +35,7 @@ To run MeshCentral you may need to use "nodejs" instead of "node" on Linux.
node meshcentral [arguments] node meshcentral [arguments]
``` ```
One of the first things you will want to do is set a server name or IP address. This will be used by mesh agents to connect back to the server. So, make sure you set **a name that will resolve back to your server**. MeshCentral will not register this name for you. You must make sure to setup the DNS name yourself first, or use the right IP address. If you are just taking a quick look at MeshCentral, you can skip this step and do it at later time. You can launch MeshCentral with no arguments to start it in LAN mode. In LAN mode only devices on the local network can be managed. To setup a more seciour server, use --cert to specify an IP address or name that resolves to your server. This name will be used by mesh agents to connect back to the server. So, make sure you set **a name that will resolve back to your server**. MeshCentral will not register this name for you. You must make sure to setup the DNS name yourself first, or use the right IP address. If you are just taking a quick look at MeshCentral, you can skip this step and do it at later time.
``` ```
node meshcentral --cert servername.domain.com node meshcentral --cert servername.domain.com

View File

@ -1486,7 +1486,7 @@
x += "<div style='max-height:150px;overflow-y:auto;overflow-x:hidden;margin-top:6px;margin-bottom:6px'>"; x += "<div style='max-height:150px;overflow-y:auto;overflow-x:hidden;margin-top:6px;margin-bottom:6px'>";
if (message.keys && message.keys.length > 0) { if (message.keys && message.keys.length > 0) {
for (var i in message.keys) { for (var i in message.keys) {
var key = message.keys[i], type = (key.type == 1)?'U2F':'OTP'; var key = message.keys[i], type = ((key.type == 1)?'U2F':(key.type == 2)?'OTP':'WebAuthn');
x += start + '<tr style=margin:5px><td style=width:30px><img width=24 height=18 src="images/hardware-key-' + type + '-24.png" style=margin-top:4px><td style=width:250px>' + key.name + "<td><input type=button value='Remove' onclick=account_removehkey(" + key.i + ")></input>" + end; x += start + '<tr style=margin:5px><td style=width:30px><img width=24 height=18 src="images/hardware-key-' + type + '-24.png" style=margin-top:4px><td style=width:250px>' + key.name + "<td><input type=button value='Remove' onclick=account_removehkey(" + key.i + ")></input>" + end;
} }
} else { } else {
@ -1496,6 +1496,7 @@
x += "<div><input type=button value='Close' onclick=setDialogMode(0) style=float:right></input>"; x += "<div><input type=button value='Close' onclick=setDialogMode(0) style=float:right></input>";
x += "<input id=d2addkey1 type=button value='Add U2F Key' onclick='account_addhkey(1);'></input>"; x += "<input id=d2addkey1 type=button value='Add U2F Key' onclick='account_addhkey(1);'></input>";
if ((features & 0x4000) != 0) { x += "<input id=d2addkey2 type=button value='Add OTP Key' onclick='account_addhkey(2);'></input>"; } if ((features & 0x4000) != 0) { x += "<input id=d2addkey2 type=button value='Add OTP Key' onclick='account_addhkey(2);'></input>"; }
x += "<input id=d2addkey3 type=button value='Add FIDO2 Key' onclick='account_addhkey(3);'></input>";
x += "</div><br />"; x += "</div><br />";
setDialogMode(2, "Manage Security Keys", 8, null, x, 'otpauth-hardware-manage'); setDialogMode(2, "Manage Security Keys", 8, null, x, 'otpauth-hardware-manage');
if (u2fSupported() == false) { QE('d2addkey1', false); } if (u2fSupported() == false) { QE('d2addkey1', false); }
@ -1533,6 +1534,26 @@
} }
break; break;
} }
case 'webauthn-startregister': {
if (xxdialogMode && (xxdialogTag != 'otpauth-hardware-manage')) return;
var x = "Press the key button now.<br /><br /><div style=width:100%;text-align:center><img width=120 height=117 src='images/hardware-keypress-120.png' /></div><input id=dp1keyname style=display:none value=" + message.name + " />";
setDialogMode(2, "Add Security Key", 2, null, x);
var publicKey = message.request;
message.request.challenge = Uint8Array.from(atob(message.request.challenge), c => c.charCodeAt(0))
message.request.user.id = Uint8Array.from(atob(message.request.user.id), c => c.charCodeAt(0))
navigator.credentials.create({ publicKey })
.then((newCredentialInfo) => {
// Public key credential
var r = { rawId: btoa(String.fromCharCode.apply(null, new Uint8Array(newCredentialInfo.rawId))), response: { attestationObject: btoa(String.fromCharCode.apply(null, new Uint8Array(newCredentialInfo.response.attestationObject))), clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(newCredentialInfo.response.clientDataJSON))) }, type: newCredentialInfo.type };
meshserver.send({ action: 'webauthn-endregister', response: r });
setDialogMode(0);
}).catch((error) => {
// Error
setDialogMode(2, "Add Security Key", 1, null, "ERROR: " + error);
});
break;
}
case 'event': { case 'event': {
if (!message.event.nolog) { if (!message.event.nolog) {
events.unshift(message.event); events.unshift(message.event);
@ -5611,7 +5632,7 @@
} }
function account_addhkey(type) { function account_addhkey(type) {
if (type == 1) { if (type == 1 || type == 3) {
var x = "Type in the name of the key to add.<br /><br />"; var x = "Type in the name of the key to add.<br /><br />";
x += addHtmlValue('Key Name', '<input id=dp1keyname style=width:230px maxlength=20 autocomplete=off placeholder="MyKey" onkeyup=account_addhkeyValidate(event,2) />'); x += addHtmlValue('Key Name', '<input id=dp1keyname style=width:230px maxlength=20 autocomplete=off placeholder="MyKey" onkeyup=account_addhkeyValidate(event,2) />');
} else if (type == 2) { } else if (type == 2) {
@ -5635,6 +5656,8 @@
} else if (type == 2) { } else if (type == 2) {
meshserver.send({ action: 'otp-hkey-yubikey-add', name: name, otp: Q('dp1key').value }); meshserver.send({ action: 'otp-hkey-yubikey-add', name: name, otp: Q('dp1key').value });
setDialogMode(2, "Add Security Key", 0, null, "<br />Checking...<br /><br /><br />", 'otpauth-hardware-manage'); setDialogMode(2, "Add Security Key", 0, null, "<br />Checking...<br /><br /><br />", 'otpauth-hardware-manage');
} else if (type == 3) {
meshserver.send({ action: 'webauthn-startregister', name: name });
} }
} }

View File

@ -372,7 +372,37 @@
if ('{{loginmode}}' == '4') { if ('{{loginmode}}' == '4') {
try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null } try { if (hardwareKeyChallenge.length > 0) { hardwareKeyChallenge = JSON.parse(hardwareKeyChallenge); } else { hardwareKeyChallenge = null; } } catch (ex) { hardwareKeyChallenge = null }
if ((hardwareKeyChallenge != null) && u2fSupported()) { if ((hardwareKeyChallenge != null) && (hardwareKeyChallenge.type == 'webAuthn')) {
hardwareKeyChallenge.challenge = Uint8Array.from(atob(hardwareKeyChallenge.challenge), c => c.charCodeAt(0)).buffer;
const publicKeyCredentialRequestOptions = { challenge: hardwareKeyChallenge.challenge, allowCredentials: [], timeout: hardwareKeyChallenge.timeout }
for (var i = 0; i < hardwareKeyChallenge.keyIds.length; i++) {
publicKeyCredentialRequestOptions.allowCredentials.push(
{ id: Uint8Array.from(atob(hardwareKeyChallenge.keyIds[i]), c => c.charCodeAt(0)), type: 'public-key', transports: ['usb', 'ble', 'nfc'], }
);
}
// New WebAuthn hardware keys
navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
function (rawAssertion) {
console.log(rawAssertion);
/*
var assertion = {
id: base64encode(rawAssertion.rawId),
clientDataJSON: arrayBufferToString(rawAssertion.response.clientDataJSON),
userHandle: base64encode(rawAssertion.response.userHandle),
signature: base64encode(rawAssertion.response.signature),
authenticatorData: base64encode(rawAssertion.response.authenticatorData)
};
console.log(assertion);
*/
},
function (error) {
console.log('credentials-get error', error);
}
);
} else if ((hardwareKeyChallenge != null) && u2fSupported()) {
// Old U2F hardware keys
window.u2f.sign(hardwareKeyChallenge.appId, hardwareKeyChallenge.challenge, hardwareKeyChallenge.registeredKeys, function (authResponse) { window.u2f.sign(hardwareKeyChallenge.appId, hardwareKeyChallenge.challenge, hardwareKeyChallenge.registeredKeys, function (authResponse) {
if ((currentpanel == 4) && authResponse.signatureData) { if ((currentpanel == 4) && authResponse.signatureData) {
Q('hwtokenInput').value = JSON.stringify(authResponse); Q('hwtokenInput').value = JSON.stringify(authResponse);

View File

@ -61,6 +61,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
obj.interceptor = require('./interceptor'); obj.interceptor = require('./interceptor');
const constants = (obj.crypto.constants ? obj.crypto.constants : require('constants')); // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead. const constants = (obj.crypto.constants ? obj.crypto.constants : require('constants')); // require('constants') is deprecated in Node 11.10, use require('crypto').constants instead.
// Setup WebAuthn, this is an optional install.
// "npm install @davedoesdev/fido2-lib"
try {
const { Fido2Lib } = require("@davedoesdev/fido2-lib");
obj.f2l = new Fido2Lib({ attestation: "none" });
} catch (ex) { console.log(ex); }
// Variables // Variables
obj.parent = parent; obj.parent = parent;
obj.filespath = parent.filespath; obj.filespath = parent.filespath;
@ -385,6 +392,25 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
function getHardwareKeyChallenge(req, domain, user, func) { function getHardwareKeyChallenge(req, domain, user, func) {
if (req.session.u2fchallenge) { delete req.session.u2fchallenge; }; if (req.session.u2fchallenge) { delete req.session.u2fchallenge; };
if (user.otphkeys && (user.otphkeys.length > 0)) { if (user.otphkeys && (user.otphkeys.length > 0)) {
// Get all WebAuthn keys
if (obj.f2l != null) {
var webAuthnKeys = [];
for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type == 3) { webAuthnKeys.push(user.otphkeys[i]); } }
if (webAuthnKeys.length > 0) {
obj.f2l.assertionOptions().then(function (authnOptions) {
authnOptions.type = 'webAuthn';
authnOptions.keyIds = [];
for (var i = 0; i < webAuthnKeys.length; i++) { authnOptions.keyIds.push(webAuthnKeys[0].keyId); }
req.session.u2fchallenge = authnOptions.challenge = Buffer(authnOptions.challenge).toString('base64');
func(JSON.stringify(authnOptions));
}, function (error) {
console.log('assertionOptions-Error', error);
func('');
});
return;
}
}
// Get all U2F keys // Get all U2F keys
var u2fKeys = []; var u2fKeys = [];
for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type == 1) { u2fKeys.push(user.otphkeys[i]); } } for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type == 1) { u2fKeys.push(user.otphkeys[i]); } }