diff --git a/MeshCentralServer.njsproj b/MeshCentralServer.njsproj index d3fb9da6..0fbff49c 100644 --- a/MeshCentralServer.njsproj +++ b/MeshCentralServer.njsproj @@ -130,6 +130,7 @@ + diff --git a/Webauthn.js b/Webauthn.js new file mode 100644 index 00000000..72f48b03 --- /dev/null +++ b/Webauthn.js @@ -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; +} diff --git a/meshcentral.js b/meshcentral.js index 2ecafbce..0ad8d743 100644 --- a/meshcentral.js +++ b/meshcentral.js @@ -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 diff --git a/meshuser.js b/meshuser.js index 771f1543..cf2936ae 100644 --- a/meshuser.js +++ b/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; diff --git a/package-lock.json b/package-lock.json index 2a9d7b13..6ef6764c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -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" diff --git a/package.json b/package.json index 4655fb9b..b8228c3a 100644 --- a/package.json +++ b/package.json @@ -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" diff --git a/views/default-min.handlebars b/views/default-min.handlebars index f6a85625..f2943de2 100644 --- a/views/default-min.handlebars +++ b/views/default-min.handlebars @@ -1 +1 @@ - {{{title}}}
{{{title}}}
{{{title2}}}

{{{logoutControl}}}

 

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

{{{logoutControl}}}

 

\ No newline at end of file diff --git a/views/default.handlebars b/views/default.handlebars index 49255500..a94eee17 100644 --- a/views/default.handlebars +++ b/views/default.handlebars @@ -1561,7 +1561,6 @@ } x += ""; x += "
"; - if ((features & 0x00020000) == 0) { x += ""; } if ((features & 0x00020000) != 0) { x += ""; } if ((features & 0x00004000) != 0) { x += ""; } x += "

"; @@ -1577,23 +1576,6 @@ } break; } - case 'otp-hkey-setup-request': { - if (xxdialogMode && (xxdialogTag != 'otpauth-hardware-manage')) return; - var x = "Press the key button now.

"; - 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, '
Checking...


', '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, '
' + errorCodes[registrationResponse.errorCode] + '.

'); - } - } - }, 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.

"; x += addHtmlValue('Key Name', ''); } 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, "
Checking...


", 'otpauth-hardware-manage'); } else if (type == 3) { diff --git a/webserver.js b/webserver.js index b98004cd..f6670342 100644 --- a/webserver.js +++ b/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.