First version that can do FIDO2/WebAuthn authentication.

This commit is contained in:
Ylian Saint-Hilaire 2019-03-23 12:14:53 -07:00
parent 06f2911ddc
commit 616e92b9e4
3 changed files with 68 additions and 25 deletions

View File

@ -1987,7 +1987,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
parent.f2l.attestationOptions().then(function (registrationOptions) { parent.f2l.attestationOptions().then(function (registrationOptions) {
// Convert the challenge to base64 and add user information // Convert the challenge to base64 and add user information
registrationOptions.challenge = Buffer(registrationOptions.challenge).toString('base64'); registrationOptions.challenge = Buffer(registrationOptions.challenge).toString('base64');
registrationOptions.user.id = Buffer(parent.crypto.randomBytes(16)).toString('base64'); registrationOptions.user.id = Buffer(user._id, 'binary').toString('base64');
registrationOptions.user.name = user._id; registrationOptions.user.name = user._id;
registrationOptions.user.displayName = user._id.split('/')[2]; registrationOptions.user.displayName = user._id.split('/')[2];

View File

@ -385,21 +385,18 @@
// New WebAuthn hardware keys // New WebAuthn hardware keys
navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then( navigator.credentials.get({ publicKey: publicKeyCredentialRequestOptions }).then(
function (rawAssertion) { function (rawAssertion) {
console.log(rawAssertion);
/*
var assertion = { var assertion = {
id: base64encode(rawAssertion.rawId), id: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.rawId))), //base64encode(rawAssertion.rawId),
clientDataJSON: arrayBufferToString(rawAssertion.response.clientDataJSON), clientDataJSON: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.clientDataJSON))), //arrayBufferToString(rawAssertion.response.clientDataJSON),
userHandle: base64encode(rawAssertion.response.userHandle), userHandle: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.userHandle))), //base64encode(rawAssertion.response.userHandle),
signature: base64encode(rawAssertion.response.signature), signature: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.signature))), //base64encode(rawAssertion.response.signature),
authenticatorData: base64encode(rawAssertion.response.authenticatorData) authenticatorData: btoa(String.fromCharCode.apply(null, new Uint8Array(rawAssertion.response.authenticatorData))), //base64encode(rawAssertion.response.authenticatorData)
}; };
console.log(assertion); Q('hwtokenInput').value = JSON.stringify(assertion);
*/ QE('tokenOkButton', true);
Q('tokenOkButton').click();
}, },
function (error) { function (error) { console.log('credentials-get error', error); }
console.log('credentials-get error', error);
}
); );
} else if ((hardwareKeyChallenge != null) && u2fSupported()) { } else if ((hardwareKeyChallenge != null) && u2fSupported()) {
// Old U2F hardware keys // Old U2F hardware keys

View File

@ -340,21 +340,67 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
const twoStepLoginSupported = ((domain.auth != 'sspi') && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.nousers !== true)); const twoStepLoginSupported = ((domain.auth != 'sspi') && (obj.parent.certificates.CommonName.indexOf('.') != -1) && (obj.args.nousers !== true));
if (twoStepLoginSupported == false) { func(true); return; }; if (twoStepLoginSupported == false) { func(true); return; };
// Check U2F hardware key // Check hardware key
if (user.otphkeys && (user.otphkeys.length > 0) && (typeof (hwtoken) == 'string') && (hwtoken.length > 0)) { if (user.otphkeys && (user.otphkeys.length > 0) && (typeof (hwtoken) == 'string') && (hwtoken.length > 0)) {
var authResponse = null;
try { authResponse = JSON.parse(hwtoken); } catch (ex) { }
if (authResponse != null) {
if ((obj.f2l != null) && (authResponse.clientDataJSON)) {
// Get all WebAuthn keys
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) {
// Decode authentication response
var clientAssertionResponse = { response: {} };
clientAssertionResponse.id = authResponse.id;
clientAssertionResponse.rawId = new Uint8Array(Buffer.from(authResponse.id, 'base64')).buffer;
clientAssertionResponse.response.authenticatorData = new Uint8Array(Buffer.from(authResponse.authenticatorData, 'base64')).buffer;
clientAssertionResponse.response.clientDataJSON = new Uint8Array(Buffer.from(authResponse.clientDataJSON, 'base64')).buffer;
clientAssertionResponse.response.signature = new Uint8Array(Buffer.from(authResponse.signature, 'base64')).buffer;
clientAssertionResponse.response.userHandle = new Uint8Array(Buffer.from(authResponse.userHandle, 'base64')).buffer;
// Look for the key with clientAssertionResponse.id
var webAuthnKey = null;
for (var i = 0; i < webAuthnKeys.length; i++) { if (webAuthnKeys[i].keyId == clientAssertionResponse.id) { webAuthnKey = webAuthnKeys[i]; } }
// If we found a valid key to use, let's validate the response
if (webAuthnKey != null) {
var assertionExpectations = {
challenge: req.session.u2fchallenge,
origin: "https://devbox.mesh.meshcentral.com",
factor: "either",
publicKey: webAuthnKey.publicKey,
prevCounter: webAuthnKey.counter,
userHandle: Buffer(user._id, 'binary').toString('base64')
};
obj.f2l.assertionResult(clientAssertionResponse, assertionExpectations).then(
function (authnResult) {
// Update the hardware key counter and accept the 2nd factor
webAuthnKey.counter = authnResult.authnrData.get('counter');
obj.db.SetUser(user);
func(true);
},
function (error) {
//console.log('attestationResult-error', error);
func(false);
}
);
return;
}
}
} else {
// 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]); } }
if (u2fKeys.length > 0) { if (u2fKeys.length > 0) {
var authResponse = null;
try { authResponse = JSON.parse(hwtoken); } catch (ex) { }
if (authResponse != null) {
// Check authentication response // Check authentication response
require('authdog').finishAuthentication(req.session.u2fchallenge, authResponse, u2fKeys).then(function (authenticationStatus) { func(true); }, function (error) { func(false); }); require('authdog').finishAuthentication(req.session.u2fchallenge, authResponse, u2fKeys).then(function (authenticationStatus) { func(true); }, function (error) { func(false); });
return; return;
} }
} }
} }
}
// Check Google Authenticator // Check Google Authenticator
const otplib = require('otplib') const otplib = require('otplib')
@ -400,7 +446,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
obj.f2l.assertionOptions().then(function (authnOptions) { obj.f2l.assertionOptions().then(function (authnOptions) {
authnOptions.type = 'webAuthn'; authnOptions.type = 'webAuthn';
authnOptions.keyIds = []; authnOptions.keyIds = [];
for (var i = 0; i < webAuthnKeys.length; i++) { authnOptions.keyIds.push(webAuthnKeys[0].keyId); } for (var i = 0; i < webAuthnKeys.length; i++) { authnOptions.keyIds.push(webAuthnKeys[i].keyId); }
req.session.u2fchallenge = authnOptions.challenge = Buffer(authnOptions.challenge).toString('base64'); req.session.u2fchallenge = authnOptions.challenge = Buffer(authnOptions.challenge).toString('base64');
func(JSON.stringify(authnOptions)); func(JSON.stringify(authnOptions));
}, function (error) { }, function (error) {