From c1dfe3e5b46d864667b4be19846503ce6e699ecd Mon Sep 17 00:00:00 2001 From: Scott Nonnenberg Date: Wed, 15 Apr 2020 16:12:28 -0700 Subject: [PATCH] Adds support for versioned profiles * Add zkgroup library * tsconfig.json: Prettier wants to mess it up. :0( * Initial take on versioned profile fetches * Fix up the logging in getProfiles() - warn instead of error * Introduce new VERSIONED_PROFILE_FETCH flag * Update zkgroup dependency to v0.5.0 * Fix lint-deps - new zkgroup library brought in new debug dep * ts/zkgroup: Introduce some commonly-used helper functions * Update to latest serverPublicParams * Don't derive profileKeyVersion unless flag is set --- config/default.json | 1 + config/production.json | 1 + js/models/conversations.js | 105 ++++++++++++++++++++--- main.js | 1 + package.json | 14 ++- preload.js | 4 + ts/Crypto.ts | 9 ++ ts/textsecure/SendMessage.ts | 19 ++++- ts/textsecure/WebAPI.ts | 61 +++++++++++-- ts/util/index.ts | 2 + ts/util/lint/exceptions.json | 160 +++++++++++++++++++++++++++++++++++ ts/util/zkgroup.ts | 102 ++++++++++++++++++++++ tsconfig.json | 2 +- yarn.lock | 135 ++++++++++++++++++++++++++++- 14 files changed, 591 insertions(+), 25 deletions(-) create mode 100644 ts/util/zkgroup.ts diff --git a/config/default.json b/config/default.json index 0fc8fe0c5a..c8983e88c7 100644 --- a/config/default.json +++ b/config/default.json @@ -9,5 +9,6 @@ "buildExpiration": 0, "certificateAuthority": "-----BEGIN CERTIFICATE-----\nMIID7zCCAtegAwIBAgIJAIm6LatK5PNiMA0GCSqGSIb3DQEBBQUAMIGNMQswCQYD\nVQQGEwJVUzETMBEGA1UECAwKQ2FsaWZvcm5pYTEWMBQGA1UEBwwNU2FuIEZyYW5j\naXNjbzEdMBsGA1UECgwUT3BlbiBXaGlzcGVyIFN5c3RlbXMxHTAbBgNVBAsMFE9w\nZW4gV2hpc3BlciBTeXN0ZW1zMRMwEQYDVQQDDApUZXh0U2VjdXJlMB4XDTEzMDMy\nNTIyMTgzNVoXDTIzMDMyMzIyMTgzNVowgY0xCzAJBgNVBAYTAlVTMRMwEQYDVQQI\nDApDYWxpZm9ybmlhMRYwFAYDVQQHDA1TYW4gRnJhbmNpc2NvMR0wGwYDVQQKDBRP\ncGVuIFdoaXNwZXIgU3lzdGVtczEdMBsGA1UECwwUT3BlbiBXaGlzcGVyIFN5c3Rl\nbXMxEzARBgNVBAMMClRleHRTZWN1cmUwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw\nggEKAoIBAQDBSWBpOCBDF0i4q2d4jAXkSXUGpbeWugVPQCjaL6qD9QDOxeW1afvf\nPo863i6Crq1KDxHpB36EwzVcjwLkFTIMeo7t9s1FQolAt3mErV2U0vie6Ves+yj6\ngrSfxwIDAcdsKmI0a1SQCZlr3Q1tcHAkAKFRxYNawADyps5B+Zmqcgf653TXS5/0\nIPPQLocLn8GWLwOYNnYfBvILKDMItmZTtEbucdigxEA9mfIvvHADEbteLtVgwBm9\nR5vVvtwrD6CCxI3pgH7EH7kMP0Od93wLisvn1yhHY7FuYlrkYqdkMvWUrKoASVw4\njb69vaeJCUdU+HCoXOSP1PQcL6WenNCHAgMBAAGjUDBOMB0GA1UdDgQWBBQBixjx\nP/s5GURuhYa+lGUypzI8kDAfBgNVHSMEGDAWgBQBixjxP/s5GURuhYa+lGUypzI8\nkDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4IBAQB+Hr4hC56m0LvJAu1R\nK6NuPDbTMEN7/jMojFHxH4P3XPFfupjR+bkDq0pPOU6JjIxnrD1XD/EVmTTaTVY5\niOheyv7UzJOefb2pLOc9qsuvI4fnaESh9bhzln+LXxtCrRPGhkxA1IMIo3J/s2WF\n/KVYZyciu6b4ubJ91XPAuBNZwImug7/srWvbpk0hq6A6z140WTVSKtJG7EP41kJe\n/oF4usY5J7LPkxK3LWzMJnb5EIJDmRvyH8pyRwWg6Qm6qiGFaI4nL8QU4La1x2en\n4DGXRaLMPRwjELNgQPodR38zoCMuA8gHZfZYYoZ7D7Q1wNUiVHcxuFrEeBaYJbLE\nrwLV\n-----END CERTIFICATE-----\n", "import": false, + "serverPublicParams": "ZFt6K+dFE23BsKZMqTroFbbxOXDhDHXcIeCFlWeOIjXeNPrpm9pGwwcQ1AiENlEM1xOaQ2EW48s++quhVv1TEkKormac7WM6Z01kWn/FH2zVcXJxQmsKfYWmAa6lnLhIHO8MXCfllR9uDW2Jfj++8SEzn6oD3+wmzqOzPqiOPAtQxuqWsVTFk4bt7ChuiVWjF7PVZ37deUH/mKhV0flvFA==", "serverTrustRoot": "BbqY1DzohE4NUZoVF+L18oUPrK3kILllLEJh2UnPSsEx" } diff --git a/config/production.json b/config/production.json index aea16d3a82..06d2321502 100644 --- a/config/production.json +++ b/config/production.json @@ -1,6 +1,7 @@ { "serverUrl": "https://textsecure-service.whispersystems.org", "cdnUrl": "https://cdn.signal.org", + "serverPublicParams": "DDZM414H2QbA3brAa6NCMaZIN1ZRY+B46PWDvw4LmwrY6CEQArF4OF/yHdBL7HW/JPgjjauzJau+cpikvqH3dDZQ7KFKgx/MGsbw49ATUj6fhBXko9iyPwVwC3+kjNY6PGZuSoYpD4SJJIgzTJ8Gnuk23tSbX1aQWAWNlc8WiyWIHm/A+22w/D1zQmGuFCEGImU4blMK+HhNfC7jM5leBQ==", "serverTrustRoot": "BXu6QIKVz5MA8gstzfOgRQGqyLqOwNKHL6INkv3IHWMF", "updatesEnabled": true } diff --git a/js/models/conversations.js b/js/models/conversations.js index aeba9604c1..ced09bcd2f 100644 --- a/js/models/conversations.js +++ b/js/models/conversations.js @@ -1918,7 +1918,15 @@ } const c = await ConversationController.getOrCreateAndWait(id, 'private'); + const { + generateProfileKeyCredentialRequest, + getClientZkProfileOperations, + handleProfileKeyCredential, + } = Util.zkgroup; + const clientZkProfileCipher = getClientZkProfileOperations( + window.getServerPublicParams() + ); // Because we're no longer using Backbone-integrated saves, we need to manually // clear the changed fields here so our hasChanged() check is useful. c.changed = {}; @@ -1926,7 +1934,36 @@ let profile; try { - await c.deriveAccessKeyIfNeeded(); + await Promise.all([ + c.deriveAccessKeyIfNeeded(), + c.deriveProfileKeyVersionIfNeeded(), + ]); + + const profileKey = c.get('profileKey'); + const uuid = c.get('uuid'); + const profileKeyVersionHex = window.VERSIONED_PROFILE_FETCH + ? c.get('profileKeyVersion') + : null; + const existingProfileKeyCredential = c.get('profileKeyCredential'); + + const weHaveVersion = Boolean( + profileKey && uuid && profileKeyVersionHex + ); + let profileKeyCredentialRequestHex; + let profileCredentialRequestContext; + + if (weHaveVersion && !existingProfileKeyCredential) { + window.log.info('Generating request...'); + ({ + requestHex: profileKeyCredentialRequestHex, + context: profileCredentialRequestContext, + } = generateProfileKeyCredentialRequest( + clientZkProfileCipher, + uuid, + profileKey + )); + } + const sendMetadata = c.getSendMetadata({ disableMeCheck: true }) || {}; const getInfo = sendMetadata[c.id] || {}; @@ -1934,6 +1971,8 @@ try { profile = await textsecure.messaging.getProfile(id, { accessKey: getInfo.accessKey, + profileKeyVersion: profileKeyVersionHex, + profileKeyCredentialRequest: profileKeyCredentialRequestHex, }); } catch (error) { if (error.code === 401 || error.code === 403) { @@ -1941,13 +1980,19 @@ `Setting sealedSender to DISABLED for conversation ${c.idForLogging()}` ); c.set({ sealedSender: SEALED_SENDER.DISABLED }); - profile = await textsecure.messaging.getProfile(id); + profile = await textsecure.messaging.getProfile(id, { + profileKeyVersion: profileKeyVersionHex, + profileKeyCredentialRequest: profileKeyCredentialRequestHex, + }); } else { throw error; } } } else { - profile = await textsecure.messaging.getProfile(id); + profile = await textsecure.messaging.getProfile(id, { + profileKeyVersion: profileKeyVersionHex, + profileKeyCredentialRequest: profileKeyCredentialRequestHex, + }); } const identityKey = window.Signal.Crypto.base64ToArrayBuffer( @@ -2014,10 +2059,18 @@ if (profile.capabilities) { c.set({ capabilities: profile.capabilities }); } + if (profileCredentialRequestContext && profile.credential) { + const profileKeyCredential = handleProfileKeyCredential( + clientZkProfileCipher, + profileCredentialRequestContext, + profile.credential + ); + c.set({ profileKeyCredential }); + } } catch (error) { if (error.code !== 403 && error.code !== 404) { - window.log.error( - 'getProfile error:', + window.log.warn( + 'getProfile failure:', id, error && error.stack ? error.stack : error ); @@ -2030,8 +2083,8 @@ try { await c.setProfileName(profile.name); } catch (error) { - window.log.error( - 'getProfile decryption error:', + window.log.warn( + 'getProfile decryption failure:', id, error && error.stack ? error.stack : error ); @@ -2042,6 +2095,9 @@ await c.setProfileAvatar(profile.avatar); } catch (error) { if (error.code === 403 || error.code === 404) { + window.log.info( + `Clearing profile avatar for conversation ${c.idForLogging()}` + ); c.set({ profileAvatar: null, }); @@ -2120,6 +2176,8 @@ ); this.set({ profileKey, + profileKeyVersion: null, + profileKeyCredential: null, accessKey: null, profileName: null, profileFamilyName: null, @@ -2127,7 +2185,10 @@ sealedSender: SEALED_SENDER.UNKNOWN, }); - await this.deriveAccessKeyIfNeeded(); + await Promise.all([ + this.deriveAccessKeyIfNeeded(), + this.deriveProfileKeyVersionIfNeeded(), + ]); window.Signal.Data.updateConversation(this.attributes, { Conversation: Whisper.Conversation, @@ -2145,11 +2206,13 @@ } this.set({ - profileAvatar: null, profileKey: null, + profileKeyVersion: null, + profileKeyCredential: null, + accessKey: null, profileName: null, profileFamilyName: null, - accessKey: null, + profileAvatar: null, sealedSender: SEALED_SENDER.UNKNOWN, }); @@ -2177,6 +2240,28 @@ ); this.set({ accessKey }); }, + async deriveProfileKeyVersionIfNeeded() { + const profileKey = this.get('profileKey'); + if (!profileKey) { + return; + } + // We won't even save derived profile key versions if we haven't flipped this switch + if (!window.VERSIONED_PROFILE_FETCH) { + return; + } + + const uuid = this.get('uuid'); + if (!uuid || this.get('profileKeyVersion')) { + return; + } + + const profileKeyVersion = Util.zkgroup.deriveProfileKeyVersion( + profileKey, + uuid + ); + + this.set({ profileKeyVersion }); + }, hasMember(identifier) { const cid = ConversationController.getConversationId(identifier); diff --git a/main.js b/main.js index 95dbafc2f5..2c70871535 100644 --- a/main.js +++ b/main.js @@ -197,6 +197,7 @@ function prepareURL(pathSegments, moreKeys) { proxyUrl: process.env.HTTPS_PROXY || process.env.https_proxy, contentProxyUrl: config.contentProxyUrl, importMode: importMode ? true : undefined, // for stringify() + serverPublicParams: config.get('serverPublicParams'), serverTrustRoot: config.get('serverTrustRoot'), appStartInitialSpellcheckSetting, ...moreKeys, diff --git a/package.json b/package.json index 387acdb418..c9719b31ec 100644 --- a/package.json +++ b/package.json @@ -140,7 +140,8 @@ "typeface-inter": "3.10.0", "underscore": "1.9.0", "uuid": "3.3.2", - "websocket": "1.0.28" + "websocket": "1.0.28", + "zkgroup": "https://github.com/signalapp/signal-zkgroup-node.git#eca01d0bbe5f5c8f83665b3a92bdfe2e9df7376c" }, "resolutions": { "fbjs/isomorphic-fetch/node-fetch": "https://github.com/scottnonnenberg-signal/node-fetch.git#3e5f51e08c647ee5f20c43b15cf2d352d61c36b4" @@ -254,6 +255,9 @@ "build": { "appId": "org.whispersystems.signal-desktop", "mac": { + "asarUnpack": [ + "node_modules/zkgroup/libzkgroup.*" + ], "artifactName": "${name}-mac-${version}.${ext}", "category": "public.app-category.social-networking", "darkModeSupport": true, @@ -275,7 +279,8 @@ "win": { "asarUnpack": [ "node_modules/spellchecker/vendor/hunspell_dictionaries", - "node_modules/sharp" + "node_modules/sharp", + "node_modules/zkgroup/libzkgroup.*" ], "artifactName": "${name}-win-${version}.${ext}", "certificateSubjectName": "Signal (Quiet Riddle Ventures, LLC)", @@ -302,7 +307,8 @@ }, "asarUnpack": [ "node_modules/spellchecker/vendor/hunspell_dictionaries", - "node_modules/sharp" + "node_modules/sharp", + "node_modules/zkgroup/libzkgroup.*" ], "target": [ "deb" @@ -372,6 +378,8 @@ "node_modules/spellchecker/build/Release/*.node", "node_modules/websocket/build/Release/*.node", "node_modules/curve25519-n/build/Release/*.node", + "node_modules/ref-napi/build/Release/*.node", + "node_modules/ffi-napi/build/Release/*.node", "node_modules/socks/build/*.js", "node_modules/socks/build/common/*.js", "node_modules/socks/build/client/*.js", diff --git a/preload.js b/preload.js index a0bf65298a..5e6538f5de 100644 --- a/preload.js +++ b/preload.js @@ -15,6 +15,9 @@ try { const { app } = remote; const { nativeTheme } = remote.require('electron'); + // Derive profile key versions, then use those to fetch versioned profiles from server + window.VERSIONED_PROFILE_FETCH = false; + window.PROTO_ROOT = 'protos'; const config = require('url').parse(window.location.toString(), true).query; @@ -35,6 +38,7 @@ try { window.getNodeVersion = () => config.node_version; window.getHostName = () => config.hostname; window.getServerTrustRoot = () => config.serverTrustRoot; + window.getServerPublicParams = () => config.serverPublicParams; window.isBehindProxy = () => Boolean(config.proxyUrl); function setSystemTheme() { diff --git a/ts/Crypto.ts b/ts/Crypto.ts index c8dfc8a0ba..e91347e63b 100644 --- a/ts/Crypto.ts +++ b/ts/Crypto.ts @@ -17,10 +17,19 @@ export function typedArrayToArrayBuffer(typedArray: Uint8Array): ArrayBuffer { export function arrayBufferToBase64(arrayBuffer: ArrayBuffer) { return window.dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('base64'); } + +export function arrayBufferToHex(arrayBuffer: ArrayBuffer) { + return window.dcodeIO.ByteBuffer.wrap(arrayBuffer).toString('hex'); +} + export function base64ToArrayBuffer(base64string: string) { return window.dcodeIO.ByteBuffer.wrap(base64string, 'base64').toArrayBuffer(); } +export function hexToArrayBuffer(hexString: string) { + return window.dcodeIO.ByteBuffer.wrap(hexString, 'hex').toArrayBuffer(); +} + export function fromEncodedBinaryToArrayBuffer(key: string) { return window.dcodeIO.ByteBuffer.wrap(key, 'binary').toArrayBuffer(); } diff --git a/ts/textsecure/SendMessage.ts b/ts/textsecure/SendMessage.ts index 1a2e55cdfc..ad22362f68 100644 --- a/ts/textsecure/SendMessage.ts +++ b/ts/textsecure/SendMessage.ts @@ -675,12 +675,25 @@ export default class MessageSender { ); } - async getProfile(number: string, { accessKey }: { accessKey?: string } = {}) { + async getProfile( + number: string, + options: { + accessKey?: string; + profileKeyVersion?: string; + profileKeyCredentialRequest?: string; + } = {} + ) { + const { accessKey } = options; + if (accessKey) { - return this.server.getProfileUnauth(number, { accessKey }); + const unauthOptions = { + ...options, + accessKey, + }; + return this.server.getProfileUnauth(number, unauthOptions); } - return this.server.getProfile(number); + return this.server.getProfile(number, options); } async getAvatar(path: string) { diff --git a/ts/textsecure/WebAPI.ts b/ts/textsecure/WebAPI.ts index 085a1f0542..41ba225912 100644 --- a/ts/textsecure/WebAPI.ts +++ b/ts/textsecure/WebAPI.ts @@ -546,10 +546,20 @@ export type WebAPIType = { ) => Promise; getMessageSocket: () => WebSocket; getMyKeys: () => Promise; - getProfile: (identifier: string) => Promise; + getProfile: ( + identifier: string, + options?: { + profileKeyVersion?: string; + profileKeyCredentialRequest?: string; + } + ) => Promise; getProfileUnauth: ( identifier: string, - options?: { accessKey?: string } + options: { + accessKey: string; + profileKeyVersion?: string; + profileKeyCredentialRequest?: string; + } ) => Promise; getProvisioningSocket: () => WebSocket; getSenderCertificate: (withUuid?: boolean) => Promise; @@ -797,22 +807,61 @@ export function initialize({ }); } - async function getProfile(identifier: string) { + function getProfileUrl( + identifier: string, + profileKeyVersion?: string, + profileKeyCredentialRequest?: string + ) { + if (profileKeyVersion && profileKeyCredentialRequest) { + return `/${identifier}/${profileKeyVersion}/${profileKeyCredentialRequest}`; + } + + return `/${identifier}`; + } + + async function getProfile( + identifier: string, + options: { + profileKeyVersion?: string; + profileKeyCredentialRequest?: string; + } = {} + ) { + const { profileKeyVersion, profileKeyCredentialRequest } = options; + return _ajax({ call: 'profile', httpType: 'GET', - urlParameters: `/${identifier}`, + urlParameters: getProfileUrl( + identifier, + profileKeyVersion, + profileKeyCredentialRequest + ), responseType: 'json', }); } + async function getProfileUnauth( identifier: string, - { accessKey }: { accessKey?: string } = {} + options: { + accessKey: string; + profileKeyVersion?: string; + profileKeyCredentialRequest?: string; + } ) { + const { + accessKey, + profileKeyVersion, + profileKeyCredentialRequest, + } = options; + return _ajax({ call: 'profile', httpType: 'GET', - urlParameters: `/${identifier}`, + urlParameters: getProfileUrl( + identifier, + profileKeyVersion, + profileKeyCredentialRequest + ), responseType: 'json', unauthenticated: true, accessKey, diff --git a/ts/util/index.ts b/ts/util/index.ts index 83a2a97069..f767c2ba40 100644 --- a/ts/util/index.ts +++ b/ts/util/index.ts @@ -9,6 +9,7 @@ import { isFileDangerous } from './isFileDangerous'; import { makeLookup } from './makeLookup'; import { migrateColor } from './migrateColor'; import { missingCaseError } from './missingCaseError'; +import * as zkgroup from './zkgroup'; export { arrayBufferToObjectURL, @@ -22,4 +23,5 @@ export { migrateColor, missingCaseError, Registration, + zkgroup, }; diff --git a/ts/util/lint/exceptions.json b/ts/util/lint/exceptions.json index bf768273d6..cdb58450e8 100644 --- a/ts/util/lint/exceptions.json +++ b/ts/util/lint/exceptions.json @@ -1311,6 +1311,38 @@ "reasonCategory": "falseMatch", "updated": "2019-07-31T00:19:18.696Z" }, + { + "rule": "jQuery-load(", + "path": "node_modules/array-index/node_modules/debug/src/browser.js", + "line": "function load() {", + "lineNumber": 150, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/array-index/node_modules/debug/src/browser.js", + "line": "exports.enable(load());", + "lineNumber": 168, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/array-index/node_modules/debug/src/node.js", + "line": "function load() {", + "lineNumber": 154, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/array-index/node_modules/debug/src/node.js", + "line": "exports.enable(load());", + "lineNumber": 246, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, { "rule": "jQuery-insertBefore(", "path": "node_modules/ast-types/lib/path.js", @@ -3213,6 +3245,38 @@ "reasonCategory": "falseMatch", "updated": "2020-02-21T14:09:28.005Z" }, + { + "rule": "jQuery-load(", + "path": "node_modules/ffi-napi/node_modules/debug/src/browser.js", + "line": "function load() {", + "lineNumber": 160, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/ffi-napi/node_modules/debug/src/browser.js", + "line": "exports.enable(load());", + "lineNumber": 178, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/ffi-napi/node_modules/debug/src/node.js", + "line": "function load() {", + "lineNumber": 162, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/ffi-napi/node_modules/debug/src/node.js", + "line": "exports.enable(load());", + "lineNumber": 186, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, { "rule": "jQuery-load(", "path": "node_modules/file-entry-cache/cache.js", @@ -10290,6 +10354,70 @@ "reasonCategory": "falseMatch", "updated": "2019-03-09T00:08:44.242Z" }, + { + "rule": "jQuery-load(", + "path": "node_modules/ref-napi/node_modules/debug/src/browser.js", + "line": "function load() {", + "lineNumber": 160, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/ref-napi/node_modules/debug/src/browser.js", + "line": "exports.enable(load());", + "lineNumber": 178, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/ref-napi/node_modules/debug/src/node.js", + "line": "function load() {", + "lineNumber": 162, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/ref-napi/node_modules/debug/src/node.js", + "line": "exports.enable(load());", + "lineNumber": 186, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/ref-struct-di/node_modules/debug/src/browser.js", + "line": "function load() {", + "lineNumber": 160, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/ref-struct-di/node_modules/debug/src/browser.js", + "line": "exports.enable(load());", + "lineNumber": 178, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/ref-struct-di/node_modules/debug/src/node.js", + "line": "function load() {", + "lineNumber": 162, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/ref-struct-di/node_modules/debug/src/node.js", + "line": "exports.enable(load());", + "lineNumber": 186, + "reasonCategory": "falseMatch", + "updated": "2020-02-21T22:10:39.074Z" + }, { "rule": "jQuery-$(", "path": "node_modules/regenerate/regenerate.js", @@ -11241,6 +11369,38 @@ "reasonCategory": "falseMatch", "updated": "2019-07-16T21:56:03.429Z" }, + { + "rule": "jQuery-load(", + "path": "node_modules/zkgroup/node_modules/debug/src/browser.js", + "line": "function load() {", + "lineNumber": 160, + "reasonCategory": "falseMatch", + "updated": "2020-04-13T23:38:26.065Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/zkgroup/node_modules/debug/src/browser.js", + "line": "exports.enable(load());", + "lineNumber": 178, + "reasonCategory": "falseMatch", + "updated": "2020-04-13T23:38:26.065Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/zkgroup/node_modules/debug/src/node.js", + "line": "function load() {", + "lineNumber": 162, + "reasonCategory": "falseMatch", + "updated": "2020-04-13T23:38:26.065Z" + }, + { + "rule": "jQuery-load(", + "path": "node_modules/zkgroup/node_modules/debug/src/node.js", + "line": "exports.enable(load());", + "lineNumber": 186, + "reasonCategory": "falseMatch", + "updated": "2020-04-13T23:38:26.065Z" + }, { "rule": "DOM-innerHTML", "path": "ts/backbone/views/Lightbox.js", diff --git a/ts/util/zkgroup.ts b/ts/util/zkgroup.ts new file mode 100644 index 0000000000..fe017e85a4 --- /dev/null +++ b/ts/util/zkgroup.ts @@ -0,0 +1,102 @@ +export * from 'zkgroup'; + +import { + ClientZkProfileOperations, + FFICompatArray, + FFICompatArrayType, + ProfileKey, + ProfileKeyCredentialRequestContext, + ProfileKeyCredentialResponse, + ServerPublicParams, +} from 'zkgroup'; +import { + arrayBufferToBase64, + arrayBufferToHex, + base64ToArrayBuffer, + typedArrayToArrayBuffer, +} from '../Crypto'; + +export function arrayBufferToCompatArray( + arrayBuffer: ArrayBuffer +): FFICompatArrayType { + const buffer = Buffer.from(arrayBuffer); + + return new FFICompatArray(buffer); +} + +export function compatArrayToArrayBuffer( + compatArray: FFICompatArrayType +): ArrayBuffer { + return typedArrayToArrayBuffer(compatArray.buffer); +} + +export function base64ToCompatArray(base64: string): FFICompatArrayType { + return arrayBufferToCompatArray(base64ToArrayBuffer(base64)); +} + +export function compatArrayToBase64(compatArray: FFICompatArrayType): string { + return arrayBufferToBase64(compatArrayToArrayBuffer(compatArray)); +} + +export function compatArrayToHex(compatArray: FFICompatArrayType): string { + return arrayBufferToHex(compatArrayToArrayBuffer(compatArray)); +} + +export function deriveProfileKeyVersion( + profileKeyBase64: string, + uuid: string +) { + const profileKeyArray = base64ToCompatArray(profileKeyBase64); + const profileKey = new ProfileKey(profileKeyArray); + + const profileKeyVersion = profileKey.getProfileKeyVersion(uuid); + + return profileKeyVersion.toString(); +} + +export function getClientZkProfileOperations( + serverPublicParamsBase64: string +): ClientZkProfileOperations { + const serverPublicParamsArray = base64ToCompatArray(serverPublicParamsBase64); + const serverPublicParams = new ServerPublicParams(serverPublicParamsArray); + + return new ClientZkProfileOperations(serverPublicParams); +} + +export function generateProfileKeyCredentialRequest( + clientZkProfileCipher: ClientZkProfileOperations, + uuid: string, + profileKeyBase64: string +): { context: ProfileKeyCredentialRequestContext; requestHex: string } { + const profileKeyArray = base64ToCompatArray(profileKeyBase64); + const profileKey = new ProfileKey(profileKeyArray); + + const context = clientZkProfileCipher.createProfileKeyCredentialRequestContext( + uuid, + profileKey + ); + const request = context.getRequest(); + const requestArray = request.serialize(); + + return { + context, + requestHex: compatArrayToHex(requestArray), + }; +} + +export function handleProfileKeyCredential( + clientZkProfileCipher: ClientZkProfileOperations, + context: ProfileKeyCredentialRequestContext, + responseBase64: string +): string { + const responseArray = base64ToCompatArray(responseBase64); + const response = new ProfileKeyCredentialResponse(responseArray); + const profileKeyCredential = clientZkProfileCipher.receiveProfileKeyCredential( + context, + response + ); + + const credentialArray = profileKeyCredential.serialize(); + + return compatArrayToBase64(credentialArray); +} diff --git a/tsconfig.json b/tsconfig.json index 471083a624..c83a13728e 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -53,5 +53,5 @@ // "experimentalDecorators": true, // Enables experimental support for ES7 decorators. // "emitDecoratorMetadata": true, // Enables experimental support for emitting type metadata for decorators. }, - "include": ["ts/**/*"] + "include": ["ts/**/*", "node_modules/zkgroup/zkgroup/modules/*"] } diff --git a/yarn.lock b/yarn.lock index 759ab7aab0..8bf45b68b2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2922,6 +2922,14 @@ array-includes@^3.0.3: define-properties "^1.1.2" es-abstract "^1.7.0" +array-index@1: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-index/-/array-index-1.0.0.tgz#ec56a749ee103e4e08c790b9c353df16055b97f9" + integrity sha1-7FanSe4QPk4Ix5C5w1PfFgVbl/k= + dependencies: + debug "^2.2.0" + es6-symbol "^3.0.2" + array-iterate@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/array-iterate/-/array-iterate-1.1.1.tgz#865bf7f8af39d6b0982c60902914ac76bc0108f6" @@ -3631,7 +3639,7 @@ binary@^0.3.0: buffers "~0.1.1" chainsaw "~0.1.0" -bindings@^1.5.0: +bindings@^1.3.0, bindings@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/bindings/-/bindings-1.5.0.tgz#10353c9e945334bc0511a6d90b38fbc7c9c504df" integrity sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ== @@ -5543,6 +5551,14 @@ d@1: dependencies: es5-ext "^0.10.9" +d@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/d/-/d-1.0.1.tgz#8698095372d58dbee346ffd0c7093f99f8f9eb5a" + integrity sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA== + dependencies: + es5-ext "^0.10.50" + type "^1.0.1" + dashdash@1.14.1, dashdash@^1.12.0: version "1.14.1" resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" @@ -6419,6 +6435,15 @@ es5-ext@^0.10.14, es5-ext@^0.10.35, es5-ext@^0.10.45, es5-ext@^0.10.9, es5-ext@~ es6-symbol "~3.1.1" next-tick "^1.0.0" +es5-ext@^0.10.50: + version "0.10.53" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.53.tgz#93c5a3acfdbef275220ad72644ad02ee18368de1" + integrity sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q== + dependencies: + es6-iterator "~2.0.3" + es6-symbol "~3.1.3" + next-tick "~1.0.0" + es5-shim@^4.5.13: version "4.5.13" resolved "https://registry.yarnpkg.com/es5-shim/-/es5-shim-4.5.13.tgz#5d88062de049f8969f83783f4a4884395f21d28b" @@ -6461,6 +6486,14 @@ es6-shim@^0.35.5: resolved "https://registry.yarnpkg.com/es6-shim/-/es6-shim-0.35.5.tgz#46f59dc0a84a1c5029e8ff1166ca0a902077a9ab" integrity sha512-E9kK/bjtCQRpN1K28Xh4BlmP8egvZBGJJ+9GtnzOwt7mdqtrjHFuVGr7QJfdjBIKqrlU5duPf3pCBoDrkjVYFg== +es6-symbol@^3.0.2, es6-symbol@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.3.tgz#bad5d3c1bcdac28269f4cb331e431c78ac705d18" + integrity sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA== + dependencies: + d "^1.0.1" + ext "^1.1.2" + es6-symbol@^3.1.1, es6-symbol@~3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/es6-symbol/-/es6-symbol-3.1.1.tgz#bf00ef4fdab6ba1b46ecb7b629b4c7ed5715cc77" @@ -6882,6 +6915,13 @@ express@^4.17.0, express@^4.17.1: utils-merge "1.0.1" vary "~1.1.2" +ext@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244" + integrity sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A== + dependencies: + type "^2.0.0" + extend-shallow@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" @@ -7074,6 +7114,18 @@ fd-slicer@~1.0.1: dependencies: pend "~1.2.0" +ffi-napi@2.4.5: + version "2.4.5" + resolved "https://registry.yarnpkg.com/ffi-napi/-/ffi-napi-2.4.5.tgz#12e807f238f8c68fc094fc46c1ce5193c2ab64f8" + integrity sha512-24Et/c5/sRvZvpOZ9nvkK0Be1S8A1Vkt6aJSKGaohOGb5FwV4+EmecaTtNhN4TCLJDjYC8z/k4X8W1SC5IK/fw== + dependencies: + bindings "^1.3.0" + debug "^3.1.0" + get-uv-event-loop-napi-h "^1.0.5" + node-addon-api "1.5.0" + ref-napi "^1.4.0" + ref-struct-di "^1.1.0" + figgy-pudding@^3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/figgy-pudding/-/figgy-pudding-3.5.1.tgz#862470112901c727a0e495a80744bd5baa1d6790" @@ -7675,6 +7727,11 @@ get-stream@^5.1.0: dependencies: pump "^3.0.0" +get-symbol-from-current-process-h@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-symbol-from-current-process-h/-/get-symbol-from-current-process-h-1.0.2.tgz#510af52eaef873f7028854c3377f47f7bb200265" + integrity sha512-syloC6fsCt62ELLrr1VKBM1ggOpMdetX9hTrdW77UQdcApPHLmf7CI7OKcN1c9kYuNxKcDe4iJ4FY9sX3aw2xw== + get-uri@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-2.0.1.tgz#dbdcacacd8c608a38316869368117697a1631c59" @@ -7686,6 +7743,13 @@ get-uri@^2.0.0: ftp "~0.3.10" readable-stream "2" +get-uv-event-loop-napi-h@^1.0.5: + version "1.0.6" + resolved "https://registry.yarnpkg.com/get-uv-event-loop-napi-h/-/get-uv-event-loop-napi-h-1.0.6.tgz#42b0b06b74c3ed21fbac8e7c72845fdb7a200208" + integrity sha512-t5c9VNR84nRoF+eLiz6wFrEp1SE2Acg0wS+Ysa2zF0eROes+LzOfuTaVHxGy8AbS8rq7FHEJzjnCZo1BupwdJg== + dependencies: + get-symbol-from-current-process-h "^1.0.1" + get-value@^2.0.3, get-value@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" @@ -11030,7 +11094,7 @@ netmask@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/netmask/-/netmask-1.0.6.tgz#20297e89d86f6f6400f250d9f4f6b4c1945fcd35" -next-tick@1, next-tick@^1.0.0: +next-tick@1, next-tick@^1.0.0, next-tick@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c" integrity sha1-yobR/ogoFpsBICCOPchCS524NCw= @@ -11064,6 +11128,21 @@ node-abi@^2.7.0: dependencies: semver "^5.4.1" +node-addon-api@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.5.0.tgz#55be6b3da36e746f4b1f2af16c2adf67647d1ff8" + integrity sha512-YsL/8dpBWxCFj3wAVAa/ceN4TlT8lACK8EgpuN0q/4ecflWHDuKpodb+tt7Rx22r/6FJ2f+IT25XSsXnZGwYgA== + +node-addon-api@^1.6.2: + version "1.7.1" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-1.7.1.tgz#cf813cd69bb8d9100f6bdca6755fc268f54ac492" + integrity sha512-2+DuKodWvwRTrCfKOeR24KIc5unKjOh8mz17NCzVnHWfjAdDqbfbjqh7gUT+BkXBRQM52+xCHciKWonJ3CbJMQ== + +node-addon-api@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/node-addon-api/-/node-addon-api-2.0.0.tgz#f9afb8d777a91525244b01775ea0ddbe1125483b" + integrity sha512-ASCL5U13as7HhOExbT6OlWJJUV/lLzL2voOSP1UVehpRD8FbSrSDjfScK/KwAvVTI5AS6r4VwbOMlIqtvRidnA== + node-dir@^0.1.10: version "0.1.17" resolved "https://registry.yarnpkg.com/node-dir/-/node-dir-0.1.17.tgz#5f5665d93351335caabef8f1c554516cf5f1e4e5" @@ -13846,6 +13925,40 @@ redux@^3.6.0: loose-envify "^1.1.0" symbol-observable "^1.0.3" +ref-array-napi@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/ref-array-napi/-/ref-array-napi-1.2.0.tgz#7ec96b0e648cec2164101cf55f62cb217b325f2e" + integrity sha512-EkqS2iyJsrPAGu4Cv5bGAItuDEsE9ZXPoICU0dYB7qqLgksIhmMS4HaBRyJVsrTwb6Da/PNAZgBy6T6gN/HbkQ== + dependencies: + array-index "1" + debug "2" + ref-napi "^1.4.2" + +ref-napi@1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/ref-napi/-/ref-napi-1.4.2.tgz#28ee242de131cd4fbbdd7d935086996d3cb9abc8" + integrity sha512-6AkdfqTLmP9oHQ6/aTnuIoPlVble6LHZ2wWqC1Sh/LWhnXHoT2L3CvyF72rJQ9w76XR5v9rIX6UQUwsry1vfBg== + dependencies: + bindings "^1.3.0" + debug "^3.1.0" + node-addon-api "^1.6.2" + +ref-napi@^1.4.0, ref-napi@^1.4.2: + version "1.4.3" + resolved "https://registry.yarnpkg.com/ref-napi/-/ref-napi-1.4.3.tgz#c9495a4670a18655b3d45472284cc1fdac03e314" + integrity sha512-yE98eVwjpeGSbHjahn+hNlheGgKdV3gCW1rSj7HZL4ITzBhRb0HlUapWamRcAjZebPr3yuhvxeKFmso8NbRv5g== + dependencies: + bindings "^1.3.0" + debug "^3.1.0" + node-addon-api "^2.0.0" + +ref-struct-di@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/ref-struct-di/-/ref-struct-di-1.1.0.tgz#d252144eb449608ccf2e5c12fda35f8153bd3760" + integrity sha512-gghZITj/iQwdwFDduZ6T8kL2B2ogInlOz7AOB0ggFoEc7akAKMcDrbzh3OIPk13Kxy8U2bHPvN6nejcBh4jN7A== + dependencies: + debug "^3.1.0" + refractor@^2.4.1: version "2.10.0" resolved "https://registry.yarnpkg.com/refractor/-/refractor-2.10.0.tgz#4cc7efc0028a87924a9b31d82d129dec831a287b" @@ -16188,6 +16301,16 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" +type@^1.0.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/type/-/type-1.2.0.tgz#848dd7698dafa3e54a6c479e759c4bc3f18847a0" + integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg== + +type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/type/-/type-2.0.0.tgz#5f16ff6ef2eb44f260494dae271033b29c09a9c3" + integrity sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow== + typed-scss-modules@0.0.11: version "0.0.11" resolved "https://registry.yarnpkg.com/typed-scss-modules/-/typed-scss-modules-0.0.11.tgz#9d63ebc4a7bc1add8dff80030a2406a930170215" @@ -17432,3 +17555,11 @@ zip-stream@^1.2.0: compress-commons "^1.2.0" lodash "^4.8.0" readable-stream "^2.0.0" + +"zkgroup@https://github.com/signalapp/signal-zkgroup-node.git#eca01d0bbe5f5c8f83665b3a92bdfe2e9df7376c": + version "0.5.0" + resolved "https://github.com/signalapp/signal-zkgroup-node.git#eca01d0bbe5f5c8f83665b3a92bdfe2e9df7376c" + dependencies: + ffi-napi "2.4.5" + ref-array-napi "1.2.0" + ref-napi "1.4.2"