mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2024-12-22 13:21:31 +03:00
Started work on adding FIDO2 support.
This commit is contained in:
parent
1f77f70155
commit
2b81e12438
54
meshuser.js
54
meshuser.js
@ -1978,6 +1978,60 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
|
||||
});
|
||||
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': {
|
||||
if (common.validateString(command.nodeid, 1, 1024) == false) break; // Check nodeid
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "meshcentral",
|
||||
"version": "0.3.0-t",
|
||||
"version": "0.3.0-u",
|
||||
"keywords": [
|
||||
"Remote Management",
|
||||
"Intel AMT",
|
||||
|
BIN
public/images/hardware-key-WebAuthn-24.png
Normal file
BIN
public/images/hardware-key-WebAuthn-24.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 976 B |
@ -3,13 +3,11 @@ MeshCentral
|
||||
|
||||
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 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.
|
||||
|
||||
This version is BETA and should not be used in production.
|
||||
To test this server, feel free to try [MeshCentral.com](http://meshcentral.com).
|
||||
|
||||
|
||||
Installation
|
||||
@ -37,7 +35,7 @@ To run MeshCentral you may need to use "nodejs" instead of "node" on Linux.
|
||||
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
|
||||
|
@ -1486,7 +1486,7 @@
|
||||
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) {
|
||||
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;
|
||||
}
|
||||
} else {
|
||||
@ -1496,6 +1496,7 @@
|
||||
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>";
|
||||
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 />";
|
||||
setDialogMode(2, "Manage Security Keys", 8, null, x, 'otpauth-hardware-manage');
|
||||
if (u2fSupported() == false) { QE('d2addkey1', false); }
|
||||
@ -1533,6 +1534,26 @@
|
||||
}
|
||||
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': {
|
||||
if (!message.event.nolog) {
|
||||
events.unshift(message.event);
|
||||
@ -5611,7 +5632,7 @@
|
||||
}
|
||||
|
||||
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 />";
|
||||
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) {
|
||||
@ -5635,6 +5656,8 @@
|
||||
} else if (type == 2) {
|
||||
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');
|
||||
} else if (type == 3) {
|
||||
meshserver.send({ action: 'webauthn-startregister', name: name });
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -372,7 +372,37 @@
|
||||
|
||||
if ('{{loginmode}}' == '4') {
|
||||
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) {
|
||||
if ((currentpanel == 4) && authResponse.signatureData) {
|
||||
Q('hwtokenInput').value = JSON.stringify(authResponse);
|
||||
|
26
webserver.js
26
webserver.js
@ -61,6 +61,13 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
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.
|
||||
|
||||
// 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
|
||||
obj.parent = parent;
|
||||
obj.filespath = parent.filespath;
|
||||
@ -385,6 +392,25 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
|
||||
function getHardwareKeyChallenge(req, domain, user, func) {
|
||||
if (req.session.u2fchallenge) { delete req.session.u2fchallenge; };
|
||||
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
|
||||
var u2fKeys = [];
|
||||
for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type == 1) { u2fKeys.push(user.otphkeys[i]); } }
|
||||
|
Loading…
Reference in New Issue
Block a user