From d432e2a77be3badfbdba5118e4c4b7a1c6640215 Mon Sep 17 00:00:00 2001 From: Ryan Tharp Date: Thu, 3 Oct 2019 02:48:24 -0700 Subject: [PATCH] verifyUserObjectDeviceMap() and refactor out verifyPrimaryPubKeys() --- js/modules/loki_file_server_api.js | 176 +++++++++++++++++++++++++++++ 1 file changed, 176 insertions(+) diff --git a/js/modules/loki_file_server_api.js b/js/modules/loki_file_server_api.js index ed12c61ad..be1d88985 100644 --- a/js/modules/loki_file_server_api.js +++ b/js/modules/loki_file_server_api.js @@ -1,3 +1,4 @@ +/* global dcodeIO, window, log, textsecure */ /* global storage: false */ /* global Signal: false */ @@ -10,6 +11,8 @@ function LokiFileServerAPIWrapper(serverUrl) { return LokiFileServerAPI.bind(null, serverUrl); } +// can have multiple of these objects instances as each user can have a +// different home server class LokiFileServerAPI { constructor(serverUrl, ourKey) { this.ourKey = ourKey; @@ -45,6 +48,179 @@ class LokiFileServerAPI { return users; } + async verifyUserObjectDeviceMap( + pubKeys, + isRequest, + iterator, + notFoundHandler + ) { + const users = await this.getDeviceMappingForUsers(pubKeys); + // log.info('verifyUserObjectDeviceMap Found', users.length, 'users') + + // go through each user and find deviceMap annotations + users.forEach(user => { + let found = false; + if (user.annotations) { + user.annotations.forEach(note => { + if (note.type === 'network.loki.messenger.devicemapping') { + // is desired type + if ( + (isRequest && note.value.isPrimary === '0') || + (!isRequest && note.value.isPrimary !== '0') + ) { + const { authorisations } = note.value; + if (Array.isArray(authorisations)) { + authorisations.forEach(auth => { + // log.info('devmap auth', auth); + // only skip, if in secondary search mode + if ( + isRequest && + auth.secondaryDevicePubKey !== user.username + ) { + // this is not the authorization we're looking for + log.info( + `Request and ${auth.secondaryDevicePubKey} != ${ + user.username + }` + ); + return; + } + // log.info('auth', auth); + try { + // request (secondary wants to be paired with this primary) + // grant (primary approves this secondary) + window.libloki.crypto.verifyPairingSignature( + auth.primaryDevicePubKey, + auth.secondaryDevicePubKey, + dcodeIO.ByteBuffer.wrap( + isRequest ? auth.requestSignature : auth.grantSignature, + 'base64' + ).toArrayBuffer(), + isRequest + ? textsecure.protobuf.PairingAuthorisationMessage.Type + .REQUEST + : textsecure.protobuf.PairingAuthorisationMessage.Type + .GRANT + ); + // log.info('auth is valid for', user.username) + if (iterator(user.username, auth)) { + found = true; + } + } catch (e) { + log.warn( + `Invalid signature on pubkey ${ + user.username + } authorization ${ + auth.secondaryDevicePubKey + } isRequest ${isRequest}` + ); + } + }); // end forEach authorisations + } + } + } + }); // end forEach annotations + } + if (notFoundHandler) { + if (found) { + return; + } + notFoundHandler(user.username); + } + }); // end forEach users + // log.info('done with users', users.length); + } + + // verifies list of pubKeys for any deviceMappings + // returns the relevant primary pubKeys + async verifyPrimaryPubKeys(pubKeys) { + const newSlavePrimaryMap = {}; // new slave to primary map + const checkSigs = {}; // cache for authorization + const primaryPubKeys = []; + + // go through multiDeviceResults and get primary Pubkey + await this.verifyUserObjectDeviceMap(pubKeys, true, (slaveKey, auth) => { + // log.info('slave iterator', slaveKey); + // if it doesn't throw, that means it's valid + // add map to newSlavePrimaryMap + if ( + newSlavePrimaryMap[slaveKey] && + newSlavePrimaryMap[slaveKey] !== auth.primaryDevicePubKey + ) { + log.warn( + `file server user annotation primaryKey mismatch, had ${ + newSlavePrimaryMap[slaveKey] + } now ${auth.primaryDevicePubKey} for ${slaveKey}` + ); + return; + } + // log.info('valid', slaveKey); + if (primaryPubKeys.indexOf(`@${auth.primaryDevicePubKey}`) === -1) { + primaryPubKeys.push(`@${auth.primaryDevicePubKey}`); + } + checkSigs[slaveKey] = auth; + newSlavePrimaryMap[slaveKey] = auth.primaryDevicePubKey; + }); // end verifyUserObjectDeviceMap + + // log.info('verifyUserObjectDeviceMap', pubKeys, '=>', primaryPubKeys); + + // no valid primary pubkeys to check + if (!primaryPubKeys.length) { + return []; + } + + const verifiedPrimaryPKs = []; + + // get a list of all of primary pubKeys to verify the secondaryDevice assertion + await this.verifyUserObjectDeviceMap( + primaryPubKeys, + false, + (primaryKey, auth) => { + // log.info('primary iterator', slaveKey); + if (verifiedPrimaryPKs.indexOf(`@${primaryKey}`) === -1) { + verifiedPrimaryPKs.push(`@${primaryKey}`); + } + // assuming both are ordered + // make sure our secondary and primary authorization match + if ( + JSON.stringify(checkSigs[auth.secondaryDevicePubKey]) !== + JSON.stringify(auth) + ) { + // should hopefully never happen + log.warn( + `Valid authorizations from ${ + auth.secondaryDevicePubKey + } does not match ${primaryKey}` + ); + return false; + } + return true; + }, + primaryPubKey => { + // if not verified remove this user pubkey from newSlavePrimaryMap + Object.keys(newSlavePrimaryMap).forEach(slaveKey => { + if (newSlavePrimaryMap[slaveKey] === primaryPubKey) { + log.warn( + `removing unverifible ${slaveKey} to ${primaryPubKey} mapping` + ); + delete newSlavePrimaryMap[slaveKey]; + } + }); + } + ); // end verifyUserObjectDeviceMap + + // make new map final + window.lokiPublicChatAPI.slavePrimaryMap = newSlavePrimaryMap; + + log.info( + `Updated device mappings ${JSON.stringify( + window.lokiPublicChatAPI.slavePrimaryMap + )}` + ); + + return verifiedPrimaryPKs; + } + _setOurDeviceMapping(authorisations, isPrimary) { const content = { isPrimary: isPrimary ? '1' : '0',