mirror of
				https://github.com/Ylianst/MeshCentral.git
				synced 2025-03-09 15:40:18 +00:00 
			
		
		
		
	Complete replacement of the WebAuthn 2 factor support.
This commit is contained in:
		
							parent
							
								
									36ac790452
								
							
						
					
					
						commit
						e00cbf33d3
					
				
					 9 changed files with 576 additions and 241 deletions
				
			
		| 
						 | 
				
			
			@ -130,6 +130,7 @@
 | 
			
		|||
    <Compile Include="public\scripts\filesaver.1.1.20151003.js" />
 | 
			
		||||
    <Compile Include="public\scripts\meshcentral.js" />
 | 
			
		||||
    <Compile Include="redirserver.js" />
 | 
			
		||||
    <Compile Include="webauthn.js" />
 | 
			
		||||
    <Compile Include="webserver.js" />
 | 
			
		||||
    <Compile Include="winservice.js" />
 | 
			
		||||
    <Content Include="agents\compressModules.bat" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										263
									
								
								Webauthn.js
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										263
									
								
								Webauthn.js
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,263 @@
 | 
			
		|||
/**
 | 
			
		||||
* @description MeshCentral WebAuthn module
 | 
			
		||||
* @version v0.0.1
 | 
			
		||||
*/
 | 
			
		||||
 | 
			
		||||
//
 | 
			
		||||
 | 
			
		||||
'use strict'
 | 
			
		||||
 | 
			
		||||
const crypto = require('crypto')
 | 
			
		||||
const cbor = require('cbor')
 | 
			
		||||
//const iso_3166_1 = require('iso-3166-1')
 | 
			
		||||
//const Certificate = null; //require('@fidm/x509')
 | 
			
		||||
 | 
			
		||||
module.exports.CreateWebAuthnModule = function () {
 | 
			
		||||
    var obj = {};
 | 
			
		||||
 | 
			
		||||
    obj.generateRegistrationChallenge = function (rpName, user) {
 | 
			
		||||
        return {
 | 
			
		||||
            rp: { name: rpName },
 | 
			
		||||
            user: user,
 | 
			
		||||
            challenge: crypto.randomBytes(64).toString('base64'),
 | 
			
		||||
            pubKeyCredParams: [{ type: 'public-key', alg: -7 }],
 | 
			
		||||
            timeout: 60000,
 | 
			
		||||
            attestation: 'none'
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    obj.verifyAuthenticatorAttestationResponse = function (webauthnResponse) {
 | 
			
		||||
        const attestationBuffer = Buffer.from(webauthnResponse.attestationObject, 'base64');
 | 
			
		||||
        const ctapMakeCredResp = cbor.decodeAllSync(attestationBuffer)[0];
 | 
			
		||||
        const authrDataStruct = parseMakeCredAuthData(ctapMakeCredResp.authData);
 | 
			
		||||
        //console.log('***CTAP_RESPONSE', ctapMakeCredResp)
 | 
			
		||||
        //console.log('***AUTHR_DATA_STRUCT', authrDataStruct)
 | 
			
		||||
 | 
			
		||||
        const response = { 'verified': false };
 | 
			
		||||
 | 
			
		||||
        if (ctapMakeCredResp.fmt === 'none') {
 | 
			
		||||
            if (!(authrDataStruct.flags & 0x01)) { throw new Error('User was NOT presented during authentication!'); } // U2F_USER_PRESENTED
 | 
			
		||||
 | 
			
		||||
            const publicKey = COSEECDHAtoPKCS(authrDataStruct.COSEPublicKey)
 | 
			
		||||
            response.verified = true;
 | 
			
		||||
 | 
			
		||||
            if (response.verified) {
 | 
			
		||||
                response.authrInfo = {
 | 
			
		||||
                    fmt: 'none',
 | 
			
		||||
                    publicKey: ASN1toPEM(publicKey),
 | 
			
		||||
                    counter: authrDataStruct.counter,
 | 
			
		||||
                    keyId: authrDataStruct.credID.toString('base64')
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        /*
 | 
			
		||||
        else if (ctapMakeCredResp.fmt === 'fido-u2f') {
 | 
			
		||||
            if (!(authrDataStruct.flags & 0x01)) // U2F_USER_PRESENTED
 | 
			
		||||
                throw new Error('User was NOT presented during authentication!');
 | 
			
		||||
 | 
			
		||||
            const clientDataHash = hash(webauthnResponse.clientDataJSON)
 | 
			
		||||
            const reservedByte = Buffer.from([0x00]);
 | 
			
		||||
            const publicKey = COSEECDHAtoPKCS(authrDataStruct.COSEPublicKey)
 | 
			
		||||
            const signatureBase = Buffer.concat([reservedByte, authrDataStruct.rpIdHash, clientDataHash, authrDataStruct.credID, publicKey]);
 | 
			
		||||
 | 
			
		||||
            const PEMCertificate = ASN1toPEM(ctapMakeCredResp.attStmt.x5c[0]);
 | 
			
		||||
            const signature = ctapMakeCredResp.attStmt.sig;
 | 
			
		||||
 | 
			
		||||
            response.verified = verifySignature(signature, signatureBase, PEMCertificate)
 | 
			
		||||
 | 
			
		||||
            if (response.verified) {
 | 
			
		||||
                response.authrInfo = {
 | 
			
		||||
                    fmt: 'fido-u2f',
 | 
			
		||||
                    publicKey: ASN1toPEM(publicKey),
 | 
			
		||||
                    counter: authrDataStruct.counter,
 | 
			
		||||
                    keyId: authrDataStruct.credID.toString('base64')
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        } else if (ctapMakeCredResp.fmt === 'packed' && ctapMakeCredResp.attStmt.hasOwnProperty('x5c')) {
 | 
			
		||||
            if (!(authrDataStruct.flags & 0x01)) // U2F_USER_PRESENTED
 | 
			
		||||
                throw new Error('User was NOT presented durring authentication!');
 | 
			
		||||
 | 
			
		||||
            const clientDataHash = hash(webauthnResponse.clientDataJSON)
 | 
			
		||||
            const publicKey = COSEECDHAtoPKCS(authrDataStruct.COSEPublicKey)
 | 
			
		||||
            const signatureBase = Buffer.concat([ctapMakeCredResp.authData, clientDataHash]);
 | 
			
		||||
 | 
			
		||||
            const PEMCertificate = ASN1toPEM(ctapMakeCredResp.attStmt.x5c[0]);
 | 
			
		||||
            const signature = ctapMakeCredResp.attStmt.sig;
 | 
			
		||||
 | 
			
		||||
            const pem = Certificate.fromPEM(PEMCertificate);
 | 
			
		||||
 | 
			
		||||
            // Getting requirements from https://www.w3.org/TR/webauthn/#packed-attestation
 | 
			
		||||
            const aaguid_ext = pem.getExtension('1.3.6.1.4.1.45724.1.1.4')
 | 
			
		||||
 | 
			
		||||
            response.verified = // Verify that sig is a valid signature over the concatenation of authenticatorData
 | 
			
		||||
                // and clientDataHash using the attestation public key in attestnCert with the algorithm specified in alg.
 | 
			
		||||
                verifySignature(signature, signatureBase, PEMCertificate) &&
 | 
			
		||||
                // version must be 3 (which is indicated by an ASN.1 INTEGER with value 2)
 | 
			
		||||
                pem.version == 3 &&
 | 
			
		||||
                // ISO 3166 valid country
 | 
			
		||||
                typeof iso_3166_1.whereAlpha2(pem.subject.countryName) !== 'undefined' &&
 | 
			
		||||
                // Legal name of the Authenticator vendor (UTF8String)
 | 
			
		||||
                pem.subject.organizationName &&
 | 
			
		||||
                // Literal string “Authenticator Attestation” (UTF8String)
 | 
			
		||||
                pem.subject.organizationalUnitName === 'Authenticator Attestation' &&
 | 
			
		||||
                // A UTF8String of the vendor’s choosing
 | 
			
		||||
                pem.subject.commonName &&
 | 
			
		||||
                // The Basic Constraints extension MUST have the CA component set to false
 | 
			
		||||
                !pem.extensions.isCA &&
 | 
			
		||||
                // If attestnCert contains an extension with OID 1.3.6.1.4.1.45724.1.1.4 (id-fido-gen-ce-aaguid)
 | 
			
		||||
                // verify that the value of this extension matches the aaguid in authenticatorData.
 | 
			
		||||
                // The extension MUST NOT be marked as critical.
 | 
			
		||||
                (aaguid_ext != null ?
 | 
			
		||||
                    (authrDataStruct.hasOwnProperty('aaguid') ?
 | 
			
		||||
                        !aaguid_ext.critical && aaguid_ext.value.slice(2).equals(authrDataStruct.aaguid) : false)
 | 
			
		||||
                    : true);
 | 
			
		||||
 | 
			
		||||
            if (response.verified) {
 | 
			
		||||
                response.authrInfo = {
 | 
			
		||||
                    fmt: 'fido-u2f',
 | 
			
		||||
                    publicKey: publicKey,
 | 
			
		||||
                    counter: authrDataStruct.counter,
 | 
			
		||||
                    keyId: authrDataStruct.credID.toString('base64')
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        // Self signed
 | 
			
		||||
        } else if (ctapMakeCredResp.fmt === 'packed') {
 | 
			
		||||
            if (!(authrDataStruct.flags & 0x01)) // U2F_USER_PRESENTED
 | 
			
		||||
                throw new Error('User was NOT presented durring authentication!');
 | 
			
		||||
 | 
			
		||||
            const clientDataHash = hash(webauthnResponse.clientDataJSON)
 | 
			
		||||
            const publicKey = COSEECDHAtoPKCS(authrDataStruct.COSEPublicKey)
 | 
			
		||||
            const signatureBase = Buffer.concat([ctapMakeCredResp.authData, clientDataHash]);
 | 
			
		||||
            const PEMCertificate = ASN1toPEM(publicKey);
 | 
			
		||||
 | 
			
		||||
            const { attStmt: { sig: signature, alg } } = ctapMakeCredResp
 | 
			
		||||
 | 
			
		||||
            response.verified = // Verify that sig is a valid signature over the concatenation of authenticatorData
 | 
			
		||||
                // and clientDataHash using the attestation public key in attestnCert with the algorithm specified in alg.
 | 
			
		||||
                verifySignature(signature, signatureBase, PEMCertificate) && alg === -7
 | 
			
		||||
 | 
			
		||||
            if (response.verified) {
 | 
			
		||||
                response.authrInfo = {
 | 
			
		||||
                    fmt: 'fido-u2f',
 | 
			
		||||
                    publicKey: ASN1toPEM(publicKey),
 | 
			
		||||
                    counter: authrDataStruct.counter,
 | 
			
		||||
                    keyId: authrDataStruct.credID.toString('base64')
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
        } else if (ctapMakeCredResp.fmt === 'android-safetynet') {
 | 
			
		||||
            console.log("Android safetynet request\n")
 | 
			
		||||
            console.log(ctapMakeCredResp)
 | 
			
		||||
 | 
			
		||||
            const authrDataStruct = parseMakeCredAuthData(ctapMakeCredResp.authData);
 | 
			
		||||
            console.log('AUTH_DATA', authrDataStruct)
 | 
			
		||||
            //console.log('CLIENT_DATA_JSON ', webauthnResponse.clientDataJSON)
 | 
			
		||||
 | 
			
		||||
            const publicKey = COSEECDHAtoPKCS(authrDataStruct.COSEPublicKey)
 | 
			
		||||
 | 
			
		||||
            let [header, payload, signature] = ctapMakeCredResp.attStmt.response.toString('utf8').split('.')
 | 
			
		||||
            const signatureBase = Buffer.from([header, payload].join('.'))
 | 
			
		||||
 | 
			
		||||
            header = JSON.parse(header)
 | 
			
		||||
            payload = JSON.parse(payload)
 | 
			
		||||
 | 
			
		||||
            console.log('JWS HEADER', header)
 | 
			
		||||
            console.log('JWS PAYLOAD', payload)
 | 
			
		||||
            console.log('JWS SIGNATURE', signature)
 | 
			
		||||
 | 
			
		||||
            const PEMCertificate = ASN1toPEM(Buffer.from(header.x5c[0], 'base64'))
 | 
			
		||||
 | 
			
		||||
            const pem = Certificate.fromPEM(PEMCertificate)
 | 
			
		||||
 | 
			
		||||
            console.log('PEM', pem)
 | 
			
		||||
 | 
			
		||||
            response.verified = // Verify that sig is a valid signature over the concatenation of authenticatorData
 | 
			
		||||
                // and clientDataHash using the attestation public key in attestnCert with the algorithm specified in alg.
 | 
			
		||||
                verifySignature(signature, signatureBase, PEMCertificate) &&
 | 
			
		||||
                // version must be 3 (which is indicated by an ASN.1 INTEGER with value 2)
 | 
			
		||||
                pem.version == 3 &&
 | 
			
		||||
                pem.subject.commonName === 'attest.android.com'
 | 
			
		||||
 | 
			
		||||
            if (response.verified) {
 | 
			
		||||
                response.authrInfo = {
 | 
			
		||||
                    fmt: 'fido-u2f',
 | 
			
		||||
                    publicKey: ASN1toPEM(publicKey),
 | 
			
		||||
                    counter: authrDataStruct.counter,
 | 
			
		||||
                    keyId: authrDataStruct.credID.toString('base64')
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            console.log('RESPONSE', response)
 | 
			
		||||
        } */
 | 
			
		||||
        else {
 | 
			
		||||
            throw new Error(`Unsupported attestation format: ${ctapMakeCredResp.fmt}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return response
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    obj.verifyAuthenticatorAssertionResponse = function (webauthnResponse, authr) {
 | 
			
		||||
        const response = { 'verified': false }
 | 
			
		||||
        if (['fido-u2f'].includes(authr.fmt)) {
 | 
			
		||||
            const authrDataStruct = parseGetAssertAuthData(webauthnResponse.authenticatorData)
 | 
			
		||||
            if (!(authrDataStruct.flags & 0x01)) { throw new Error('User was not presented durring authentication!') } // U2F_USER_PRESENTED
 | 
			
		||||
            response.counter = authrDataStruct.counter;
 | 
			
		||||
            response.verified = verifySignature(webauthnResponse.signature, Buffer.concat([authrDataStruct.rpIdHash, authrDataStruct.flagsBuf, authrDataStruct.counterBuf, hash(webauthnResponse.clientDataJSON)]), authr.publicKey);
 | 
			
		||||
        }
 | 
			
		||||
        return response;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function hash(data) { return crypto.createHash('sha256').update(data).digest() }
 | 
			
		||||
    function verifySignature(signature, data, publicKey) { return crypto.createVerify('SHA256').update(data).verify(publicKey, signature); }
 | 
			
		||||
 | 
			
		||||
    function parseGetAssertAuthData(buffer) {
 | 
			
		||||
        const rpIdHash = buffer.slice(0, 32)
 | 
			
		||||
        buffer = buffer.slice(32)
 | 
			
		||||
        const flagsBuf = buffer.slice(0, 1)
 | 
			
		||||
        buffer = buffer.slice(1)
 | 
			
		||||
        const flags = flagsBuf[0]
 | 
			
		||||
        const counterBuf = buffer.slice(0, 4)
 | 
			
		||||
        buffer = buffer.slice(4)
 | 
			
		||||
        const counter = counterBuf.readUInt32BE(0)
 | 
			
		||||
        return { rpIdHash, flagsBuf, flags, counter, counterBuf }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function parseMakeCredAuthData(buffer) {
 | 
			
		||||
        const rpIdHash = buffer.slice(0, 32)
 | 
			
		||||
        buffer = buffer.slice(32)
 | 
			
		||||
        const flagsBuf = buffer.slice(0, 1)
 | 
			
		||||
        buffer = buffer.slice(1)
 | 
			
		||||
        const flags = flagsBuf[0]
 | 
			
		||||
        const counterBuf = buffer.slice(0, 4)
 | 
			
		||||
        buffer = buffer.slice(4)
 | 
			
		||||
        const counter = counterBuf.readUInt32BE(0)
 | 
			
		||||
        const aaguid = buffer.slice(0, 16)
 | 
			
		||||
        buffer = buffer.slice(16)
 | 
			
		||||
        const credIDLenBuf = buffer.slice(0, 2)
 | 
			
		||||
        buffer = buffer.slice(2)
 | 
			
		||||
        const credIDLen = credIDLenBuf.readUInt16BE(0)
 | 
			
		||||
        const credID = buffer.slice(0, credIDLen)
 | 
			
		||||
        buffer = buffer.slice(credIDLen)
 | 
			
		||||
        const COSEPublicKey = buffer
 | 
			
		||||
        return { rpIdHash, flagsBuf, flags, counter, counterBuf, aaguid, credID, COSEPublicKey }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function COSEECDHAtoPKCS(COSEPublicKey) {
 | 
			
		||||
        const coseStruct = cbor.decodeAllSync(COSEPublicKey)[0];
 | 
			
		||||
        return Buffer.concat([Buffer.from([0x04]), coseStruct.get(-2), coseStruct.get(-3)])
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    function ASN1toPEM(pkBuffer) {
 | 
			
		||||
        if (!Buffer.isBuffer(pkBuffer)) { throw new Error("ASN1toPEM: pkBuffer must be Buffer."); }
 | 
			
		||||
        let type
 | 
			
		||||
        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')
 | 
			
		||||
        let PEMKey = ''
 | 
			
		||||
        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`
 | 
			
		||||
        return PEMKey
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return obj;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1748,8 +1748,7 @@ function mainStart(args) {
 | 
			
		|||
        if (config.settings.no2factorauth !== true) {
 | 
			
		||||
            // Setup YubiKey OTP if configured
 | 
			
		||||
            if (yubikey == true) { modules.push('yubikeyotp'); } // Add YubiKey OTP support
 | 
			
		||||
            // if not all SSPI, WebAuthn/FIDO2 or U2F support depending on the NodeJS version. FIDO2 does not work below NodeJS 8.x
 | 
			
		||||
            if (allsspi == false) { modules.push('otplib'); if (nodeVersion >= 8) { modules.push('@davedoesdev/fido2-lib'); } else { modules.push('authdog'); } }
 | 
			
		||||
            if (allsspi == false) { modules.push('otplib'); } // Google Authenticator support
 | 
			
		||||
        }
 | 
			
		||||
        
 | 
			
		||||
        // Install any missing modules and launch the server
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										107
									
								
								meshuser.js
									
										
									
									
									
								
							
							
						
						
									
										107
									
								
								meshuser.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -2253,64 +2253,6 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
 | 
			
		|||
                        }
 | 
			
		||||
                    });
 | 
			
		||||
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            case 'otp-hkey-setup-request':
 | 
			
		||||
                {
 | 
			
		||||
                    if (parent.parent.config.settings.no2factorauth === true) return;
 | 
			
		||||
 | 
			
		||||
                    var authdoglib = null;
 | 
			
		||||
                    try { authdoglib = require('authdog'); } catch (ex) { }
 | 
			
		||||
 | 
			
		||||
                    // 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 ((authdoglib == null) || (twoStepLoginSupported == false)) break;
 | 
			
		||||
 | 
			
		||||
                    // Build list of known keys
 | 
			
		||||
                    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
 | 
			
		||||
                    authdoglib.startRegistration('https://' + 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':
 | 
			
		||||
                {
 | 
			
		||||
                    if (parent.parent.config.settings.no2factorauth === true) return;
 | 
			
		||||
 | 
			
		||||
                    var authdoglib = null;
 | 
			
		||||
                    try { authdoglib = require('authdog'); } catch (ex) { }
 | 
			
		||||
 | 
			
		||||
                    // 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 ((authdoglib == null) || (twoStepLoginSupported == false) || (command.response == null) || (command.name == null) || (obj.hardwareKeyRegistrationRequest == null)) break;
 | 
			
		||||
 | 
			
		||||
                    // Check the key registration request
 | 
			
		||||
                    authdoglib.finishRegistration(obj.hardwareKeyRegistrationRequest, command.response).then(function (registrationStatus) {
 | 
			
		||||
                        var keyIndex = parent.crypto.randomBytes(4).readUInt32BE(0);
 | 
			
		||||
                        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 });
 | 
			
		||||
                        parent.db.SetUser(user);
 | 
			
		||||
                        delete obj.hardwareKeyRegistrationRequest;
 | 
			
		||||
 | 
			
		||||
                        // Notify change
 | 
			
		||||
                        var targets = ['*', 'server-users', user._id];
 | 
			
		||||
                        if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
 | 
			
		||||
                        parent.parent.DispatchEvent(targets, obj, { etype: 'user', username: user.name, account: parent.CloneSafeUser(user), action: 'accountchange', msg: 'Added security key.', domain: domain.id });
 | 
			
		||||
                    }, 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 'webauthn-startregister':
 | 
			
		||||
| 
						 | 
				
			
			@ -2319,47 +2261,28 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
 | 
			
		|||
 | 
			
		||||
                    // 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;
 | 
			
		||||
                    if ((twoStepLoginSupported == false) || (command.name == 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(user._id, 'binary').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);
 | 
			
		||||
                    });
 | 
			
		||||
                    // Send the registration request
 | 
			
		||||
                    var registrationOptions = parent.webauthn.generateRegistrationChallenge("Anonymous Service", { id: Buffer(user._id, 'binary').toString('base64'), name: user._id, displayName: user._id.split('/')[2] });
 | 
			
		||||
                    obj.webAuthnReqistrationRequest = { action: 'webauthn-startregister', keyname: command.name, request: registrationOptions };
 | 
			
		||||
                    ws.send(JSON.stringify(obj.webAuthnReqistrationRequest));
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
            case 'webauthn-endregister':
 | 
			
		||||
                {
 | 
			
		||||
                    if (parent.parent.config.settings.no2factorauth === true) return;
 | 
			
		||||
                    if ((obj.webAuthnReqistrationRequest == null) || (parent.f2l == null)) return;
 | 
			
		||||
                    if (obj.webAuthnReqistrationRequest == null) return;
 | 
			
		||||
 | 
			
		||||
                    // Figure out the origin
 | 
			
		||||
                    var httpport = ((args.aliasport != null) ? args.aliasport : args.port);
 | 
			
		||||
                    var origin = "https://" + (domain.dns ? domain.dns : parent.certificates.CommonName);
 | 
			
		||||
                    if (httpport != 443) { origin += ':' + httpport; }
 | 
			
		||||
 | 
			
		||||
                    var attestationExpectations = {
 | 
			
		||||
                        challenge: obj.webAuthnReqistrationRequest.request.challenge.split('+').join('-').split('/').join('_').split('=').join(''), // Convert to Base64URL
 | 
			
		||||
                        origin: origin,
 | 
			
		||||
                        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) {
 | 
			
		||||
                    // Use internal WebAuthn module to check the response
 | 
			
		||||
                    var regResult = null;
 | 
			
		||||
                    try { regResult = parent.webauthn.verifyAuthenticatorAttestationResponse(command.response.response); } catch (ex) { regResult = { verified: false, error: ex }; }
 | 
			
		||||
                    if (regResult.verified === true) {
 | 
			
		||||
                        // Since we are registering a WebAuthn/FIDO2 key, remove all U2F keys (Type 1).
 | 
			
		||||
                        var otphkeys2 = [];
 | 
			
		||||
                        if (user.otphkeys && Array.isArray(user.otphkeys)) { for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type != 1) { otphkeys2.push(user.otphkeys[i]); } } }
 | 
			
		||||
| 
						 | 
				
			
			@ -2368,7 +2291,7 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
 | 
			
		|||
                        // Add the new WebAuthn/FIDO2 keys
 | 
			
		||||
                        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 });
 | 
			
		||||
                        user.otphkeys.push({ name: obj.webAuthnReqistrationRequest.keyname, type: 3, publicKey: regResult.authrInfo.publicKey, counter: regResult.authrInfo.counter, keyIndex: keyIndex, keyId: regResult.authrInfo.keyId });
 | 
			
		||||
                        parent.db.SetUser(user);
 | 
			
		||||
                        ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: true, name: command.name, index: keyIndex }));
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -2376,10 +2299,10 @@ module.exports.CreateMeshUser = function (parent, db, ws, req, args, domain, use
 | 
			
		|||
                        var targets = ['*', 'server-users', user._id];
 | 
			
		||||
                        if (user.groups) { for (var i in user.groups) { targets.push('server-users:' + i); } }
 | 
			
		||||
                        parent.parent.DispatchEvent(targets, 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 }));
 | 
			
		||||
                    });
 | 
			
		||||
                    } else {
 | 
			
		||||
                        //console.log('webauthn-endregister-error', regResult.error);
 | 
			
		||||
                        ws.send(JSON.stringify({ action: 'otp-hkey-setup-response', result: false, error: regResult.error, name: command.name, index: keyIndex }));
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    delete obj.hardwareKeyRegistrationRequest;
 | 
			
		||||
                    break;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										264
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										264
									
								
								package-lock.json
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "meshcentral",
 | 
			
		||||
  "version": "0.3.1-h",
 | 
			
		||||
  "version": "0.3.4-l",
 | 
			
		||||
  "lockfileVersion": 1,
 | 
			
		||||
  "requires": true,
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
| 
						 | 
				
			
			@ -9,7 +9,7 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/@davedoesdev/fido2-lib/-/fido2-lib-2.4.0.tgz",
 | 
			
		||||
      "integrity": "sha512-qBNCio4amWMxDe7e8VG3wOo3iu/cFxzLpZ+vzRhLDYbQpZVBhJxFyYXCak33+1qPkDHJOSLZBU7x5jyruU1RMA==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@peculiar/webcrypto": "1.0.8",
 | 
			
		||||
        "@peculiar/webcrypto": "1.0.10",
 | 
			
		||||
        "asn1js": "2.0.22",
 | 
			
		||||
        "cbor": "4.1.5",
 | 
			
		||||
        "cose-to-jwk": "1.1.0",
 | 
			
		||||
| 
						 | 
				
			
			@ -19,6 +19,27 @@
 | 
			
		|||
        "psl": "1.1.31"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@fidm/asn1": {
 | 
			
		||||
      "version": "1.0.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@fidm/asn1/-/asn1-1.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-esd1jyNvRb2HVaQGq2Gg8Z0kbQPXzV9Tq5Z14KNIov6KfFD6PTaRIO8UpcsYiTNzOqJpmyzWgVTrUwFV3UF4TQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "@fidm/x509": {
 | 
			
		||||
      "version": "1.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@fidm/x509/-/x509-1.2.1.tgz",
 | 
			
		||||
      "integrity": "sha512-nwc2iesjyc9hkuzcrMCBXQRn653XuAUKorfWM8PZyJawiy1QzLj4vahwzaI25+pfpwOLvMzbJ0uKpWLDNmo16w==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@fidm/asn1": "1.0.4",
 | 
			
		||||
        "tweetnacl": "1.0.1"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "tweetnacl": {
 | 
			
		||||
          "version": "1.0.1",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.1.tgz",
 | 
			
		||||
          "integrity": "sha512-kcoMoKTPYnoeS50tzoqjPY3Uv9axeuuFAZY9M/9zFnhoVvRfxz9K29IMPD7jGmt2c8SW7i3gT9WqDl2+nV7p4A=="
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@peculiar/asn1-schema": {
 | 
			
		||||
      "version": "1.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-1.0.3.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -37,22 +58,31 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@peculiar/webcrypto": {
 | 
			
		||||
      "version": "1.0.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.0.8.tgz",
 | 
			
		||||
      "integrity": "sha512-zOCwDimHbQeUgSdBCHnw/69wbA1ks+aUt1MPAttcWNOlXaZjbUppc8shtwX/mzDQYctzNGFlI8Xrayl7lBUe5A==",
 | 
			
		||||
      "version": "1.0.10",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@peculiar/webcrypto/-/webcrypto-1.0.10.tgz",
 | 
			
		||||
      "integrity": "sha512-Ohkfu3xgk/Y/6+40Uq8juAO1zPuuAuSJ/BLpP4xovjU3+u3J7Rc/dbcWB4/Z2sj+0L4hZXh6p4rZsvzDFk22QQ==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@peculiar/asn1-schema": "1.0.3",
 | 
			
		||||
        "@peculiar/json-schema": "1.1.5",
 | 
			
		||||
        "asn1js": "2.0.22",
 | 
			
		||||
        "pvtsutils": "1.0.4",
 | 
			
		||||
        "tslib": "1.9.3",
 | 
			
		||||
        "webcrypto-core": "1.0.11"
 | 
			
		||||
        "webcrypto-core": "1.0.12"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@types/node": {
 | 
			
		||||
      "version": "10.14.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.4.tgz",
 | 
			
		||||
      "integrity": "sha512-DT25xX/YgyPKiHFOpNuANIQIVvYEwCWXgK2jYYwqgaMrYE6+tq+DtmMwlD3drl6DJbUwtlIDnn0d7tIn/EbXBg=="
 | 
			
		||||
      "version": "10.14.6",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/node/-/node-10.14.6.tgz",
 | 
			
		||||
      "integrity": "sha512-Fvm24+u85lGmV4hT5G++aht2C5I4Z4dYlWZIh62FAfFO/TfzXtPpoLI6I7AuBWkIFqZCnhFOoTT7RjjaIL5Fjg=="
 | 
			
		||||
    },
 | 
			
		||||
    "abstract-leveldown": {
 | 
			
		||||
      "version": "6.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-jzewKKpZbaYUa6HTThnrl+GrJhzjEAeuc7hTVpZdzg7kupXZFoqQDFwyOwLNbmJKJlmzw8yiipMPkDiuKkT06Q==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "level-concat-iterator": "2.0.1",
 | 
			
		||||
        "xtend": "4.0.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "accepts": {
 | 
			
		||||
      "version": "1.3.5",
 | 
			
		||||
| 
						 | 
				
			
			@ -430,9 +460,9 @@
 | 
			
		|||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "combined-stream": {
 | 
			
		||||
      "version": "1.0.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.7.tgz",
 | 
			
		||||
      "integrity": "sha512-brWl9y6vOB1xYPZcpZde3N9zDByXTosAeMDo4p1wzo6UMOX4vumB+TP1RZ76sfE6Md68Q0NJSrE/gbezd4Ul+w==",
 | 
			
		||||
      "version": "1.0.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
 | 
			
		||||
      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "delayed-stream": "1.0.0"
 | 
			
		||||
      }
 | 
			
		||||
| 
						 | 
				
			
			@ -652,6 +682,15 @@
 | 
			
		|||
      "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
 | 
			
		||||
      "optional": true
 | 
			
		||||
    },
 | 
			
		||||
    "deferred-leveldown": {
 | 
			
		||||
      "version": "5.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-BXohsvTedWOLkj2n/TY+yqVlrCWa2Zs8LSxh3uCAgFOru7/pjxKyZAexGa1j83BaKloER4PqUyQ9rGPJLt9bqA==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "abstract-leveldown": "6.0.3",
 | 
			
		||||
        "inherits": "2.0.3"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "define-properties": {
 | 
			
		||||
      "version": "1.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -724,6 +763,17 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
 | 
			
		||||
      "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k="
 | 
			
		||||
    },
 | 
			
		||||
    "encoding-down": {
 | 
			
		||||
      "version": "6.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-oAEANslmNb64AF4kvHXjTxB7KecwD7X0qf8MffMfhpjP6gjGcnCTOkRgps/1yUNeR4Bhe6ckN6aAzZz+RIYgTw==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "abstract-leveldown": "6.0.3",
 | 
			
		||||
        "inherits": "2.0.3",
 | 
			
		||||
        "level-codec": "9.0.1",
 | 
			
		||||
        "level-errors": "2.0.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "end-of-stream": {
 | 
			
		||||
      "version": "1.4.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -732,6 +782,14 @@
 | 
			
		|||
        "once": "1.4.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "errno": {
 | 
			
		||||
      "version": "0.1.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
 | 
			
		||||
      "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "prr": "1.0.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "es6-promise": {
 | 
			
		||||
      "version": "3.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -872,6 +930,11 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
 | 
			
		||||
      "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
 | 
			
		||||
    },
 | 
			
		||||
    "fast-future": {
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/fast-future/-/fast-future-1.0.2.tgz",
 | 
			
		||||
      "integrity": "sha1-hDWpqqAteSSNF9cE52JZMB2ZKAo="
 | 
			
		||||
    },
 | 
			
		||||
    "fast-json-stable-stringify": {
 | 
			
		||||
      "version": "2.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -915,7 +978,7 @@
 | 
			
		|||
      "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "asynckit": "0.4.0",
 | 
			
		||||
        "combined-stream": "1.0.7",
 | 
			
		||||
        "combined-stream": "1.0.8",
 | 
			
		||||
        "mime-types": "2.1.21"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -1104,6 +1167,11 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
 | 
			
		||||
      "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
 | 
			
		||||
    },
 | 
			
		||||
    "iso-3166-1": {
 | 
			
		||||
      "version": "1.1.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/iso-3166-1/-/iso-3166-1-1.1.0.tgz",
 | 
			
		||||
      "integrity": "sha1-gGrfYoPV96pAXRY3qKUC+R0sVHw="
 | 
			
		||||
    },
 | 
			
		||||
    "isstream": {
 | 
			
		||||
      "version": "0.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -1185,6 +1253,107 @@
 | 
			
		|||
        "readable-stream": "2.3.6"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "level": {
 | 
			
		||||
      "version": "5.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/level/-/level-5.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-wcak5OQeA4rURGacqS62R/xNHjCYnJSQDBOlm4KNUGJVE9bWv2B04TclqReYejN+oD65PzD4FsqeWoI5wNC5Lg==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "level-js": "4.0.1",
 | 
			
		||||
        "level-packager": "5.0.1",
 | 
			
		||||
        "leveldown": "5.0.3",
 | 
			
		||||
        "opencollective-postinstall": "2.0.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "level-codec": {
 | 
			
		||||
      "version": "9.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-ajFP0kJ+nyq4i6kptSM+mAvJKLOg1X5FiFPtLG9M5gCEZyBmgDi3FkDrvlMkEzrUn1cWxtvVmrvoS4ASyO/q+Q=="
 | 
			
		||||
    },
 | 
			
		||||
    "level-concat-iterator": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw=="
 | 
			
		||||
    },
 | 
			
		||||
    "level-errors": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "errno": "0.1.7"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "level-iterator-stream": {
 | 
			
		||||
      "version": "4.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-pSZWqXK6/yHQkZKCHrR59nKpU5iqorKM22C/BOHTb/cwNQ2EOZG+bovmFFGcOgaBoF3KxqJEI27YwewhJQTzsw==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "inherits": "2.0.3",
 | 
			
		||||
        "readable-stream": "3.3.0",
 | 
			
		||||
        "xtend": "4.0.1"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "readable-stream": {
 | 
			
		||||
          "version": "3.3.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.3.0.tgz",
 | 
			
		||||
          "integrity": "sha512-EsI+s3k3XsW+fU8fQACLN59ky34AZ14LoeVZpYwmZvldCFo0r0gnelwF2TcMjLor/BTL5aDJVBMkss0dthToPw==",
 | 
			
		||||
          "requires": {
 | 
			
		||||
            "inherits": "2.0.3",
 | 
			
		||||
            "string_decoder": "1.1.1",
 | 
			
		||||
            "util-deprecate": "1.0.2"
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "level-js": {
 | 
			
		||||
      "version": "4.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/level-js/-/level-js-4.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-m5JRIyHZn5VnCCFeRegJkn5bQd3MJK5qZX12zg3Oivc8+BUIS2yFS6ANMMeHX2ieGxucNvEn6/ZnyjmZQLLUWw==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "abstract-leveldown": "6.0.3",
 | 
			
		||||
        "immediate": "3.2.3",
 | 
			
		||||
        "inherits": "2.0.3",
 | 
			
		||||
        "ltgt": "2.2.1",
 | 
			
		||||
        "typedarray-to-buffer": "3.1.5"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "immediate": {
 | 
			
		||||
          "version": "3.2.3",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.2.3.tgz",
 | 
			
		||||
          "integrity": "sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw="
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "level-packager": {
 | 
			
		||||
      "version": "5.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-tigB8g7xnFE5es2d/OmGJvcJC9S+FQfJnnULSLbPr53/ABPIaCarCxofkwdBhnJxjLZCNvj/Je6luFj/XeuaUQ==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "encoding-down": "6.0.2",
 | 
			
		||||
        "levelup": "4.0.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "leveldown": {
 | 
			
		||||
      "version": "5.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-isfWtOQIXbGbQRI8nmU9FqCZM0klmqTAOFi0vF6G/D0O1ZgxLrSh6Xd4Zj9iVQfGt6+8jpYwkRbN07VLrxRM8w==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "abstract-leveldown": "6.0.3",
 | 
			
		||||
        "fast-future": "1.0.2",
 | 
			
		||||
        "napi-macros": "1.8.2",
 | 
			
		||||
        "node-gyp-build": "3.8.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "levelup": {
 | 
			
		||||
      "version": "4.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.0.1.tgz",
 | 
			
		||||
      "integrity": "sha512-l7KXOkINXHgNqmz0v9bxvRnMCUG4gmShFrzFSZXXhcqFnfvKAW8NerVsTICpZtVhGOMAmhY6JsVoVh/tUPBmdg==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "deferred-leveldown": "5.0.1",
 | 
			
		||||
        "level-errors": "2.0.1",
 | 
			
		||||
        "level-iterator-stream": "4.0.1",
 | 
			
		||||
        "xtend": "4.0.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "lie": {
 | 
			
		||||
      "version": "3.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/lie/-/lie-3.1.1.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -1251,6 +1420,11 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/longest/-/longest-1.0.1.tgz",
 | 
			
		||||
      "integrity": "sha1-MKCy2jj3N3DoKUoNIuZiXtd9AJc="
 | 
			
		||||
    },
 | 
			
		||||
    "ltgt": {
 | 
			
		||||
      "version": "2.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz",
 | 
			
		||||
      "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU="
 | 
			
		||||
    },
 | 
			
		||||
    "media-typer": {
 | 
			
		||||
      "version": "0.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -1373,7 +1547,7 @@
 | 
			
		|||
    "mongojs": {
 | 
			
		||||
      "version": "2.6.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/mongojs/-/mongojs-2.6.0.tgz",
 | 
			
		||||
      "integrity": "sha512-r6tj71DjYcaRTi2jpa+CA6Iq72cTZclB2JKy+Zub+0JPTEq/l2plsAYfF2eHqSYBtZbKNcObvhGYk9E9UKZWJg==",
 | 
			
		||||
      "integrity": "sha1-Mz9YBiGg3GSACGXjjjam5h/xaYk=",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "each-series": "1.0.0",
 | 
			
		||||
        "mongodb": "2.2.36",
 | 
			
		||||
| 
						 | 
				
			
			@ -1433,6 +1607,11 @@
 | 
			
		|||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "napi-macros": {
 | 
			
		||||
      "version": "1.8.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-1.8.2.tgz",
 | 
			
		||||
      "integrity": "sha512-Tr0DNY4RzTaBG2W2m3l7ZtFuJChTH6VZhXVhkGGjF/4cZTt+i8GcM9ozD+30Lmr4mDoZ5Xx34t2o4GJqYWDGcg=="
 | 
			
		||||
    },
 | 
			
		||||
    "nedb": {
 | 
			
		||||
      "version": "1.8.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/nedb/-/nedb-1.8.0.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -1455,6 +1634,11 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.7.6.tgz",
 | 
			
		||||
      "integrity": "sha1-/fO0GK7h+U8O9kLNY0hsd8qXJKw="
 | 
			
		||||
    },
 | 
			
		||||
    "node-gyp-build": {
 | 
			
		||||
      "version": "3.8.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-3.8.0.tgz",
 | 
			
		||||
      "integrity": "sha512-bYbpIHyRqZ7sVWXxGpz8QIRug5JZc/hzZH4GbdT9HTZi6WmKCZ8GLvP8OZ9TTiIBvwPFKgtGrlWQSXDAvYdsPw=="
 | 
			
		||||
    },
 | 
			
		||||
    "node-jose": {
 | 
			
		||||
      "version": "1.1.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/node-jose/-/node-jose-1.1.3.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -1464,7 +1648,7 @@
 | 
			
		|||
        "es6-promise": "4.2.6",
 | 
			
		||||
        "lodash": "4.17.11",
 | 
			
		||||
        "long": "4.0.0",
 | 
			
		||||
        "node-forge": "0.8.2",
 | 
			
		||||
        "node-forge": "0.8.3",
 | 
			
		||||
        "uuid": "3.3.2"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
| 
						 | 
				
			
			@ -1474,9 +1658,9 @@
 | 
			
		|||
          "integrity": "sha512-aRVgGdnmW2OiySVPUC9e6m+plolMAJKjZnQlCwNSuK5yQ0JN61DZSO1X1Ufd1foqWRAlig0rhduTCHe7sVtK5Q=="
 | 
			
		||||
        },
 | 
			
		||||
        "node-forge": {
 | 
			
		||||
          "version": "0.8.2",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.2.tgz",
 | 
			
		||||
          "integrity": "sha512-mXQ9GBq1N3uDCyV1pdSzgIguwgtVpM7f5/5J4ipz12PKWElmPpVWLDuWl8iXmhysr21+WmX/OJ5UKx82wjomgg=="
 | 
			
		||||
          "version": "0.8.3",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/node-forge/-/node-forge-0.8.3.tgz",
 | 
			
		||||
          "integrity": "sha512-5lv9UKmvTBog+m4AWL8XpZnr3WbNKxYL2M77i903ylY/huJIooSTDHyUWQ/OppFuKQpAGMk6qNtDymSJNRIEIg=="
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -1541,6 +1725,11 @@
 | 
			
		|||
        "wrappy": "1.0.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "opencollective-postinstall": {
 | 
			
		||||
      "version": "2.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz",
 | 
			
		||||
      "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw=="
 | 
			
		||||
    },
 | 
			
		||||
    "optimist": {
 | 
			
		||||
      "version": "0.6.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -1637,6 +1826,11 @@
 | 
			
		|||
        "ipaddr.js": "1.8.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "prr": {
 | 
			
		||||
      "version": "1.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
 | 
			
		||||
      "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY="
 | 
			
		||||
    },
 | 
			
		||||
    "psl": {
 | 
			
		||||
      "version": "1.1.31",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/psl/-/psl-1.1.31.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -1652,7 +1846,7 @@
 | 
			
		|||
      "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.0.4.tgz",
 | 
			
		||||
      "integrity": "sha512-lBDyLfPIWZjxHr6Nnl83/iaZgVLczDcpEqWdqRnghzBKXifRU/7D5T6JPYWUAm0sJdFeF9+sNTKto6dj/3P/Kg==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@types/node": "10.14.4",
 | 
			
		||||
        "@types/node": "10.14.6",
 | 
			
		||||
        "tslib": "1.9.3"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
| 
						 | 
				
			
			@ -1739,7 +1933,7 @@
 | 
			
		|||
        "aws-sign2": "0.7.0",
 | 
			
		||||
        "aws4": "1.8.0",
 | 
			
		||||
        "caseless": "0.12.0",
 | 
			
		||||
        "combined-stream": "1.0.7",
 | 
			
		||||
        "combined-stream": "1.0.8",
 | 
			
		||||
        "extend": "3.0.2",
 | 
			
		||||
        "forever-agent": "0.6.1",
 | 
			
		||||
        "form-data": "2.3.3",
 | 
			
		||||
| 
						 | 
				
			
			@ -1958,6 +2152,14 @@
 | 
			
		|||
        "mime-types": "2.1.21"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "typedarray-to-buffer": {
 | 
			
		||||
      "version": "3.1.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
 | 
			
		||||
      "integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "is-typedarray": "1.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "uglify-js": {
 | 
			
		||||
      "version": "2.8.29",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-2.8.29.tgz",
 | 
			
		||||
| 
						 | 
				
			
			@ -2022,7 +2224,7 @@
 | 
			
		|||
    "uuid": {
 | 
			
		||||
      "version": "3.3.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
 | 
			
		||||
      "integrity": "sha1-G0r0lV6zB3xQHCOHL8ZROBFYcTE="
 | 
			
		||||
      "integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
 | 
			
		||||
    },
 | 
			
		||||
    "vary": {
 | 
			
		||||
      "version": "1.1.2",
 | 
			
		||||
| 
						 | 
				
			
			@ -2039,10 +2241,24 @@
 | 
			
		|||
        "extsprintf": "1.3.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "webauthn": {
 | 
			
		||||
      "version": "0.1.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/webauthn/-/webauthn-0.1.1.tgz",
 | 
			
		||||
      "integrity": "sha512-UEMV9M1MKK1EscfTKNU8/7Y+C31e+2bkgV3Pp66k4deHvBw8qDD4TNCAtaRKdPlPy8g0aZ/8sI1Q4obMPPwbEQ==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@fidm/x509": "1.2.1",
 | 
			
		||||
        "base64url": "3.0.1",
 | 
			
		||||
        "cbor": "4.1.5",
 | 
			
		||||
        "express": "4.16.4",
 | 
			
		||||
        "iso-3166-1": "1.1.0",
 | 
			
		||||
        "level": "5.0.1",
 | 
			
		||||
        "lodash": "4.17.11"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "webcrypto-core": {
 | 
			
		||||
      "version": "1.0.11",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.0.11.tgz",
 | 
			
		||||
      "integrity": "sha512-cHxOMKNzRkPlCcQyGTuyaZmAVZlbby0lH9ASaFk5w8x/i3Q809sISA28AMVnkebW2ekcQ2+UtdaVBFUf58xjyA==",
 | 
			
		||||
      "version": "1.0.12",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/webcrypto-core/-/webcrypto-core-1.0.12.tgz",
 | 
			
		||||
      "integrity": "sha512-U+qBSJY4u+Ev5KNiCOPZ5MwiCSDdoM9gxeYLdnr3q8LWnX6GOBJqcowhl/kOplbhHtaa4IVZK7R2zNC4fJ7/RQ==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "pvtsutils": "1.0.4",
 | 
			
		||||
        "tslib": "1.9.3"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,6 @@
 | 
			
		|||
{
 | 
			
		||||
  "name": "meshcentral",
 | 
			
		||||
  "version": "0.3.4-k",
 | 
			
		||||
  "version": "0.3.4-l",
 | 
			
		||||
  "keywords": [
 | 
			
		||||
    "Remote Management",
 | 
			
		||||
    "Intel AMT",
 | 
			
		||||
| 
						 | 
				
			
			@ -29,6 +29,7 @@
 | 
			
		|||
  "dependencies": {
 | 
			
		||||
    "archiver": "^3.0.0",
 | 
			
		||||
    "body-parser": "^1.18.2",
 | 
			
		||||
    "cbor": "^4.1.5",
 | 
			
		||||
    "compression": "^1.7.3",
 | 
			
		||||
    "connect-redis": "^3.4.0",
 | 
			
		||||
    "cookie-session": "^2.0.0-beta.3",
 | 
			
		||||
| 
						 | 
				
			
			@ -38,10 +39,10 @@
 | 
			
		|||
    "ipcheck": "^0.1.0",
 | 
			
		||||
    "meshcentral": "*",
 | 
			
		||||
    "minimist": "^1.2.0",
 | 
			
		||||
    "mongojs": "^2.6.0",
 | 
			
		||||
    "multiparty": "^4.2.1",
 | 
			
		||||
    "nedb": "^1.8.0",
 | 
			
		||||
    "node-forge": "^0.7.6",
 | 
			
		||||
    "node-windows": "^0.1.14",
 | 
			
		||||
    "ws": "^6.1.2",
 | 
			
		||||
    "xmldom": "^0.1.27",
 | 
			
		||||
    "yauzl": "^2.10.0"
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| 
						 | 
				
			
			@ -1561,7 +1561,6 @@
 | 
			
		|||
                    }
 | 
			
		||||
                    x += "</div>";
 | 
			
		||||
                    x += "<div><input type=button value='Close' onclick=setDialogMode(0) style=float:right></input>";
 | 
			
		||||
                    if ((features & 0x00020000) == 0) { x += "<input id=d2addkey1 type=button value='Add Key' onclick='account_addhkey(1);'></input>"; }
 | 
			
		||||
                    if ((features & 0x00020000) != 0) { x += "<input id=d2addkey3 type=button value='Add Key' onclick='account_addhkey(3);'></input>"; }
 | 
			
		||||
                    if ((features & 0x00004000) != 0) { x += "<input id=d2addkey2 type=button value='Add YubiKey® OTP' onclick='account_addhkey(2);'></input>"; }
 | 
			
		||||
                    x += "</div><br />";
 | 
			
		||||
| 
						 | 
				
			
			@ -1577,23 +1576,6 @@
 | 
			
		|||
                    }
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                case 'otp-hkey-setup-request': {
 | 
			
		||||
                    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);
 | 
			
		||||
                    window.u2f.register(message.request.appId, message.request.registerRequests, message.request.registeredKeys, function (registrationResponse) {
 | 
			
		||||
                        if (registrationResponse.registrationData) {
 | 
			
		||||
                            meshserver.send({ action: 'otp-hkey-setup-response', response: registrationResponse, name: Q('dp1keyname').value });
 | 
			
		||||
                            setDialogMode(2, "Add Security Key", 0, null, '<br />Checking...<br /><br /><br />', 'otpauth-hardware-manage');
 | 
			
		||||
                        } else {
 | 
			
		||||
                            if (xxdialogMode && (xxdialogTag != 'otpauth-hardware-manage')) {
 | 
			
		||||
                                var errorCodes = ['', 'Unknown error', 'Bad request', 'Unsupported configuration', 'This key was already registered', 'Timeout'];
 | 
			
		||||
                                setDialogMode(2, "Add Security Key", 1, null, '<br />' + errorCodes[registrationResponse.errorCode] + '.<br /><br />');
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                    }, message.request.timeoutSeconds);
 | 
			
		||||
                    break;
 | 
			
		||||
                }
 | 
			
		||||
                case 'otp-hkey-setup-response': {
 | 
			
		||||
                    if (xxdialogMode && (xxdialogTag != 'otpauth-hardware-manage')) return;
 | 
			
		||||
                    if (message.result == true) {
 | 
			
		||||
| 
						 | 
				
			
			@ -5777,7 +5759,7 @@
 | 
			
		|||
        }
 | 
			
		||||
 | 
			
		||||
        function account_addhkey(type) {
 | 
			
		||||
            if (type == 1 || type == 3) {
 | 
			
		||||
            if (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) {
 | 
			
		||||
| 
						 | 
				
			
			@ -5796,9 +5778,7 @@
 | 
			
		|||
        function account_addhkeyEx(button, type) {
 | 
			
		||||
            var name = Q('dp1keyname').value;
 | 
			
		||||
            if (name == '') { name = 'MyKey'; }
 | 
			
		||||
            if (type == 1) {
 | 
			
		||||
                meshserver.send({ action: 'otp-hkey-setup-request', name: name });
 | 
			
		||||
            } else if (type == 2) {
 | 
			
		||||
            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) {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										148
									
								
								webserver.js
									
										
									
									
									
								
							
							
						
						
									
										148
									
								
								webserver.js
									
										
									
									
									
								
							| 
						 | 
				
			
			@ -63,7 +63,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
 | 
			
		|||
    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 / FIDO2
 | 
			
		||||
    try { const { Fido2Lib } = require("@davedoesdev/fido2-lib"); obj.f2l = new Fido2Lib({ attestation: "none" }); } catch (ex) { }
 | 
			
		||||
    obj.webauthn = require("./webauthn.js").CreateWebAuthnModule();
 | 
			
		||||
 | 
			
		||||
    // Variables
 | 
			
		||||
    obj.parent = parent;
 | 
			
		||||
| 
						 | 
				
			
			@ -503,66 +503,50 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
 | 
			
		|||
        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;
 | 
			
		||||
            if ((authResponse != 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 = Buffer.from(authResponse.id, 'base64');
 | 
			
		||||
                    clientAssertionResponse.response.authenticatorData = Buffer.from(authResponse.authenticatorData, 'base64');
 | 
			
		||||
                    clientAssertionResponse.response.clientDataJSON = Buffer.from(authResponse.clientDataJSON, 'base64');
 | 
			
		||||
                    clientAssertionResponse.response.signature = Buffer.from(authResponse.signature, 'base64');
 | 
			
		||||
                    clientAssertionResponse.response.userHandle = Buffer.from(authResponse.userHandle, 'base64');
 | 
			
		||||
 | 
			
		||||
                        // 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]; } }
 | 
			
		||||
                    // 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) {
 | 
			
		||||
                            // Figure out the origin
 | 
			
		||||
                            var httpport = ((args.aliasport != null) ? args.aliasport : args.port);
 | 
			
		||||
                            var origin = "https://" + (domain.dns ? domain.dns : parent.certificates.CommonName);
 | 
			
		||||
                            if (httpport != 443) { origin += ':' + httpport; }
 | 
			
		||||
                    // If we found a valid key to use, let's validate the response
 | 
			
		||||
                    if (webAuthnKey != null) {
 | 
			
		||||
                        // Figure out the origin
 | 
			
		||||
                        var httpport = ((args.aliasport != null) ? args.aliasport : args.port);
 | 
			
		||||
                        var origin = "https://" + (domain.dns ? domain.dns : parent.certificates.CommonName);
 | 
			
		||||
                        if (httpport != 443) { origin += ':' + httpport; }
 | 
			
		||||
 | 
			
		||||
                            var assertionExpectations = {
 | 
			
		||||
                                challenge: req.session.u2fchallenge,
 | 
			
		||||
                                origin: origin,
 | 
			
		||||
                                factor: "either",
 | 
			
		||||
                                publicKey: webAuthnKey.publicKey,
 | 
			
		||||
                                prevCounter: webAuthnKey.counter,
 | 
			
		||||
                                userHandle: Buffer(user._id, 'binary').toString('base64')
 | 
			
		||||
                            };
 | 
			
		||||
                        var assertionExpectations = {
 | 
			
		||||
                            challenge: req.session.u2fchallenge,
 | 
			
		||||
                            origin: origin,
 | 
			
		||||
                            factor: "either",
 | 
			
		||||
                            fmt: "fido-u2f",
 | 
			
		||||
                            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
 | 
			
		||||
                    var u2fKeys = [];
 | 
			
		||||
                    for (var i = 0; i < user.otphkeys.length; i++) { if (user.otphkeys[i].type == 1) { u2fKeys.push(user.otphkeys[i]); } }
 | 
			
		||||
                    if (u2fKeys.length > 0) {
 | 
			
		||||
                        // Check authentication response
 | 
			
		||||
                        var authdoglib = null;
 | 
			
		||||
                        try { authdoglib = require('authdog'); } catch (ex) { }
 | 
			
		||||
                        if (authdoglib == null) { func(false); } else {
 | 
			
		||||
                            authdoglib.finishAuthentication(req.session.u2fchallenge, authResponse, u2fKeys).then(function (authenticationStatus) { func(true); }, function (error) { console.log(error); func(false); });
 | 
			
		||||
                        var webauthnResponse = null;
 | 
			
		||||
                        try { webauthnResponse = obj.webauthn.verifyAuthenticatorAssertionResponse(clientAssertionResponse.response, assertionExpectations); } catch (ex) { console.log(ex); }
 | 
			
		||||
                        if ((webauthnResponse != null) && (webauthnResponse.verified === true)) {
 | 
			
		||||
                            // Update the hardware key counter and accept the 2nd factor
 | 
			
		||||
                            webAuthnKey.counter = webauthnResponse.counter;
 | 
			
		||||
                            obj.db.SetUser(user);
 | 
			
		||||
                            func(true);
 | 
			
		||||
                        } else {
 | 
			
		||||
                            func(false);
 | 
			
		||||
                        }
 | 
			
		||||
                        return;
 | 
			
		||||
                    }
 | 
			
		||||
| 
						 | 
				
			
			@ -607,46 +591,14 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
 | 
			
		|||
        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[i].keyId); }
 | 
			
		||||
                        req.session.u2fchallenge = authnOptions.challenge = Buffer(authnOptions.challenge).toString('base64');
 | 
			
		||||
                        func(JSON.stringify(authnOptions));
 | 
			
		||||
                    }, function (error) {
 | 
			
		||||
                        console.log('assertionOptions-Error', error);
 | 
			
		||||
                        func('');
 | 
			
		||||
                    });
 | 
			
		||||
                    return;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            var authdoglib = null;
 | 
			
		||||
            try { authdoglib = require('authdog'); } catch (ex) { }
 | 
			
		||||
            if (authdoglib != null) {
 | 
			
		||||
                // 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]); } }
 | 
			
		||||
 | 
			
		||||
                // Generate a U2F challenge
 | 
			
		||||
                if (u2fKeys.length > 0) {
 | 
			
		||||
                    authdoglib.startAuthentication('https://' + obj.parent.certificates.CommonName, u2fKeys, { requestId: 0, timeoutSeconds: 60 }).then(function (registrationRequest) {
 | 
			
		||||
                        // Save authentication request to session for later use
 | 
			
		||||
                        req.session.u2fchallenge = registrationRequest;
 | 
			
		||||
 | 
			
		||||
                        // Send authentication request to client
 | 
			
		||||
                        func(JSON.stringify(registrationRequest));
 | 
			
		||||
                    }, function (error) {
 | 
			
		||||
                        // Handle authentication request error
 | 
			
		||||
                        func('');
 | 
			
		||||
                    });
 | 
			
		||||
                } else {
 | 
			
		||||
                    func('');
 | 
			
		||||
                }
 | 
			
		||||
            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) {
 | 
			
		||||
                // Generate a Webauthn challenge, this is really easy, no need to call any modules to do this.
 | 
			
		||||
                var authnOptions = { type: 'webAuthn', keyIds: [], timeout: 60000, challenge: obj.crypto.randomBytes(64).toString('base64') };
 | 
			
		||||
                for (var i = 0; i < webAuthnKeys.length; i++) { authnOptions.keyIds.push(webAuthnKeys[i].keyId); }
 | 
			
		||||
                req.session.u2fchallenge = authnOptions.challenge;
 | 
			
		||||
                func(JSON.stringify(authnOptions));
 | 
			
		||||
            }
 | 
			
		||||
        } else {
 | 
			
		||||
            func('');
 | 
			
		||||
| 
						 | 
				
			
			@ -1338,7 +1290,7 @@ module.exports.CreateWebServer = function (parent, db, args, certificates) {
 | 
			
		|||
            if ((parent.config.settings.no2factorauth !== true) && domain.yubikey && domain.yubikey.id && domain.yubikey.secret) { features += 0x00004000; } // Indicates Yubikey support
 | 
			
		||||
            if (domain.geolocation == true) { features += 0x00008000; } // Enable geo-location features
 | 
			
		||||
            if ((domain.passwordrequirements != null) && (domain.passwordrequirements.hint === true)) { features += 0x00010000; } // Enable password hints
 | 
			
		||||
            if ((parent.config.settings.no2factorauth !== true) && (obj.f2l != null)) { features += 0x00020000; } // Enable WebAuthn/FIDO2 support
 | 
			
		||||
            if (parent.config.settings.no2factorauth !== true) { features += 0x00020000; } // Enable WebAuthn/FIDO2 support
 | 
			
		||||
            if ((obj.args.nousers != true) && (domain.passwordrequirements != null) && (domain.passwordrequirements.force2factor === true)) { features += 0x00040000; } // Force 2-factor auth
 | 
			
		||||
            if ((domain.auth == 'sspi') || (domain.auth == 'ldap')) { features += 0x00080000; } // LDAP or SSPI in use, warn that users must login first before adding a user to a group.
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue