Sender Key: Track registrationIds in senderKeyState

This commit is contained in:
Scott Nonnenberg 2021-07-30 11:35:25 -07:00 committed by GitHub
parent 689542a9b4
commit 9fb8114691
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 57 additions and 15 deletions

View file

@ -967,13 +967,17 @@ export class SignalProtocolStore extends EventsMixin {
conversationIds.has(session.fromDB.conversationId) conversationIds.has(session.fromDB.conversationId)
); );
const openEntries: Array< const openEntries: Array<
SessionCacheEntry | undefined | undefined
| {
entry: SessionCacheEntry;
record: SessionRecord;
}
> = await Promise.all( > = await Promise.all(
entries.map(async entry => { entries.map(async entry => {
if (entry.hydrated) { if (entry.hydrated) {
const record = entry.item; const record = entry.item;
if (record.hasCurrentState()) { if (record.hasCurrentState()) {
return entry; return { record, entry };
} }
return undefined; return undefined;
@ -981,7 +985,7 @@ export class SignalProtocolStore extends EventsMixin {
const record = await this._maybeMigrateSession(entry.fromDB); const record = await this._maybeMigrateSession(entry.fromDB);
if (record.hasCurrentState()) { if (record.hasCurrentState()) {
return entry; return { record, entry };
} }
return undefined; return undefined;
@ -989,10 +993,11 @@ export class SignalProtocolStore extends EventsMixin {
); );
const devices = openEntries const devices = openEntries
.map(entry => { .map(item => {
if (!entry) { if (!item) {
return undefined; return undefined;
} }
const { entry, record } = item;
const { conversationId } = entry.fromDB; const { conversationId } = entry.fromDB;
conversationIds.delete(conversationId); conversationIds.delete(conversationId);
@ -1015,9 +1020,12 @@ export class SignalProtocolStore extends EventsMixin {
); );
} }
const registrationId = record.remoteRegistrationId();
return { return {
identifier, identifier,
id, id,
registrationId,
}; };
}) })
.filter(isNotNil); .filter(isNotNil);

View file

@ -1330,18 +1330,22 @@ describe('SignalProtocolStore', () => {
{ {
id: 1, id: 1,
identifier: number, identifier: number,
registrationId: 243,
}, },
{ {
id: 2, id: 2,
identifier: number, identifier: number,
registrationId: 243,
}, },
{ {
id: 3, id: 3,
identifier: number, identifier: number,
registrationId: 243,
}, },
{ {
id: 10, id: 10,
identifier: number, identifier: number,
registrationId: 243,
}, },
], ],
emptyIdentifiers: ['blah', 'blah2'], emptyIdentifiers: ['blah', 'blah2'],

View file

@ -14,14 +14,17 @@ describe('sendToGroup', () => {
{ {
identifier: 'ident-guid-one', identifier: 'ident-guid-one',
id: 1, id: 1,
registrationId: 11,
}, },
{ {
identifier: 'ident-guid-one', identifier: 'ident-guid-one',
id: 2, id: 2,
registrationId: 22,
}, },
{ {
identifier: 'ident-guid-two', identifier: 'ident-guid-two',
id: 2, id: 2,
registrationId: 33,
}, },
]; ];
} }
@ -60,10 +63,12 @@ describe('sendToGroup', () => {
{ {
identifier: 'ident-guid-one', identifier: 'ident-guid-one',
id: 2, id: 2,
registrationId: 22,
}, },
{ {
identifier: 'ident-guid-two', identifier: 'ident-guid-two',
id: 2, id: 2,
registrationId: 33,
}, },
]); ]);
assert.deepEqual(newToMemberUuids, ['ident-guid-one', 'ident-guid-two']); assert.deepEqual(newToMemberUuids, ['ident-guid-one', 'ident-guid-two']);
@ -90,10 +95,12 @@ describe('sendToGroup', () => {
{ {
identifier: 'ident-guid-one', identifier: 'ident-guid-one',
id: 2, id: 2,
registrationId: 22,
}, },
{ {
identifier: 'ident-guid-two', identifier: 'ident-guid-two',
id: 2, id: 2,
registrationId: 33,
}, },
]); ]);
assert.deepEqual(removedFromMemberUuids, [ assert.deepEqual(removedFromMemberUuids, [

View file

@ -32,6 +32,7 @@ export type WebAPICredentials = {
export type DeviceType = { export type DeviceType = {
id: number; id: number;
identifier: string; identifier: string;
registrationId: number;
}; };
// How the legacy APIs generate these types // How the legacy APIs generate these types

View file

@ -44,7 +44,7 @@ import {
} from '../textsecure/WebAPI'; } from '../textsecure/WebAPI';
import { SignalService as Proto } from '../protobuf'; import { SignalService as Proto } from '../protobuf';
import { assert } from './assert'; import { strictAssert } from './assert';
import { isGroupV2 } from './whatTypeOfConversation'; import { isGroupV2 } from './whatTypeOfConversation';
const ERROR_EXPIRED_OR_MISSING_DEVICES = 409; const ERROR_EXPIRED_OR_MISSING_DEVICES = 409;
@ -56,7 +56,7 @@ const DAY = 24 * HOUR;
const MAX_CONCURRENCY = 5; const MAX_CONCURRENCY = 5;
// sendWithSenderKey is recursive, but we don't want to loop back too many times. // sendWithSenderKey is recursive, but we don't want to loop back too many times.
const MAX_RECURSION = 5; const MAX_RECURSION = 10;
// TODO: remove once we move away from ArrayBuffers // TODO: remove once we move away from ArrayBuffers
const FIXMEU8 = Uint8Array; const FIXMEU8 = Uint8Array;
@ -80,7 +80,7 @@ export async function sendToGroup({
sendOptions?: SendOptionsType; sendOptions?: SendOptionsType;
sendType: SendTypesType; sendType: SendTypesType;
}): Promise<CallbackResultType> { }): Promise<CallbackResultType> {
assert( strictAssert(
window.textsecure.messaging, window.textsecure.messaging,
'sendToGroup: textsecure.messaging not available!' 'sendToGroup: textsecure.messaging not available!'
); );
@ -133,7 +133,7 @@ export async function sendContentMessageToGroup({
timestamp: number; timestamp: number;
}): Promise<CallbackResultType> { }): Promise<CallbackResultType> {
const logId = conversation.idForLogging(); const logId = conversation.idForLogging();
assert( strictAssert(
window.textsecure.messaging, window.textsecure.messaging,
'sendContentMessageToGroup: textsecure.messaging not available!' 'sendContentMessageToGroup: textsecure.messaging not available!'
); );
@ -247,7 +247,7 @@ export async function sendToGroupViaSenderKey(options: {
); );
} }
assert( strictAssert(
window.textsecure.messaging, window.textsecure.messaging,
'sendToGroupViaSenderKey: textsecure.messaging not available!' 'sendToGroupViaSenderKey: textsecure.messaging not available!'
); );
@ -293,14 +293,14 @@ export async function sendToGroupViaSenderKey(options: {
) { ) {
await fetchKeysForIdentifiers(emptyIdentifiers); await fetchKeysForIdentifiers(emptyIdentifiers);
// Restart here to capture devices for accounts we just started sesions with // Restart here to capture devices for accounts we just started sessions with
return sendToGroupViaSenderKey({ return sendToGroupViaSenderKey({
...options, ...options,
recursionCount: recursionCount + 1, recursionCount: recursionCount + 1,
}); });
} }
assert( strictAssert(
attributes.senderKeyInfo, attributes.senderKeyInfo,
`sendToGroupViaSenderKey/${logId}: expect senderKeyInfo` `sendToGroupViaSenderKey/${logId}: expect senderKeyInfo`
); );
@ -380,6 +380,13 @@ export async function sendToGroupViaSenderKey(options: {
), ),
{ messageIds: [], sendType: 'senderKeyDistributionMessage' } { messageIds: [], sendType: 'senderKeyDistributionMessage' }
); );
// Restart here because we might have discovered new or dropped devices as part of
// distributing our sender key.
return sendToGroupViaSenderKey({
...options,
recursionCount: recursionCount + 1,
});
} }
// 9. Update memberDevices with both adds and the removals which didn't require a reset. // 9. Update memberDevices with both adds and the removals which didn't require a reset.
@ -696,7 +703,7 @@ async function handle410Response(
// been re-registered or re-linked. // been re-registered or re-linked.
const senderKeyInfo = conversation.get('senderKeyInfo'); const senderKeyInfo = conversation.get('senderKeyInfo');
if (senderKeyInfo) { if (senderKeyInfo) {
const devicesToRemove: Array<DeviceType> = devices.staleDevices.map( const devicesToRemove: Array<PartialDeviceType> = devices.staleDevices.map(
id => ({ id, identifier: uuid }) id => ({ id, identifier: uuid })
); );
conversation.set({ conversation.set({
@ -705,7 +712,7 @@ async function handle410Response(
memberDevices: differenceWith( memberDevices: differenceWith(
senderKeyInfo.memberDevices, senderKeyInfo.memberDevices,
devicesToRemove, devicesToRemove,
deviceComparator partialDeviceComparator
), ),
}, },
}); });
@ -732,7 +739,7 @@ function getXorOfAccessKeys(devices: Array<DeviceType>): Buffer {
const uuids = getUuidsFromDevices(devices); const uuids = getUuidsFromDevices(devices);
const result = Buffer.alloc(ACCESS_KEY_LENGTH); const result = Buffer.alloc(ACCESS_KEY_LENGTH);
assert( strictAssert(
result.length === ACCESS_KEY_LENGTH, result.length === ACCESS_KEY_LENGTH,
'getXorOfAccessKeys starting value' 'getXorOfAccessKeys starting value'
); );
@ -876,6 +883,21 @@ function isValidSenderKeyRecipient(
} }
function deviceComparator(left?: DeviceType, right?: DeviceType): boolean { function deviceComparator(left?: DeviceType, right?: DeviceType): boolean {
return Boolean(
left &&
right &&
left.id === right.id &&
left.identifier === right.identifier &&
left.registrationId === right.registrationId
);
}
type PartialDeviceType = Omit<DeviceType, 'registrationId'>;
function partialDeviceComparator(
left?: PartialDeviceType,
right?: PartialDeviceType
): boolean {
return Boolean( return Boolean(
left && left &&
right && right &&