diff --git a/package.json b/package.json index c8e069e1e..df6a58714 100644 --- a/package.json +++ b/package.json @@ -198,7 +198,7 @@ "@electron/fuses": "1.5.0", "@formatjs/intl": "2.6.7", "@mixer/parallel-prettier": "2.0.3", - "@signalapp/mock-server": "4.1.2", + "@signalapp/mock-server": "4.2.0", "@storybook/addon-a11y": "7.4.5", "@storybook/addon-actions": "7.4.5", "@storybook/addon-controls": "7.4.5", diff --git a/protos/SignalService.proto b/protos/SignalService.proto index 742f81a4b..dce4018ae 100644 --- a/protos/SignalService.proto +++ b/protos/SignalService.proto @@ -505,7 +505,8 @@ message SyncMessage { } message Keys { - optional bytes storageService = 1; + optional bytes storageService = 1; // deprecated: this field will be removed in a future release. + optional bytes master = 2; } message Read { diff --git a/ts/Crypto.ts b/ts/Crypto.ts index ceb3273b7..6a0829234 100644 --- a/ts/Crypto.ts +++ b/ts/Crypto.ts @@ -127,6 +127,10 @@ export function decryptDeviceName( return Bytes.toString(plaintext); } +export function deriveStorageServiceKey(masterKey: Uint8Array): Uint8Array { + return hmacSha256(masterKey, Bytes.fromString('Storage Service Encryption')); +} + export function deriveStorageManifestKey( storageServiceKey: Uint8Array, version: Long = Long.fromNumber(0) diff --git a/ts/background.ts b/ts/background.ts index ff7316ef7..a86c9d6e2 100644 --- a/ts/background.ts +++ b/ts/background.ts @@ -189,6 +189,7 @@ import { getCallIdFromEra, updateLocalGroupCallHistoryTimestamp, } from './util/callDisposition'; +import { deriveStorageServiceKey } from './Crypto'; export function isOverHourIntoPast(timestamp: number): boolean { return isNumber(timestamp) && isOlderThan(timestamp, HOUR); @@ -1350,22 +1351,6 @@ export async function startApp(): Promise { async function runStorageService() { StorageService.enableStorageService(); - - if (window.ConversationController.areWePrimaryDevice()) { - log.warn( - 'background/runStorageService: We are primary device; not sending key sync request' - ); - return; - } - - try { - await singleProtoJobQueue.add(MessageSender.getRequestKeySyncMessage()); - } catch (error) { - log.error( - 'runStorageService: Failed to queue sync message', - Errors.toLogFormat(error) - ); - } } async function start() { @@ -1821,6 +1806,26 @@ export async function startApp(): Promise { } if (firstRun === true && deviceId !== 1) { + if (!window.storage.get('masterKey')) { + const lastSent = window.storage.get('masterKeyLastRequestTime') ?? 0; + const now = Date.now(); + + // If we last attempted sync one day in the past, or if we time + // traveled. + if (isOlderThan(lastSent, DAY) || lastSent > now) { + log.warn('connect: masterKey not captured, requesting sync'); + await singleProtoJobQueue.add( + MessageSender.getRequestKeySyncMessage() + ); + await window.storage.put('masterKeyLastRequestTime', now); + } else { + log.warn( + 'connect: masterKey not captured, but sync requested recently.' + + 'Not running' + ); + } + } + const hasThemeSetting = Boolean(window.storage.get('theme-setting')); if ( !hasThemeSetting && @@ -3046,7 +3051,17 @@ export async function startApp(): Promise { async function onKeysSync(ev: KeysEvent) { ev.confirm(); - const { storageServiceKey } = ev; + const { masterKey } = ev; + let { storageServiceKey } = ev; + + if (masterKey == null) { + log.info('onKeysSync: deleting window.masterKey'); + await window.storage.remove('masterKey'); + } else { + // Override provided storageServiceKey because it is deprecated. + storageServiceKey = deriveStorageServiceKey(masterKey); + await window.storage.put('masterKey', Bytes.toBase64(masterKey)); + } if (storageServiceKey == null) { log.info('onKeysSync: deleting window.storageKey'); diff --git a/ts/textsecure/MessageReceiver.ts b/ts/textsecure/MessageReceiver.ts index 9b35315e7..c2e1425ce 100644 --- a/ts/textsecure/MessageReceiver.ts +++ b/ts/textsecure/MessageReceiver.ts @@ -3292,12 +3292,15 @@ export default class MessageReceiver logUnexpectedUrgentValue(envelope, 'keySync'); - if (!sync.storageService) { + if (!sync.storageService && !sync.master) { return undefined; } const ev = new KeysEvent( - sync.storageService, + { + storageServiceKey: dropNull(sync.storageService), + masterKey: dropNull(sync.master), + }, this.removeFromCache.bind(this, envelope) ); diff --git a/ts/textsecure/messageReceiverEvents.ts b/ts/textsecure/messageReceiverEvents.ts index c73363df7..c06a9960c 100644 --- a/ts/textsecure/messageReceiverEvents.ts +++ b/ts/textsecure/messageReceiverEvents.ts @@ -355,12 +355,23 @@ export class FetchLatestEvent extends ConfirmableEvent { } } +export type KeysEventData = Readonly<{ + storageServiceKey: Uint8Array | undefined; + masterKey: Uint8Array | undefined; +}>; + export class KeysEvent extends ConfirmableEvent { + public readonly storageServiceKey: Uint8Array | undefined; + public readonly masterKey: Uint8Array | undefined; + constructor( - public readonly storageServiceKey: Uint8Array, + { storageServiceKey, masterKey }: KeysEventData, confirm: ConfirmCallback ) { super('keys', confirm); + + this.storageServiceKey = storageServiceKey; + this.masterKey = masterKey; } } diff --git a/ts/types/Storage.d.ts b/ts/types/Storage.d.ts index be1144247..68fbc3129 100644 --- a/ts/types/Storage.d.ts +++ b/ts/types/Storage.d.ts @@ -86,6 +86,8 @@ export type StorageAccessType = { lastAttemptedToRefreshProfilesAt: number; lastResortKeyUpdateTime: number; lastResortKeyUpdateTimePNI: number; + masterKey: string; + masterKeyLastRequestTime: number; maxPreKeyId: number; maxPreKeyIdPNI: number; maxKyberPreKeyId: number; diff --git a/yarn.lock b/yarn.lock index 885430669..a0f3773b8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3901,10 +3901,10 @@ node-gyp-build "^4.2.3" uuid "^8.3.0" -"@signalapp/mock-server@4.1.2": - version "4.1.2" - resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-4.1.2.tgz#3f497d4cc5cc6613d2a860173ee1d9cee24ce9cd" - integrity sha512-vOFJ8bVQdhII6ZGc34wurxJZ9roeoq4ch0VeorImcyavL5p7d9VbNwpWyOA/VAlfTaUgaiXegVmzK3t52lCQTw== +"@signalapp/mock-server@4.2.0": + version "4.2.0" + resolved "https://registry.yarnpkg.com/@signalapp/mock-server/-/mock-server-4.2.0.tgz#836bf1d8cb38b9f66a6f719205e5000cdc7d184b" + integrity sha512-pungaAf3Pel34yIK00nRtHWQHA9VCRjOH11ZBgrEjiMpdA+LPETCU94Do28Q9Un7T8GmNOJ0+cKAhCKuyo1Fow== dependencies: "@signalapp/libsignal-client" "^0.30.2" debug "^4.3.2"