mirror of
https://github.com/Ylianst/MeshCentral.git
synced 2024-12-26 07:05:52 -05:00
more WebAuthn cleanup.
This commit is contained in:
parent
e00cbf33d3
commit
ae09a77ab8
84
Webauthn.js
84
Webauthn.js
@ -3,12 +3,12 @@
|
|||||||
* @version v0.0.1
|
* @version v0.0.1
|
||||||
*/
|
*/
|
||||||
|
|
||||||
//
|
// This code is based on a portion of the webauthn module at: https://www.npmjs.com/package/webauthn
|
||||||
|
|
||||||
'use strict'
|
'use strict'
|
||||||
|
|
||||||
const crypto = require('crypto')
|
const crypto = require('crypto');
|
||||||
const cbor = require('cbor')
|
const cbor = require('cbor');
|
||||||
//const iso_3166_1 = require('iso-3166-1')
|
//const iso_3166_1 = require('iso-3166-1')
|
||||||
//const Certificate = null; //require('@fidm/x509')
|
//const Certificate = null; //require('@fidm/x509')
|
||||||
|
|
||||||
@ -23,7 +23,7 @@ module.exports.CreateWebAuthnModule = function () {
|
|||||||
pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
|
pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
|
||||||
timeout: 60000,
|
timeout: 60000,
|
||||||
attestation: 'none'
|
attestation: 'none'
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.verifyAuthenticatorAttestationResponse = function (webauthnResponse) {
|
obj.verifyAuthenticatorAttestationResponse = function (webauthnResponse) {
|
||||||
@ -38,7 +38,7 @@ module.exports.CreateWebAuthnModule = function () {
|
|||||||
if (ctapMakeCredResp.fmt === 'none') {
|
if (ctapMakeCredResp.fmt === 'none') {
|
||||||
if (!(authrDataStruct.flags & 0x01)) { throw new Error('User was NOT presented during authentication!'); } // U2F_USER_PRESENTED
|
if (!(authrDataStruct.flags & 0x01)) { throw new Error('User was NOT presented during authentication!'); } // U2F_USER_PRESENTED
|
||||||
|
|
||||||
const publicKey = COSEECDHAtoPKCS(authrDataStruct.COSEPublicKey)
|
const publicKey = COSEECDHAtoPKCS(authrDataStruct.COSEPublicKey);
|
||||||
response.verified = true;
|
response.verified = true;
|
||||||
|
|
||||||
if (response.verified) {
|
if (response.verified) {
|
||||||
@ -47,7 +47,7 @@ module.exports.CreateWebAuthnModule = function () {
|
|||||||
publicKey: ASN1toPEM(publicKey),
|
publicKey: ASN1toPEM(publicKey),
|
||||||
counter: authrDataStruct.counter,
|
counter: authrDataStruct.counter,
|
||||||
keyId: authrDataStruct.credID.toString('base64')
|
keyId: authrDataStruct.credID.toString('base64')
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/*
|
/*
|
||||||
@ -194,14 +194,14 @@ module.exports.CreateWebAuthnModule = function () {
|
|||||||
throw new Error(`Unsupported attestation format: ${ctapMakeCredResp.fmt}`);
|
throw new Error(`Unsupported attestation format: ${ctapMakeCredResp.fmt}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
obj.verifyAuthenticatorAssertionResponse = function (webauthnResponse, authr) {
|
obj.verifyAuthenticatorAssertionResponse = function (webauthnResponse, authr) {
|
||||||
const response = { 'verified': false }
|
const response = { 'verified': false }
|
||||||
if (['fido-u2f'].includes(authr.fmt)) {
|
if (['fido-u2f'].includes(authr.fmt)) {
|
||||||
const authrDataStruct = parseGetAssertAuthData(webauthnResponse.authenticatorData)
|
const authrDataStruct = parseGetAssertAuthData(webauthnResponse.authenticatorData);
|
||||||
if (!(authrDataStruct.flags & 0x01)) { throw new Error('User was not presented durring authentication!') } // U2F_USER_PRESENTED
|
if (!(authrDataStruct.flags & 0x01)) { throw new Error('User was not presented durring authentication!'); } // U2F_USER_PRESENTED
|
||||||
response.counter = authrDataStruct.counter;
|
response.counter = authrDataStruct.counter;
|
||||||
response.verified = verifySignature(webauthnResponse.signature, Buffer.concat([authrDataStruct.rpIdHash, authrDataStruct.flagsBuf, authrDataStruct.counterBuf, hash(webauthnResponse.clientDataJSON)]), authr.publicKey);
|
response.verified = verifySignature(webauthnResponse.signature, Buffer.concat([authrDataStruct.rpIdHash, authrDataStruct.flagsBuf, authrDataStruct.counterBuf, hash(webauthnResponse.clientDataJSON)]), authr.publicKey);
|
||||||
}
|
}
|
||||||
@ -212,51 +212,51 @@ module.exports.CreateWebAuthnModule = function () {
|
|||||||
function verifySignature(signature, data, publicKey) { return crypto.createVerify('SHA256').update(data).verify(publicKey, signature); }
|
function verifySignature(signature, data, publicKey) { return crypto.createVerify('SHA256').update(data).verify(publicKey, signature); }
|
||||||
|
|
||||||
function parseGetAssertAuthData(buffer) {
|
function parseGetAssertAuthData(buffer) {
|
||||||
const rpIdHash = buffer.slice(0, 32)
|
const rpIdHash = buffer.slice(0, 32);
|
||||||
buffer = buffer.slice(32)
|
buffer = buffer.slice(32);
|
||||||
const flagsBuf = buffer.slice(0, 1)
|
const flagsBuf = buffer.slice(0, 1);
|
||||||
buffer = buffer.slice(1)
|
buffer = buffer.slice(1);
|
||||||
const flags = flagsBuf[0]
|
const flags = flagsBuf[0];
|
||||||
const counterBuf = buffer.slice(0, 4)
|
const counterBuf = buffer.slice(0, 4);
|
||||||
buffer = buffer.slice(4)
|
buffer = buffer.slice(4);
|
||||||
const counter = counterBuf.readUInt32BE(0)
|
const counter = counterBuf.readUInt32BE(0);
|
||||||
return { rpIdHash, flagsBuf, flags, counter, counterBuf }
|
return { rpIdHash, flagsBuf, flags, counter, counterBuf };
|
||||||
}
|
}
|
||||||
|
|
||||||
function parseMakeCredAuthData(buffer) {
|
function parseMakeCredAuthData(buffer) {
|
||||||
const rpIdHash = buffer.slice(0, 32)
|
const rpIdHash = buffer.slice(0, 32);
|
||||||
buffer = buffer.slice(32)
|
buffer = buffer.slice(32);
|
||||||
const flagsBuf = buffer.slice(0, 1)
|
const flagsBuf = buffer.slice(0, 1);
|
||||||
buffer = buffer.slice(1)
|
buffer = buffer.slice(1);
|
||||||
const flags = flagsBuf[0]
|
const flags = flagsBuf[0];
|
||||||
const counterBuf = buffer.slice(0, 4)
|
const counterBuf = buffer.slice(0, 4);
|
||||||
buffer = buffer.slice(4)
|
buffer = buffer.slice(4);
|
||||||
const counter = counterBuf.readUInt32BE(0)
|
const counter = counterBuf.readUInt32BE(0);
|
||||||
const aaguid = buffer.slice(0, 16)
|
const aaguid = buffer.slice(0, 16);
|
||||||
buffer = buffer.slice(16)
|
buffer = buffer.slice(16);
|
||||||
const credIDLenBuf = buffer.slice(0, 2)
|
const credIDLenBuf = buffer.slice(0, 2);
|
||||||
buffer = buffer.slice(2)
|
buffer = buffer.slice(2);
|
||||||
const credIDLen = credIDLenBuf.readUInt16BE(0)
|
const credIDLen = credIDLenBuf.readUInt16BE(0);
|
||||||
const credID = buffer.slice(0, credIDLen)
|
const credID = buffer.slice(0, credIDLen);
|
||||||
buffer = buffer.slice(credIDLen)
|
buffer = buffer.slice(credIDLen);
|
||||||
const COSEPublicKey = buffer
|
const COSEPublicKey = buffer;
|
||||||
return { rpIdHash, flagsBuf, flags, counter, counterBuf, aaguid, credID, COSEPublicKey }
|
return { rpIdHash, flagsBuf, flags, counter, counterBuf, aaguid, credID, COSEPublicKey };
|
||||||
}
|
}
|
||||||
|
|
||||||
function COSEECDHAtoPKCS(COSEPublicKey) {
|
function COSEECDHAtoPKCS(COSEPublicKey) {
|
||||||
const coseStruct = cbor.decodeAllSync(COSEPublicKey)[0];
|
const coseStruct = cbor.decodeAllSync(COSEPublicKey)[0];
|
||||||
return Buffer.concat([Buffer.from([0x04]), coseStruct.get(-2), coseStruct.get(-3)])
|
return Buffer.concat([Buffer.from([0x04]), coseStruct.get(-2), coseStruct.get(-3)]);
|
||||||
}
|
}
|
||||||
|
|
||||||
function ASN1toPEM(pkBuffer) {
|
function ASN1toPEM(pkBuffer) {
|
||||||
if (!Buffer.isBuffer(pkBuffer)) { throw new Error("ASN1toPEM: pkBuffer must be Buffer."); }
|
if (!Buffer.isBuffer(pkBuffer)) { throw new Error("ASN1toPEM: pkBuffer must be Buffer."); }
|
||||||
let type
|
let type;
|
||||||
if (pkBuffer.length == 65 && pkBuffer[0] == 0x04) { pkBuffer = Buffer.concat([ new Buffer.from("3059301306072a8648ce3d020106082a8648ce3d030107034200", "hex"), pkBuffer ]); type = 'PUBLIC KEY' } else { type = 'CERTIFICATE' }
|
if (pkBuffer.length == 65 && pkBuffer[0] == 0x04) { pkBuffer = Buffer.concat([new Buffer.from("3059301306072a8648ce3d020106082a8648ce3d030107034200", "hex"), pkBuffer]); type = 'PUBLIC KEY'; } else { type = 'CERTIFICATE'; }
|
||||||
const b64cert = pkBuffer.toString('base64')
|
const b64cert = pkBuffer.toString('base64');
|
||||||
let PEMKey = ''
|
let PEMKey = '';
|
||||||
for (let i = 0; i < Math.ceil(b64cert.length / 64); i++) { const start = 64 * i; PEMKey += b64cert.substr(start, 64) + '\n'; }
|
for (let i = 0; i < Math.ceil(b64cert.length / 64); i++) { const start = 64 * i; PEMKey += b64cert.substr(start, 64) + '\n'; }
|
||||||
PEMKey = `-----BEGIN ${type}-----\n` + PEMKey + `-----END ${type}-----\n`
|
PEMKey = `-----BEGIN ${type}-----\n` + PEMKey + `-----END ${type}-----\n`;
|
||||||
return PEMKey
|
return PEMKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
return obj;
|
return obj;
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 988 B |
File diff suppressed because one or more lines are too long
@ -1553,7 +1553,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':(key.type == 2)?'OTP':'WebAuthn');
|
var key = message.keys[i], type = (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 {
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -313,15 +313,6 @@
|
|||||||
},
|
},
|
||||||
function (error) { console.log('credentials-get error', error); }
|
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);
|
|
||||||
QE('tokenOkButton', true);
|
|
||||||
Q('tokenOkButton').click();
|
|
||||||
}
|
|
||||||
}, hardwareKeyChallenge.timeoutSeconds);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -353,15 +344,6 @@
|
|||||||
},
|
},
|
||||||
function (error) { console.log('credentials-get error', error); }
|
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 == 5) && authResponse.signatureData) {
|
|
||||||
Q('resetHwtokenInput').value = JSON.stringify(authResponse);
|
|
||||||
QE('resetTokenOkButton', true);
|
|
||||||
Q('resetTokenOkButton').click();
|
|
||||||
}
|
|
||||||
}, hardwareKeyChallenge.timeoutSeconds);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -613,7 +595,6 @@
|
|||||||
function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
|
function haltEvent(e) { if (e.preventDefault) e.preventDefault(); if (e.stopPropagation) e.stopPropagation(); return false; }
|
||||||
function haltReturn(e) { if (e.keyCode == 13) { haltEvent(e); } }
|
function haltReturn(e) { if (e.keyCode == 13) { haltEvent(e); } }
|
||||||
function validateEmail(v) { var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return emailReg.test(v); } // New version
|
function validateEmail(v) { var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return emailReg.test(v); } // New version
|
||||||
function u2fSupported() { return (window.u2f && ((navigator.userAgent.indexOf('Chrome/') > 0) || (navigator.userAgent.indexOf('Firefox/') > 0) || (navigator.userAgent.indexOf('Opera/') > 0) || (navigator.userAgent.indexOf('Safari/') > 0))); }
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
@ -321,15 +321,6 @@
|
|||||||
},
|
},
|
||||||
function (error) { console.log('credentials-get error', error); }
|
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);
|
|
||||||
QE('tokenOkButton', true);
|
|
||||||
Q('tokenOkButton').click();
|
|
||||||
}
|
|
||||||
}, hardwareKeyChallenge.timeoutSeconds);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -361,15 +352,6 @@
|
|||||||
},
|
},
|
||||||
function (error) { console.log('credentials-get error', error); }
|
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 == 5) && authResponse.signatureData) {
|
|
||||||
Q('resetHwtokenInput').value = JSON.stringify(authResponse);
|
|
||||||
QE('resetTokenOkButton', true);
|
|
||||||
Q('resetTokenOkButton').click();
|
|
||||||
}
|
|
||||||
}, hardwareKeyChallenge.timeoutSeconds);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -685,7 +667,6 @@
|
|||||||
function validateEmail(v) { var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return emailReg.test(v); } // New version
|
function validateEmail(v) { var emailReg = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; return emailReg.test(v); } // New version
|
||||||
function putstore(name, val) { try { if (typeof (localStorage) === 'undefined') return; localStorage.setItem(name, val); } catch (e) { } }
|
function putstore(name, val) { try { if (typeof (localStorage) === 'undefined') return; localStorage.setItem(name, val); } catch (e) { } }
|
||||||
function getstore(name, val) { try { if (typeof (localStorage) === 'undefined') return val; var v = localStorage.getItem(name); if ((v == null) || (v == null)) return val; return v; } catch (e) { return val; } }
|
function getstore(name, val) { try { if (typeof (localStorage) === 'undefined') return val; var v = localStorage.getItem(name); if ((v == null) || (v == null)) return val; return v; } catch (e) { return val; } }
|
||||||
function u2fSupported() { return (window.u2f && ((navigator.userAgent.indexOf('Chrome/') > 0) || (navigator.userAgent.indexOf('Firefox/') > 0) || (navigator.userAgent.indexOf('Opera/') > 0) || (navigator.userAgent.indexOf('Safari/') > 0))); }
|
|
||||||
|
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
Loading…
Reference in New Issue
Block a user