Support for GV1 -> GV2 migration
This commit is contained in:
parent
a0baa3e03f
commit
2c69f2c367
32 changed files with 2626 additions and 341 deletions
|
@ -23,6 +23,7 @@ import WebSocketResource, {
|
|||
IncomingWebSocketRequest,
|
||||
} from './WebsocketResources';
|
||||
import Crypto from './Crypto';
|
||||
import { deriveMasterKeyFromGroupV1 } from '../Crypto';
|
||||
import { ContactBuffer, GroupBuffer } from './ContactsParser';
|
||||
import { IncomingIdentityKeyError } from './Errors';
|
||||
|
||||
|
@ -43,6 +44,8 @@ import { WebSocket } from './WebSocket';
|
|||
|
||||
import { deriveGroupFields, MASTER_KEY_LENGTH } from '../groups';
|
||||
|
||||
const GROUPV1_ID_LENGTH = 16;
|
||||
const GROUPV2_ID_LENGTH = 32;
|
||||
const RETRY_TIMEOUT = 2 * 60 * 1000;
|
||||
|
||||
declare global {
|
||||
|
@ -58,6 +61,7 @@ declare global {
|
|||
eventType?: string | number;
|
||||
groupDetails?: any;
|
||||
groupId?: string;
|
||||
groupV2Id?: string;
|
||||
messageRequestResponseType?: number | null;
|
||||
proto?: any;
|
||||
read?: any;
|
||||
|
@ -273,6 +277,7 @@ class MessageReceiverInner extends EventTarget {
|
|||
delete this.socket.onclose;
|
||||
delete this.socket.onerror;
|
||||
delete this.socket.onopen;
|
||||
|
||||
this.socket = undefined;
|
||||
}
|
||||
|
||||
|
@ -1201,7 +1206,13 @@ class MessageReceiverInner extends EventTarget {
|
|||
);
|
||||
}
|
||||
|
||||
this.deriveGroupsV2Data(msg);
|
||||
if (this.isInvalidGroupData(msg, envelope)) {
|
||||
this.removeFromCache(envelope);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this.deriveGroupV1Data(msg);
|
||||
this.deriveGroupV2Data(msg);
|
||||
|
||||
if (
|
||||
msg.flags &&
|
||||
|
@ -1377,7 +1388,7 @@ class MessageReceiverInner extends EventTarget {
|
|||
return Promise.all(results);
|
||||
}
|
||||
|
||||
handleTypingMessage(
|
||||
async handleTypingMessage(
|
||||
envelope: EnvelopeClass,
|
||||
typingMessage: TypingMessageClass
|
||||
) {
|
||||
|
@ -1403,25 +1414,29 @@ class MessageReceiverInner extends EventTarget {
|
|||
ev.senderUuid = envelope.sourceUuid;
|
||||
ev.senderDevice = envelope.sourceDevice;
|
||||
|
||||
const groupIdBuffer = groupId ? groupId.toArrayBuffer() : null;
|
||||
|
||||
ev.typing = {
|
||||
typingMessage,
|
||||
timestamp: timestamp ? timestamp.toNumber() : Date.now(),
|
||||
groupId:
|
||||
groupIdBuffer && groupIdBuffer.byteLength <= 16
|
||||
? groupId.toString('binary')
|
||||
: null,
|
||||
groupV2Id:
|
||||
groupIdBuffer && groupIdBuffer.byteLength > 16
|
||||
? groupId.toString('base64')
|
||||
: null,
|
||||
started:
|
||||
action === window.textsecure.protobuf.TypingMessage.Action.STARTED,
|
||||
stopped:
|
||||
action === window.textsecure.protobuf.TypingMessage.Action.STOPPED,
|
||||
};
|
||||
|
||||
const groupIdBuffer = groupId ? groupId.toArrayBuffer() : null;
|
||||
|
||||
if (groupIdBuffer && groupIdBuffer.byteLength > 0) {
|
||||
if (groupIdBuffer.byteLength === GROUPV1_ID_LENGTH) {
|
||||
ev.typing.groupId = groupId.toString('binary');
|
||||
ev.typing.groupV2Id = await this.deriveGroupV2FromV1(groupIdBuffer);
|
||||
} else if (groupIdBuffer.byteLength === GROUPV2_ID_LENGTH) {
|
||||
ev.typing.groupV2Id = groupId.toString('base64');
|
||||
} else {
|
||||
window.log.error('handleTypingMessage: Received invalid groupId value');
|
||||
this.removeFromCache(envelope);
|
||||
}
|
||||
}
|
||||
|
||||
return this.dispatchEvent(ev);
|
||||
}
|
||||
|
||||
|
@ -1430,7 +1445,76 @@ class MessageReceiverInner extends EventTarget {
|
|||
this.removeFromCache(envelope);
|
||||
}
|
||||
|
||||
deriveGroupsV2Data(message: DataMessageClass) {
|
||||
isInvalidGroupData(
|
||||
message: DataMessageClass,
|
||||
envelope: EnvelopeClass
|
||||
): boolean {
|
||||
const { group, groupV2 } = message;
|
||||
|
||||
if (group) {
|
||||
const id = group.id.toArrayBuffer();
|
||||
const isInvalid = id.byteLength !== GROUPV1_ID_LENGTH;
|
||||
|
||||
if (isInvalid) {
|
||||
window.log.info(
|
||||
'isInvalidGroupData: invalid GroupV1 message from',
|
||||
this.getEnvelopeId(envelope)
|
||||
);
|
||||
}
|
||||
|
||||
return isInvalid;
|
||||
}
|
||||
|
||||
if (groupV2) {
|
||||
const masterKey = groupV2.masterKey.toArrayBuffer();
|
||||
const isInvalid = masterKey.byteLength !== MASTER_KEY_LENGTH;
|
||||
|
||||
if (isInvalid) {
|
||||
window.log.info(
|
||||
'isInvalidGroupData: invalid GroupV2 message from',
|
||||
this.getEnvelopeId(envelope)
|
||||
);
|
||||
}
|
||||
return isInvalid;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
async deriveGroupV2FromV1(groupId: ArrayBuffer): Promise<string> {
|
||||
if (groupId.byteLength !== GROUPV1_ID_LENGTH) {
|
||||
throw new Error(
|
||||
`deriveGroupV2FromV1: had id with wrong byteLength: ${groupId.byteLength}`
|
||||
);
|
||||
}
|
||||
const masterKey = await deriveMasterKeyFromGroupV1(groupId);
|
||||
const data = deriveGroupFields(masterKey);
|
||||
|
||||
const toBase64 = MessageReceiverInner.arrayBufferToStringBase64;
|
||||
return toBase64(data.id);
|
||||
}
|
||||
|
||||
async deriveGroupV1Data(message: DataMessageClass) {
|
||||
const { group } = message;
|
||||
|
||||
if (!group) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!group.id) {
|
||||
throw new Error('deriveGroupV1Data: had falsey id');
|
||||
}
|
||||
|
||||
const id = group.id.toArrayBuffer();
|
||||
if (id.byteLength !== GROUPV1_ID_LENGTH) {
|
||||
throw new Error(
|
||||
`deriveGroupV1Data: had id with wrong byteLength: ${id.byteLength}`
|
||||
);
|
||||
}
|
||||
group.derivedGroupV2Id = await this.deriveGroupV2FromV1(id);
|
||||
}
|
||||
|
||||
deriveGroupV2Data(message: DataMessageClass) {
|
||||
const { groupV2 } = message;
|
||||
|
||||
if (!groupV2) {
|
||||
|
@ -1438,10 +1522,10 @@ class MessageReceiverInner extends EventTarget {
|
|||
}
|
||||
|
||||
if (!isNumber(groupV2.revision)) {
|
||||
throw new Error('deriveGroupsV2Data: revision was not a number');
|
||||
throw new Error('deriveGroupV2Data: revision was not a number');
|
||||
}
|
||||
if (!groupV2.masterKey) {
|
||||
throw new Error('deriveGroupsV2Data: had falsey masterKey');
|
||||
throw new Error('deriveGroupV2Data: had falsey masterKey');
|
||||
}
|
||||
|
||||
const toBase64 = MessageReceiverInner.arrayBufferToStringBase64;
|
||||
|
@ -1449,7 +1533,7 @@ class MessageReceiverInner extends EventTarget {
|
|||
const length = masterKey.byteLength;
|
||||
if (length !== MASTER_KEY_LENGTH) {
|
||||
throw new Error(
|
||||
`deriveGroupsV2Data: masterKey had length ${length}, expected ${MASTER_KEY_LENGTH}`
|
||||
`deriveGroupV2Data: masterKey had length ${length}, expected ${MASTER_KEY_LENGTH}`
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -1522,7 +1606,13 @@ class MessageReceiverInner extends EventTarget {
|
|||
);
|
||||
}
|
||||
|
||||
this.deriveGroupsV2Data(sentMessage.message);
|
||||
if (this.isInvalidGroupData(sentMessage.message, envelope)) {
|
||||
this.removeFromCache(envelope);
|
||||
return undefined;
|
||||
}
|
||||
|
||||
this.deriveGroupV1Data(sentMessage.message);
|
||||
this.deriveGroupV2Data(sentMessage.message);
|
||||
|
||||
window.log.info(
|
||||
'sent message to',
|
||||
|
@ -1630,14 +1720,32 @@ class MessageReceiverInner extends EventTarget {
|
|||
ev.confirm = this.removeFromCache.bind(this, envelope);
|
||||
ev.threadE164 = sync.threadE164;
|
||||
ev.threadUuid = sync.threadUuid;
|
||||
ev.groupId = sync.groupId ? sync.groupId.toString('binary') : null;
|
||||
ev.messageRequestResponseType = sync.type;
|
||||
|
||||
const idBuffer: ArrayBuffer = sync.groupId
|
||||
? sync.groupId.toArrayBuffer()
|
||||
: null;
|
||||
|
||||
if (idBuffer && idBuffer.byteLength > 0) {
|
||||
if (idBuffer.byteLength === GROUPV1_ID_LENGTH) {
|
||||
ev.groupId = sync.groupId.toString('binary');
|
||||
ev.groupV2Id = await this.deriveGroupV2FromV1(idBuffer);
|
||||
} else if (idBuffer.byteLength === GROUPV2_ID_LENGTH) {
|
||||
ev.groupV2Id = sync.groupId.toString('base64');
|
||||
} else {
|
||||
this.removeFromCache(envelope);
|
||||
window.log.error('Received message request with invalid groupId');
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
window.normalizeUuids(
|
||||
ev,
|
||||
['threadUuid'],
|
||||
'MessageReceiver::handleMessageRequestResponse'
|
||||
);
|
||||
|
||||
return this.dispatchAndWait(ev);
|
||||
}
|
||||
|
||||
async handleFetchLatest(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue