Support endorsements for group 1:1 sends
Co-authored-by: trevor-signal <131492920+trevor-signal@users.noreply.github.com>
This commit is contained in:
parent
76a77a9b7f
commit
e617981e59
26 changed files with 1296 additions and 796 deletions
|
@ -23,7 +23,7 @@ import {
|
|||
import { Address } from '../types/Address';
|
||||
import { QualifiedAddress } from '../types/QualifiedAddress';
|
||||
import * as Errors from '../types/errors';
|
||||
import { DataWriter, DataReader } from '../sql/Client';
|
||||
import { DataWriter } from '../sql/Client';
|
||||
import { getValue } from '../RemoteConfig';
|
||||
import type { ServiceIdString } from '../types/ServiceId';
|
||||
import { ServiceIdKind } from '../types/ServiceId';
|
||||
|
@ -66,11 +66,11 @@ import { strictAssert } from './assert';
|
|||
import * as log from '../logging/log';
|
||||
import { GLOBAL_ZONE } from '../SignalProtocolStore';
|
||||
import { waitForAll } from './waitForAll';
|
||||
import type { GroupSendEndorsementState } from './groupSendEndorsements';
|
||||
import {
|
||||
GroupSendEndorsementState,
|
||||
maybeCreateGroupSendEndorsementState,
|
||||
onFailedToSendWithEndorsements,
|
||||
} from './groupSendEndorsements';
|
||||
import { maybeUpdateGroup } from '../groups';
|
||||
import type { GroupSendToken } from '../types/GroupSendEndorsements';
|
||||
import { isAciString } from './isAciString';
|
||||
import { safeParseStrict, safeParseUnknown } from './schemas';
|
||||
|
@ -213,11 +213,11 @@ export async function sendContentMessageToGroup(
|
|||
|
||||
if (sendTarget.isValid()) {
|
||||
try {
|
||||
return await sendToGroupViaSenderKey(
|
||||
options,
|
||||
0,
|
||||
'init (sendContentMessageToGroup)'
|
||||
);
|
||||
return await sendToGroupViaSenderKey(options, {
|
||||
count: 0,
|
||||
didRefreshGroupState: false,
|
||||
reason: 'init (sendContentMessageToGroup)',
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
if (!(error instanceof Error)) {
|
||||
throw error;
|
||||
|
@ -259,11 +259,27 @@ export async function sendContentMessageToGroup(
|
|||
|
||||
// The Primary Sender Key workflow
|
||||
|
||||
type SendRecursion = {
|
||||
count: number;
|
||||
didRefreshGroupState: boolean;
|
||||
reason: string;
|
||||
};
|
||||
|
||||
export async function sendToGroupViaSenderKey(
|
||||
options: SendToGroupOptions,
|
||||
recursionCount: number,
|
||||
recursionReason: string
|
||||
recursion: SendRecursion
|
||||
): Promise<CallbackResultType> {
|
||||
function startOver(
|
||||
reason: string,
|
||||
didRefreshGroupState = recursion.didRefreshGroupState
|
||||
) {
|
||||
return sendToGroupViaSenderKey(options, {
|
||||
count: recursion.count + 1,
|
||||
didRefreshGroupState,
|
||||
reason,
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
contentHint,
|
||||
contentMessage,
|
||||
|
@ -282,12 +298,12 @@ export async function sendToGroupViaSenderKey(
|
|||
|
||||
const logId = sendTarget.idForLogging();
|
||||
log.info(
|
||||
`sendToGroupViaSenderKey/${logId}: Starting ${timestamp}, recursion count ${recursionCount}, reason: ${recursionReason}...`
|
||||
`sendToGroupViaSenderKey/${logId}: Starting ${timestamp}, recursion count ${recursion.count}, reason: ${recursion.reason}...`
|
||||
);
|
||||
|
||||
if (recursionCount > MAX_RECURSION) {
|
||||
if (recursion.count > MAX_RECURSION) {
|
||||
throw new Error(
|
||||
`sendToGroupViaSenderKey/${logId}: Too much recursion! Count is at ${recursionCount}`
|
||||
`sendToGroupViaSenderKey/${logId}: Too much recursion! Count is at ${recursion.count}`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -328,11 +344,7 @@ export async function sendToGroupViaSenderKey(
|
|||
});
|
||||
|
||||
// Restart here because we updated senderKeyInfo
|
||||
return sendToGroupViaSenderKey(
|
||||
options,
|
||||
recursionCount + 1,
|
||||
'Added missing sender key info'
|
||||
);
|
||||
return startOver('Added missing sender key info');
|
||||
}
|
||||
|
||||
const EXPIRE_DURATION = getSenderKeyExpireDuration();
|
||||
|
@ -344,11 +356,7 @@ export async function sendToGroupViaSenderKey(
|
|||
await resetSenderKey(sendTarget);
|
||||
|
||||
// Restart here because we updated senderKeyInfo
|
||||
return sendToGroupViaSenderKey(
|
||||
options,
|
||||
recursionCount + 1,
|
||||
'sender key info expired'
|
||||
);
|
||||
return startOver('sender key info expired');
|
||||
}
|
||||
|
||||
// 2. Fetch all devices we believe we'll be sending to
|
||||
|
@ -356,49 +364,20 @@ export async function sendToGroupViaSenderKey(
|
|||
const { devices: currentDevices, emptyServiceIds } =
|
||||
await window.textsecure.storage.protocol.getOpenDevices(ourAci, recipients);
|
||||
|
||||
const conversation =
|
||||
groupId != null
|
||||
? (window.ConversationController.get(groupId) ?? null)
|
||||
: null;
|
||||
|
||||
let groupSendEndorsementState: GroupSendEndorsementState | null = null;
|
||||
if (groupId != null) {
|
||||
strictAssert(conversation, 'Must have conversation for endorsements');
|
||||
|
||||
const data = await DataReader.getGroupSendEndorsementsData(groupId);
|
||||
if (data == null) {
|
||||
if (conversation.isMember(ourAci)) {
|
||||
onFailedToSendWithEndorsements(
|
||||
new Error(
|
||||
`sendToGroupViaSenderKey/${logId}: Missing all endorsements for group`
|
||||
)
|
||||
);
|
||||
}
|
||||
} else {
|
||||
log.info(
|
||||
`sendToGroupViaSenderKey/${logId}: Loaded endorsements for ${data.memberEndorsements.length} members`
|
||||
if (groupId != null && !story) {
|
||||
const { state, didRefreshGroupState } =
|
||||
await maybeCreateGroupSendEndorsementState(
|
||||
groupId,
|
||||
recursion.didRefreshGroupState
|
||||
);
|
||||
const groupSecretParamsBase64 = conversation.get('secretParams');
|
||||
strictAssert(groupSecretParamsBase64, 'Must have secret params');
|
||||
groupSendEndorsementState = new GroupSendEndorsementState(
|
||||
data,
|
||||
groupSecretParamsBase64
|
||||
if (state != null) {
|
||||
groupSendEndorsementState = state;
|
||||
} else if (didRefreshGroupState) {
|
||||
return startOver(
|
||||
'group send endorsements outside expiration range',
|
||||
true
|
||||
);
|
||||
|
||||
if (
|
||||
groupSendEndorsementState != null &&
|
||||
!groupSendEndorsementState.isSafeExpirationRange()
|
||||
) {
|
||||
log.info(
|
||||
`sendToGroupViaSenderKey/${logId}: Endorsements close to expiration (${groupSendEndorsementState.getExpiration().getTime()}, ${Date.now()}), refreshing group`
|
||||
);
|
||||
await maybeUpdateGroup({ conversation });
|
||||
return sendToGroupViaSenderKey(
|
||||
options,
|
||||
recursionCount + 1,
|
||||
'group send endorsements outside expiration range'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -412,11 +391,7 @@ export async function sendToGroupViaSenderKey(
|
|||
await fetchKeysForServiceIds(emptyServiceIds, groupSendEndorsementState);
|
||||
|
||||
// Restart here to capture devices for accounts we just started sessions with
|
||||
return sendToGroupViaSenderKey(
|
||||
options,
|
||||
recursionCount + 1,
|
||||
'fetched prekey bundles'
|
||||
);
|
||||
return startOver('fetched prekey bundles');
|
||||
}
|
||||
|
||||
const { memberDevices, distributionId, createdAtDate } = senderKeyInfo;
|
||||
|
@ -478,11 +453,7 @@ export async function sendToGroupViaSenderKey(
|
|||
|
||||
// Restart here to start over; empty memberDevices means we'll send distribution
|
||||
// message to everyone.
|
||||
return sendToGroupViaSenderKey(
|
||||
options,
|
||||
recursionCount + 1,
|
||||
'removed members in send target'
|
||||
);
|
||||
return startOver('removed members in send target');
|
||||
}
|
||||
|
||||
// 8. If there are new members or new devices in the group, we need to ensure that they
|
||||
|
@ -533,11 +504,7 @@ export async function sendToGroupViaSenderKey(
|
|||
|
||||
// Restart here because we might have discovered new or dropped devices as part of
|
||||
// distributing our sender key.
|
||||
return sendToGroupViaSenderKey(
|
||||
options,
|
||||
recursionCount + 1,
|
||||
'sent skdm to new members'
|
||||
);
|
||||
return startOver('sent skdm to new members');
|
||||
}
|
||||
|
||||
// 9. Update memberDevices with removals which didn't require a reset.
|
||||
|
@ -572,17 +539,12 @@ export async function sendToGroupViaSenderKey(
|
|||
senderKeyRecipientsWithDevices[serviceId].push(id);
|
||||
});
|
||||
|
||||
let groupSendToken: GroupSendToken | undefined;
|
||||
let accessKeys: Buffer | undefined;
|
||||
let groupSendToken: GroupSendToken | null = null;
|
||||
let accessKeys: Buffer | null = null;
|
||||
if (groupSendEndorsementState != null) {
|
||||
strictAssert(conversation, 'Must have conversation for endorsements');
|
||||
try {
|
||||
groupSendToken = groupSendEndorsementState.buildToken(
|
||||
new Set(senderKeyRecipients)
|
||||
);
|
||||
} catch (error) {
|
||||
onFailedToSendWithEndorsements(error);
|
||||
}
|
||||
groupSendToken = groupSendEndorsementState.buildToken(
|
||||
new Set(senderKeyRecipients)
|
||||
);
|
||||
} else {
|
||||
accessKeys = getXorOfAccessKeys(devicesForSenderKey, { story });
|
||||
}
|
||||
|
@ -656,22 +618,14 @@ export async function sendToGroupViaSenderKey(
|
|||
await handle409Response(sendTarget, groupSendEndorsementState, error);
|
||||
|
||||
// Restart here to capture the right set of devices for our next send.
|
||||
return sendToGroupViaSenderKey(
|
||||
options,
|
||||
recursionCount + 1,
|
||||
'error: expired or missing devices'
|
||||
);
|
||||
return startOver('error: expired or missing devices');
|
||||
}
|
||||
if (error.code === ERROR_STALE_DEVICES) {
|
||||
await handle410Response(sendTarget, groupSendEndorsementState, error);
|
||||
|
||||
// Restart here to use the right registrationIds for devices we already knew about,
|
||||
// as well as send our sender key to these re-registered or re-linked devices.
|
||||
return sendToGroupViaSenderKey(
|
||||
options,
|
||||
recursionCount + 1,
|
||||
'error: stale devices'
|
||||
);
|
||||
return startOver('error: stale devices');
|
||||
}
|
||||
if (
|
||||
error instanceof LibSignalErrorBase &&
|
||||
|
@ -689,11 +643,7 @@ export async function sendToGroupViaSenderKey(
|
|||
await DataWriter.updateConversation(brokenAccount.attributes);
|
||||
|
||||
// Now that we've eliminate this problematic account, we can try the send again.
|
||||
return sendToGroupViaSenderKey(
|
||||
options,
|
||||
recursionCount + 1,
|
||||
'error: invalid registration id'
|
||||
);
|
||||
return startOver('error: invalid registration id');
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue