Send support for Sender Key

This commit is contained in:
Scott Nonnenberg 2021-05-25 15:40:04 -07:00 committed by GitHub
parent d8417e562b
commit e6f1ec2b6b
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
30 changed files with 2290 additions and 911 deletions

View file

@ -6,7 +6,7 @@
import PQueue from 'p-queue';
import { isNumber } from 'lodash';
import * as z from 'zod';
import { z } from 'zod';
import {
Direction,
@ -32,6 +32,7 @@ import {
sessionStructureToArrayBuffer,
} from './util/sessionTranslation';
import {
DeviceType,
KeyPairType,
IdentityKeyType,
SenderKeyType,
@ -545,7 +546,7 @@ export class SignalProtocolStore extends EventsMixin {
}
if (entry.hydrated) {
window.log.info('Successfully fetched signed prekey (cache hit):', id);
window.log.info('Successfully fetched sender key (cache hit):', id);
return entry.item;
}
@ -555,17 +556,40 @@ export class SignalProtocolStore extends EventsMixin {
item,
fromDB: entry.fromDB,
});
window.log.info('Successfully fetched signed prekey (cache miss):', id);
window.log.info('Successfully fetched sender key(cache miss):', id);
return item;
} catch (error) {
const errorString = error && error.stack ? error.stack : error;
window.log.error(
`getSenderKey: failed to load senderKey ${encodedAddress}/${distributionId}: ${errorString}`
`getSenderKey: failed to load sender key ${encodedAddress}/${distributionId}: ${errorString}`
);
return undefined;
}
}
async removeSenderKey(
encodedAddress: string,
distributionId: string
): Promise<void> {
if (!this.senderKeys) {
throw new Error('getSenderKey: this.senderKeys not yet cached!');
}
try {
const senderId = await normalizeEncodedAddress(encodedAddress);
const id = this.getSenderKeyId(senderId, distributionId);
await window.Signal.Data.removeSenderKeyById(id);
this.senderKeys.delete(id);
} catch (error) {
const errorString = error && error.stack ? error.stack : error;
window.log.error(
`removeSenderKey: failed to remove senderKey ${encodedAddress}/${distributionId}: ${errorString}`
);
}
}
// Session Queue
async enqueueSessionJob<T>(
@ -792,6 +816,21 @@ export class SignalProtocolStore extends EventsMixin {
});
}
async loadSessions(
encodedAddresses: Array<string>,
{ zone = GLOBAL_ZONE }: SessionTransactionOptions = {}
): Promise<Array<SessionRecord>> {
return this.withZone(zone, 'loadSession', async () => {
const sessions = await Promise.all(
encodedAddresses.map(async address =>
this.loadSession(address, { zone })
)
);
return sessions.filter(isNotNil);
});
}
private async _maybeMigrateSession(
session: SessionType
): Promise<SessionRecord> {
@ -882,33 +921,51 @@ export class SignalProtocolStore extends EventsMixin {
});
}
async getDeviceIds(identifier: string): Promise<Array<number>> {
return this.withZone(GLOBAL_ZONE, 'getDeviceIds', async () => {
async getOpenDevices(
identifiers: Array<string>
): Promise<{
devices: Array<DeviceType>;
emptyIdentifiers: Array<string>;
}> {
return this.withZone(GLOBAL_ZONE, 'getOpenDevices', async () => {
if (!this.sessions) {
throw new Error('getDeviceIds: this.sessions not yet cached!');
throw new Error('getOpenDevices: this.sessions not yet cached!');
}
if (identifier === null || identifier === undefined) {
throw new Error('getDeviceIds: identifier was undefined/null');
if (identifiers.length === 0) {
throw new Error('getOpenDevices: No identifiers provided!');
}
try {
const id = window.ConversationController.getConversationId(identifier);
if (!id) {
throw new Error(
`getDeviceIds: No conversationId found for identifier ${identifier}`
const conversationIds = new Map<string, string>();
identifiers.forEach(identifier => {
if (identifier === null || identifier === undefined) {
throw new Error('getOpenDevices: identifier was undefined/null');
}
const conversation = window.ConversationController.getOrCreate(
identifier,
'private'
);
}
if (!conversation) {
throw new Error(
`getOpenDevices: No conversationId found for identifier ${identifier}`
);
}
conversationIds.set(conversation.get('id'), identifier);
});
const allSessions = this._getAllSessions();
const entries = allSessions.filter(
session => session.fromDB.conversationId === id
const entries = allSessions.filter(session =>
conversationIds.has(session.fromDB.conversationId)
);
const openIds = await Promise.all(
const openEntries: Array<
SessionCacheEntry | undefined
> = await Promise.all(
entries.map(async entry => {
if (entry.hydrated) {
const record = entry.item;
if (record.hasCurrentState()) {
return entry.fromDB.deviceId;
return entry;
}
return undefined;
@ -916,25 +973,67 @@ export class SignalProtocolStore extends EventsMixin {
const record = await this._maybeMigrateSession(entry.fromDB);
if (record.hasCurrentState()) {
return entry.fromDB.deviceId;
return entry;
}
return undefined;
})
);
return openIds.filter(isNotNil);
const devices = openEntries
.map(entry => {
if (!entry) {
return undefined;
}
const { conversationId } = entry.fromDB;
conversationIds.delete(conversationId);
const id = entry.fromDB.deviceId;
const conversation = window.ConversationController.get(
conversationId
);
if (!conversation) {
throw new Error(
`getOpenDevices: Unable to find matching conversation for ${conversationId}`
);
}
const identifier =
conversation.get('uuid') || conversation.get('e164');
if (!identifier) {
throw new Error(
`getOpenDevices: No identifier for conversation ${conversationId}`
);
}
return {
identifier,
id,
};
})
.filter(isNotNil);
const emptyIdentifiers = Array.from(conversationIds.values());
return {
devices,
emptyIdentifiers,
};
} catch (error) {
window.log.error(
`getDeviceIds: Failed to get device ids for identifier ${identifier}`,
'getOpenDevices: Failed to get devices',
error && error.stack ? error.stack : error
);
throw error;
}
return [];
});
}
async getDeviceIds(identifier: string): Promise<Array<number>> {
const { devices } = await this.getOpenDevices([identifier]);
return devices.map((device: DeviceType) => device.id);
}
async removeSession(encodedAddress: string): Promise<void> {
return this.withZone(GLOBAL_ZONE, 'removeSession', async () => {
if (!this.sessions) {