From f1e9d83cc95a9c8b78a0994d3556a276f30de2e4 Mon Sep 17 00:00:00 2001 From: Ylian Saint-Hilaire Date: Sun, 10 Feb 2019 16:04:36 -0800 Subject: [PATCH] Improved U2F authentication, added multiple U2F key support. --- meshuser.js | 54 +++++++---- package.json | 4 +- sample-config.json | 3 +- views/default-min.handlebars | 2 +- views/default-mobile-min.handlebars | 2 +- views/default-mobile.handlebars | 36 ++++---- views/default.handlebars | 81 ++++++++-------- views/login-min.handlebars | 2 +- views/login-mobile-min.handlebars | 2 +- views/login-mobile.handlebars | 11 +-- views/login.handlebars | 11 +-- webserver.js | 138 +++++++++++++++------------- 12 files changed, 191 insertions(+), 155 deletions(-) diff --git a/meshuser.js b/meshuser.js index 318e9b16..10d8fbcc 100644 --- a/meshuser.js +++ b/meshuser.js @@ -445,12 +445,19 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use break; } case 'showconfig': { + // Make a copy of the configuration and hide any secrets var config = obj.common.Clone(obj.parent.parent.config); if (config.settings) { if (config.settings.configkey) { config.settings.configkey = '(present)'; } if (config.settings.sessionkey) { config.settings.sessionkey = '(present)'; } if (config.settings.dbencryptkey) { config.settings.dbencryptkey = '(present)'; } } + if (config.domains) { + for (var i in config.domains) { + if (config.domains[i].yubikey && config.domains[i].yubikey.secret) { config.domains[i].yubikey.secret = '(present)'; } + } + } + r = JSON.stringify(removeAllUnderScore(config), null, 4); break; } @@ -1433,7 +1440,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (twoStepLoginSupported) { // Perform the one time password setup const otplib = require('otplib'); - otplib.authenticator.options = { window: 6 }; // Set +/- 3 minute window + otplib.authenticator.options = { window: 2 }; // Set +/- 1 minute window if (otplib.authenticator.check(command.token, command.secret) === true) { // Token is valid, activate 2-step login on this account. user.otpsecret = command.secret; @@ -1493,6 +1500,9 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (user.otpsecret || ((user.otphkeys != null) && (user.otphkeys.length > 0))) { ws.send(JSON.stringify({ action: 'otpauth-getpasswords', passwords: user.otpkeys ? user.otpkeys.keys : null })); } + + // Notify change TODO: Should be done on all sessions/servers for this user. + try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { } break; } case 'otp-hkey-get': @@ -1573,35 +1583,43 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use if (twoStepLoginSupported == false) break; // Build list of known keys - //var knownKeys = []; - //if (user.otphkeys != null) { for (var i = 0; i < user.otphkeys.length; i++) { knownKeys.push(user.otphkeys[i].keyHandle); } } + var knownKeys = []; + if (user.otphkeys != null) { for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type == 1) { knownKeys.push(user.otphkeys[i]); } } } // Build a key registration request and send it over - const registrationRequest = require('u2f').request('https://' + obj.parent.parent.certificates.CommonName); - if (registrationRequest) { ws.send(JSON.stringify({ action: 'otp-hkey-setup-request', request: registrationRequest, name: command.name })); } + require('authdog').startRegistration('https://' + obj.parent.parent.certificates.CommonName, knownKeys, { requestId: 556, timeoutSeconds: 100 }).then(function (registrationRequest) { + // Save registration request to session for later use + obj.hardwareKeyRegistrationRequest = registrationRequest; + + // Send registration request to client + ws.send(JSON.stringify({ action: 'otp-hkey-setup-request', request: registrationRequest, name: command.name })); + }, function (error) { + // Handle registration request error + ws.send(JSON.stringify({ action: 'otp-hkey-setup-request', request: null, error: error, name: command.name })); + }); break; } case 'otp-hkey-setup-response': { // Check is 2-step login is supported const twoStepLoginSupported = ((domain.auth != 'sspi') && (obj.parent.parent.certificates.CommonName != 'un-configured') && (obj.args.lanonly !== true) && (obj.args.nousers !== true)); - if ((twoStepLoginSupported == false) || (command.request == null) || (command.response == null) || (command.name == null)) break; + if ((twoStepLoginSupported == false) || (command.response == null) || (command.name == null) || (obj.hardwareKeyRegistrationRequest == null)) break; // Check the key registration request - const result = require('u2f').checkRegistration(command.request, command.response); - if (result) { + require('authdog').finishRegistration(obj.hardwareKeyRegistrationRequest, command.response).then(function (registrationStatus) { var keyIndex = obj.parent.crypto.randomBytes(4).readUInt32BE(0); - ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: result.successful, name: command.name, index: keyIndex })); - if (result.successful) { - if (user.otphkeys == null) { user.otphkeys = []; } - user.otphkeys.push({ name: command.name, type: 1, publicKey: result.publicKey, keyHandle: result.keyHandle, keyIndex: keyIndex }); - obj.parent.db.SetUser(user); - //console.log('KEYS', JSON.stringify(user.otphkeys)); + ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: true, name: command.name, index: keyIndex })); + if (user.otphkeys == null) { user.otphkeys = []; } + user.otphkeys.push({ name: command.name, type: 1, publicKey: registrationStatus.publicKey, keyHandle: registrationStatus.keyHandle, certificate: registrationStatus.certificate, keyIndex: keyIndex }); + obj.parent.db.SetUser(user); - // Notify change TODO: Should be done on all sessions/servers for this user. - try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { } - } - } + // Notify change TODO: Should be done on all sessions/servers for this user. + try { ws.send(JSON.stringify({ action: 'userinfo', userinfo: obj.parent.CloneSafeUser(user) })); } catch (ex) { } + delete obj.hardwareKeyRegistrationRequest; + }, function (error) { + ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: false, error: error, name: command.name, index: keyIndex })); + delete obj.hardwareKeyRegistrationRequest; + }); break; } case 'getNotes': diff --git a/package.json b/package.json index 83ce8157..312c6790 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "meshcentral", - "version": "0.2.7-s", + "version": "0.2.7-t", "keywords": [ "Remote Management", "Intel AMT", @@ -27,6 +27,7 @@ ], "dependencies": { "archiver": "^3.0.0", + "authdog": "^0.1.1", "body-parser": "^1.18.2", "compression": "^1.7.3", "connect-redis": "^3.4.0", @@ -42,7 +43,6 @@ "nedb": "^1.8.0", "node-forge": "^0.7.6", "otplib": "^10.0.1", - "u2f": "^0.1.3", "util.promisify": "^1.0.0", "ws": "^6.1.2", "xmldom": "^0.1.27", diff --git a/sample-config.json b/sample-config.json index 3111728f..a76ba3b4 100644 --- a/sample-config.json +++ b/sample-config.json @@ -40,7 +40,8 @@ "_UserAllowedIP": "127.0.0.1,192.168.1.0/24", "_UserBlockedIP": "127.0.0.1,::1,192.168.0.100", "_AgentAllowedIP": "192.168.0.100/24", - "_AgentBlockedIP": "127.0.0.1,::1" + "_AgentBlockedIP": "127.0.0.1,::1", + "_yubikey": { "id": "0000", "secret": "xxxxxxxxxxxxxxxxxxxxx", "_proxy": "http://myproxy.domain.com:80" }, }, "customer1": { "DNS": "customer1.myserver.com", diff --git a/views/default-min.handlebars b/views/default-min.handlebars index 2efffb3e..661216d5 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file + MeshCentral
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default-mobile-min.handlebars b/views/default-mobile-min.handlebars index a7577f05..aaeccb37 100644 --- a/views/default-mobile-min.handlebars +++ b/views/default-mobile-min.handlebars @@ -1 +1 @@ - MeshCentral
{{{title}}}
{{{title2}}}
\ No newline at end of file + MeshCentral
{{{title}}}
{{{title2}}}
\ No newline at end of file diff --git a/views/default-mobile.handlebars b/views/default-mobile.handlebars index e049ea31..b5cef093 100644 --- a/views/default-mobile.handlebars +++ b/views/default-mobile.handlebars @@ -234,14 +234,14 @@
-

Account actions

+

Account Security

+
+ + +
+

Account Actions

- - @@ -652,9 +652,8 @@ function updateSelf() { QV('verifyEmailId', (userinfo.emailVerified !== true) && (userinfo.email != null) && (serverinfo.emailcheck == true)); - QV('otpAuth', ((features & 4096) != 0) && (userinfo.otpsecret != 1)); - QV('otpAuthRemove', ((features & 4096) != 0) && (userinfo.otpsecret == 1)); - QV('manageOtp', ((features & 4096) != 0) && (userinfo.otpsecret == 1)); + QV('manageAuthApp', features & 4096); + QV('manageOtp', ((features & 4096) != 0) && ((userinfo.otpsecret == 1) || (userinfo.otphkeys > 0))); } function onMessage(server, message) { @@ -742,12 +741,12 @@ } case 'otpauth-setup': { if (xxdialogMode) return; - setDialogMode(2, "Add 2-Step Login", 1, null, message.success ? "2-step login activation successful. You will now need a valid token to login again." : "2-step login activation failed. Clear the secret from the application and try again. You only have a few minutes to enter the proper code."); + setDialogMode(2, "Authenticator App", 1, null, message.success ? "2-step login activation successful. You will now need a valid token to login again." : "2-step login activation failed. Clear the secret from the application and try again. You only have a few minutes to enter the proper code."); break; } case 'otpauth-clear': { if (xxdialogMode) return; - setDialogMode(2, "Remove 2-Step Login", 1, null, message.success ? "2-step login activation removed. You can reactivate this feature at any time." : "2-step login activation removal failed. Try again."); + setDialogMode(2, "Authenticator App", 1, null, message.success ? "2-step login activation removed. You can reactivate this feature at any time." : "2-step login activation removal failed. Try again."); break; } case 'otpauth-getpasswords': { @@ -770,7 +769,7 @@ x += ""; if (message.passwords != null) { x += ""; } x += "

"; - setDialogMode(2, "One-Time Passwords", 8, null, x, 'otpauth-manage'); + setDialogMode(2, "Manage Backup Codes", 8, null, x, 'otpauth-manage'); break; } case 'event': { @@ -976,13 +975,13 @@ break; } default: - console.log('Unknown message.event.action', message.event.action); + //console.log('Unknown message.event.action', message.event.action); break; } break; } default: - console.log('Unknown message.action', message.action); + //console.log('Unknown message.action', message.action); break; } } @@ -1028,9 +1027,14 @@ // MY ACCOUNT // + function account_manageAuthApp() { + if (xxdialogMode || ((features & 4096) == 0)) return; + if (userinfo.otpsecret == 1) { account_removeOtp(); } else { account_addOtp(); } + } + function account_addOtp() { if (xxdialogMode || (userinfo.otpsecret == 1) || ((features & 4096) == 0)) return; - setDialogMode(2, "Add 2-Step Login", 2, function () { meshserver.send({ action: 'otpauth-setup', secret: Q('d2optsecret').attributes.secret.value, token: Q('d2otpauthinput').value }); }, "
Loading...
", 'otpauth-request'); + setDialogMode(2, "Authenticator App", 2, function () { meshserver.send({ action: 'otpauth-setup', secret: Q('d2optsecret').attributes.secret.value, token: Q('d2otpauthinput').value }); }, "
Loading...
", 'otpauth-request'); meshserver.send({ action: 'otpauth-request' }); } @@ -1042,7 +1046,7 @@ function account_removeOtp() { if (xxdialogMode || (userinfo.otpsecret != 1) || ((features & 4096) == 0)) return; - setDialogMode(2, "Remove 2-Step Login", 3, function () { meshserver.send({ action: 'otpauth-clear' }); }, "Confirm removal of 2-step login?"); + setDialogMode(2, "Authenticator App", 3, function () { meshserver.send({ action: 'otpauth-clear' }); }, "Confirm removal of authenticator application 2-step login?"); } function account_manageOtp(action) { diff --git a/views/default.handlebars b/views/default.handlebars index d5bad4fe..bc6a8ecd 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -246,14 +246,19 @@