Support for translating Desktop sessions to libsignal-client sessions
This commit is contained in:
parent
44dfd28017
commit
c73e35b1b6
7 changed files with 1513 additions and 73 deletions
107
protos/LibSignal-Client.proto
Normal file
107
protos/LibSignal-Client.proto
Normal file
|
@ -0,0 +1,107 @@
|
|||
syntax = "proto3";
|
||||
|
||||
//
|
||||
// Copyright 2020-2021 Signal Messenger, LLC.
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
//
|
||||
|
||||
package signal.proto.storage;
|
||||
|
||||
message SessionStructure {
|
||||
message Chain {
|
||||
bytes sender_ratchet_key = 1;
|
||||
bytes sender_ratchet_key_private = 2;
|
||||
|
||||
message ChainKey {
|
||||
uint32 index = 1;
|
||||
bytes key = 2;
|
||||
}
|
||||
|
||||
ChainKey chain_key = 3;
|
||||
|
||||
message MessageKey {
|
||||
uint32 index = 1;
|
||||
bytes cipher_key = 2;
|
||||
bytes mac_key = 3;
|
||||
bytes iv = 4;
|
||||
}
|
||||
|
||||
repeated MessageKey message_keys = 4;
|
||||
}
|
||||
|
||||
message PendingPreKey {
|
||||
uint32 pre_key_id = 1;
|
||||
int32 signed_pre_key_id = 3;
|
||||
bytes base_key = 2;
|
||||
}
|
||||
|
||||
uint32 session_version = 1;
|
||||
bytes local_identity_public = 2;
|
||||
bytes remote_identity_public = 3;
|
||||
|
||||
bytes root_key = 4;
|
||||
uint32 previous_counter = 5;
|
||||
|
||||
Chain sender_chain = 6;
|
||||
// The order is significant; keys at the end are "older" and will get trimmed.
|
||||
repeated Chain receiver_chains = 7;
|
||||
|
||||
PendingPreKey pending_pre_key = 9;
|
||||
|
||||
uint32 remote_registration_id = 10;
|
||||
uint32 local_registration_id = 11;
|
||||
|
||||
bool needs_refresh = 12;
|
||||
bytes alice_base_key = 13;
|
||||
}
|
||||
|
||||
message RecordStructure {
|
||||
SessionStructure current_session = 1;
|
||||
// The order is significant; sessions at the end are "older" and will get trimmed.
|
||||
repeated SessionStructure previous_sessions = 2;
|
||||
}
|
||||
|
||||
message PreKeyRecordStructure {
|
||||
uint32 id = 1;
|
||||
bytes public_key = 2;
|
||||
bytes private_key = 3;
|
||||
}
|
||||
|
||||
message SignedPreKeyRecordStructure {
|
||||
uint32 id = 1;
|
||||
bytes public_key = 2;
|
||||
bytes private_key = 3;
|
||||
bytes signature = 4;
|
||||
fixed64 timestamp = 5;
|
||||
}
|
||||
|
||||
message IdentityKeyPairStructure {
|
||||
bytes public_key = 1;
|
||||
bytes private_key = 2;
|
||||
}
|
||||
|
||||
message SenderKeyStateStructure {
|
||||
message SenderChainKey {
|
||||
uint32 iteration = 1;
|
||||
bytes seed = 2;
|
||||
}
|
||||
|
||||
message SenderMessageKey {
|
||||
uint32 iteration = 1;
|
||||
bytes seed = 2;
|
||||
}
|
||||
|
||||
message SenderSigningKey {
|
||||
bytes public = 1;
|
||||
bytes private = 2;
|
||||
}
|
||||
|
||||
uint32 sender_key_id = 1;
|
||||
SenderChainKey sender_chain_key = 2;
|
||||
SenderSigningKey sender_signing_key = 3;
|
||||
repeated SenderMessageKey sender_message_keys = 4;
|
||||
}
|
||||
|
||||
message SenderKeyRecordStructure {
|
||||
repeated SenderKeyStateStructure sender_key_states = 1;
|
||||
}
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
/* eslint-disable no-console */
|
||||
|
||||
const ByteBuffer = require('../components/bytebuffer/dist/ByteBufferAB.js');
|
||||
const { setEnvironment, Environment } = require('../ts/environment');
|
||||
|
||||
before(() => {
|
||||
|
@ -17,6 +18,9 @@ global.window = {
|
|||
error: (...args) => console.error(...args),
|
||||
},
|
||||
i18n: key => `i18n(${key})`,
|
||||
dcodeIO: {
|
||||
ByteBuffer,
|
||||
},
|
||||
};
|
||||
|
||||
// For ducks/network.getEmptyState()
|
||||
|
|
985
ts/test-both/util/sessionTranslation_test.ts
Normal file
985
ts/test-both/util/sessionTranslation_test.ts
Normal file
|
@ -0,0 +1,985 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
import { assert } from 'chai';
|
||||
|
||||
import {
|
||||
LocalUserDataType,
|
||||
sessionRecordToProtobuf,
|
||||
} from '../../util/sessionTranslation';
|
||||
import { base64ToArrayBuffer } from '../../Crypto';
|
||||
|
||||
const getRecordCopy = (record: any): any => JSON.parse(JSON.stringify(record));
|
||||
|
||||
describe('sessionTranslation', () => {
|
||||
let ourData: LocalUserDataType;
|
||||
|
||||
beforeEach(() => {
|
||||
ourData = {
|
||||
identityKeyPublic: base64ToArrayBuffer(
|
||||
'Baioqfzc/5JD6b+GNqapPouf6eHK7xr9ynLJHnvl+444'
|
||||
),
|
||||
registrationId: 3554,
|
||||
};
|
||||
});
|
||||
|
||||
it('Throws if given an empty object', () => {
|
||||
const record: any = {};
|
||||
assert.throws(
|
||||
() => sessionRecordToProtobuf(record, ourData),
|
||||
'toProtobuf: Record had no sessions!'
|
||||
);
|
||||
});
|
||||
|
||||
it('Generates expected protobuf with minimal record', () => {
|
||||
const record: any = {
|
||||
sessions: {
|
||||
'\u0005W¿\u0000lÈ\nyª\u000eümB0\u0017j.Û£³-s\u0016Ä(O_M': {
|
||||
registrationId: 4243,
|
||||
currentRatchet: {
|
||||
rootKey:
|
||||
'Ë\u00035/üÚg\u0003Xeûú\u0010\u0000ü\u0002¶»o5\u001c¥\u0004Ðÿ«',
|
||||
lastRemoteEphemeralKey:
|
||||
'\u0005\n7\u001cmT
b!è\u000eÍ\u0007\u0016m4g³\u0005üIYê\b\u0011ÏÎPs',
|
||||
previousCounter: 2,
|
||||
ephemeralKeyPair: {
|
||||
privKey: 'äãÅ«ªàøí)á\u0005Á"sJM.¨¡\u0012r(N\f9Ô\b',
|
||||
pubKey: '\u0005+\u00134«1\u0000\u0013l *ãKçnºÖó³íTS&{ù Í>1',
|
||||
},
|
||||
},
|
||||
indexInfo: {
|
||||
remoteIdentityKey: '\u0005¨¨©üÏäúoá©êO¢çúxr»Æ¿r²GùiT@',
|
||||
closed: -1,
|
||||
baseKey: '\u0005W¿\u0000lÈ\nyª\u000eümB0\u0017j.Û£³-s\u0016Ä(O_M',
|
||||
baseKeyType: 2,
|
||||
},
|
||||
oldRatchetList: [],
|
||||
'\u0005\n7\u001cmT
b!è\u000eÍ\u0007\u0016m4g³\u0005üIYê\b\u0011ÏÎPs': {
|
||||
messageKeys: {
|
||||
'0': 'Îgó¯2àvñX_õ\u0014Ç\u0000öl\u001f4J>ÐÏ{`-Ü5¦',
|
||||
'4': 'c¿<µâ¼Xµ!Ù¯µ®[n<ìîúcoå©n\u0013"l]Ð',
|
||||
},
|
||||
chainKey: {
|
||||
counter: 5,
|
||||
key: 'Z{òÙ8سAÝdSZk\nÃ\u001cô¡\u001b[YÒ¶\u0016a°\u0004<',
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005+\u00134«1\u0000\u0013l *ãKçnºÖó³íTS&{ù Í>1': {
|
||||
messageKeys: {},
|
||||
chainKey: {
|
||||
counter: -1,
|
||||
key:
|
||||
"èB?7\u000f¯\u001e\u0010¨\u0007}:?¹\u0010$\\ë~ª\u0000gM0Õ'£\u0005",
|
||||
},
|
||||
chainType: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
version: 'v1',
|
||||
};
|
||||
|
||||
const expected = {
|
||||
currentSession: {
|
||||
sessionVersion: 1,
|
||||
localIdentityPublic: 'Baioqfzc/5JD6b+GNqapPouf6eHK7xr9ynLJHnvl+444',
|
||||
remoteIdentityPublic: 'BaioqfzP5JD6b+GNqepPouf6eHK7xr9ynLJHnvlpgVRA',
|
||||
rootKey: 'ywM1L/yc2ppnA1hl+/oQlwD8Ara7bzUcg5etpQTQ/6s=',
|
||||
previousCounter: 2,
|
||||
senderChain: {
|
||||
senderRatchetKey: 'BSsTNJarMQATbCAq40vnbrrW87PtVFOfJox7+SDNgj4x',
|
||||
senderRatchetKeyPrivate:
|
||||
'5JfjxauqiuD47SmI4QXBIoxzSk0uqKEScp0oTgw51Ag=',
|
||||
chainKey: {
|
||||
index: -1,
|
||||
key: '6EI/Nw+vHhCoB499OpM/kLkQJFzrfqoAZ00w1ZgnowU=',
|
||||
},
|
||||
},
|
||||
receiverChains: [
|
||||
{
|
||||
senderRatchetKey: 'BQo3HG1UhWIh6A7NBxZtNGezBZH8nElZjOqNCBHPzlBz',
|
||||
chainKey: {
|
||||
index: 5,
|
||||
key: 'Wnvy2TjYs0HdZFNahmsKw5cc9KEbW1nSwraDFmGwBDw=',
|
||||
},
|
||||
messageKeys: [
|
||||
{
|
||||
index: 0,
|
||||
cipherKey: 'xVreEbT7Vtrxs85JyGBj6Y+UWftQz4H72F5kWV4cxqM=',
|
||||
iv: 'TcRanSxZVWbuIq0xDRGnEw==',
|
||||
macKey: '5fW9aIKXhtwWp/5alNJUIXInZbztf2ywzQSpYrXoQ3A=',
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
cipherKey: 'A99HjM4pUugsQ5+2v48FGTGEhZPoW6wzW9MqSc11QQ4=',
|
||||
iv: 'bE8Ei2Rkaoz4SKRwdG4+tQ==',
|
||||
macKey: 'TOTdbAf0bCHOzcQ3lBaIm3yqmpEqvvldD0qTuDFmkAI=',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
remoteRegistrationId: 4243,
|
||||
localRegistrationId: 3554,
|
||||
aliceBaseKey: 'BVeHv5MAbMgKeaoO/G1CMBdqhC7bo7Mtc4EWxI0oT19N',
|
||||
},
|
||||
};
|
||||
|
||||
const recordCopy = getRecordCopy(record);
|
||||
|
||||
const actual = sessionRecordToProtobuf(record, ourData);
|
||||
|
||||
assert.deepEqual(expected, actual.toJSON());
|
||||
|
||||
// We want to ensure that conversion doesn't modify incoming data
|
||||
assert.deepEqual(record, recordCopy);
|
||||
});
|
||||
|
||||
it('Generates expected protobuf with many old receiver chains', () => {
|
||||
const record: any = {
|
||||
sessions: {
|
||||
'\u0005W¿\u0000lÈ\nyª\u000eümB0\u0017j.Û£³-s\u0016Ä(O_M': {
|
||||
registrationId: 4243,
|
||||
currentRatchet: {
|
||||
rootKey:
|
||||
'Ë\u00035/üÚg\u0003Xeûú\u0010\u0000ü\u0002¶»o5\u001c¥\u0004Ðÿ«',
|
||||
lastRemoteEphemeralKey:
|
||||
'\u0005\n7\u001cmT
b!è\u000eÍ\u0007\u0016m4g³\u0005üIYê\b\u0011ÏÎPs',
|
||||
previousCounter: 2,
|
||||
ephemeralKeyPair: {
|
||||
privKey: 'äãÅ«ªàøí)á\u0005Á"sJM.¨¡\u0012r(N\f9Ô\b',
|
||||
pubKey: '\u0005+\u00134«1\u0000\u0013l *ãKçnºÖó³íTS&{ù Í>1',
|
||||
},
|
||||
},
|
||||
indexInfo: {
|
||||
remoteIdentityKey: '\u0005¨¨©üÏäúoá©êO¢çúxr»Æ¿r²GùiT@',
|
||||
closed: -1,
|
||||
baseKey: '\u0005W¿\u0000lÈ\nyª\u000eümB0\u0017j.Û£³-s\u0016Ä(O_M',
|
||||
baseKeyType: 2,
|
||||
},
|
||||
oldRatchetList: [
|
||||
{
|
||||
added: 1605579954962,
|
||||
ephemeralKey:
|
||||
'\u00050»\n¨ÊAä\u0006¢Ç´d\u0002\u00129}%î}ΩTc}8¼\u0011n\\',
|
||||
},
|
||||
{
|
||||
added: 1605580408250,
|
||||
ephemeralKey:
|
||||
'\u0005^Ä\nòÀ¢\u0000\u000fA\\6+Ó\u001a÷&×$¸¬ÑÔ|<qSÖ\u001aÙh',
|
||||
},
|
||||
{
|
||||
added: 1605581155167,
|
||||
ephemeralKey:
|
||||
'\u0005<\u0017å)QàFîl29Ø\u001c Ý$·;udß\u0005I|f\u0006',
|
||||
},
|
||||
{
|
||||
added: 1605638524556,
|
||||
ephemeralKey: '\u0005¯jõ±ã0wÛPÐÂSÏ´;·&\u0011Â%º¯°ÝÙþêù8F',
|
||||
},
|
||||
{
|
||||
added: 1606761719753,
|
||||
ephemeralKey: '\u0005Î(ð>xÄÈ?þv~íkx â¬.ðoòDg\u001eß.\r',
|
||||
},
|
||||
{
|
||||
added: 1606766530935,
|
||||
ephemeralKey:
|
||||
'\u0005\u0014@½M,à\bóó
}¨`i¿\u0000©I\u0001ôG\u001f:Ù{ó\u0005 ',
|
||||
},
|
||||
{
|
||||
added: 1608326293655,
|
||||
ephemeralKey: '\u0005µÒ\u0014?È¢+ÑR÷ç?3Dº\\@0\u0004®+-\br\t',
|
||||
},
|
||||
{
|
||||
added: 1609871105317,
|
||||
ephemeralKey:
|
||||
'\u0005±@íN"Í\u0019HS{$ï\u0017[Ñ\\\u001a*;>P\u0000\u001f\u000eHNaù)',
|
||||
},
|
||||
{
|
||||
added: 1611707063523,
|
||||
ephemeralKey: '\u0005ÞgÅké\u0001\u0013¡ÿûNXÈ(9\u0006¤w®/عRiJI',
|
||||
},
|
||||
{
|
||||
added: 1612211156372,
|
||||
ephemeralKey: '\u0005:[ÛOpd¯ ÂÙç\u0010OÞw{}ý\bw9Àß=\u0014Z',
|
||||
},
|
||||
],
|
||||
'\u00050»\n¨ÊAä\u0006¢Ç´d\u0002\u00129}%î}ΩTc}8¼\u0011n\\': {
|
||||
messageKeys: {},
|
||||
chainKey: {
|
||||
counter: 0,
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005^Ä\nòÀ¢\u0000\u000fA\\6+Ó\u001a÷&×$¸¬ÑÔ|<qSÖ\u001aÙh': {
|
||||
messageKeys: {},
|
||||
chainKey: {
|
||||
counter: 2,
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005<\u0017å)QàFîl29Ø\u001c Ý$·;udß\u0005I|f\u0006': {
|
||||
messageKeys: {},
|
||||
chainKey: {
|
||||
counter: 1,
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005¯jõ±ã0wÛPÐÂSÏ´;·&\u0011Â%º¯°ÝÙþêù8F': {
|
||||
messageKeys: {
|
||||
'0': 'A/{´{×f(èaøy\\D¾\u0000ÃHÀÁâô$ã\u001d3Äö°Ù',
|
||||
'1': "̶FT}dw8Æýª7»ÚÓ\u000f*'Ô»7£\u0018\u0012ñDá",
|
||||
'2': 'Îï\u0013¨ÁÕÎk\u000eýèÈ÷,¼îû5%ÓU¤6_õ¢\u0019ä]',
|
||||
},
|
||||
chainKey: {
|
||||
counter: 3,
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005Î(ð>xÄÈ?þv~íkx â¬.ðoòDg\u001eß.\r': {
|
||||
messageKeys: {
|
||||
'4': '©}j¿¼\u0014q\t¥Áñ\u0003: ÷ÞrñûÔµ%Æ\u001a',
|
||||
},
|
||||
chainKey: {
|
||||
counter: 6,
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005\u0014@½M,à\bóó
}¨`i¿\u0000©I\u0001ôG\u001f:Ù{ó\u0005 ': {
|
||||
messageKeys: {},
|
||||
chainKey: {
|
||||
counter: 0,
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005µÒ\u0014?È¢+ÑR÷ç?3Dº\\@0\u0004®+-\br\t': {
|
||||
messageKeys: {},
|
||||
chainKey: {
|
||||
counter: 2,
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005±@íN"Í\u0019HS{$ï\u0017[Ñ\\\u001a*;>P\u0000\u001f\u000eHNaù)': {
|
||||
messageKeys: {
|
||||
'0': "1kÏ\u001cí+«<º\b'VÌ!×¼«PÃ[üáy;l'",
|
||||
'2': 'ö\u00047%L-
Wm)\u001d£ääíNô.Ô8
ÃÉ4r´ó^2',
|
||||
'3': '¨¿¦7T]\u001c\u001cà4:x\u0019¿\u0002YÉÀ\u001bâjr¸»¤¢0,*',
|
||||
'5': '¥\u0006·qgó4þ\u0011®U4F\u001cl©\bäô
»ÊÇÆ[',
|
||||
},
|
||||
chainKey: {
|
||||
counter: 5,
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005ÞgÅké\u0001\u0013¡ÿûNXÈ(9\u0006¤w®/عRiJI': {
|
||||
messageKeys: {
|
||||
'0': "]'8WÄ\u0007
nºÖ{ÿ7]ôäÄ!é\u000btA@°b¢)\u001ar",
|
||||
'2': 'ÄfGÇjÖxÅö:×RÔi)M\u0019©IE+¨`þKá;£Û½',
|
||||
'3': '¦Õhýø`ÖPéPs;\u001e\u000bE}¨¿õ\u0003uªøå\u00062(×G',
|
||||
'9': 'Ï^<ÕúÌ\u0001i´;ït¼\u001aÑ?ï\u0014lãàƸ\u001a8/m',
|
||||
},
|
||||
chainKey: {
|
||||
counter: 11,
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005:[ÛOpd¯ ÂÙç\u0010OÞw{}ý\bw9Àß=\u0014Z': {
|
||||
messageKeys: {
|
||||
'0': '!\u00115\\W~|¯oa2\u001e\u0004V8Ï¡d}\u001b\u001a8^QÖfvÕ"',
|
||||
},
|
||||
chainKey: {
|
||||
counter: 1,
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005\n7\u001cmT
b!è\u000eÍ\u0007\u0016m4g³\u0005üIYê\b\u0011ÏÎPs': {
|
||||
messageKeys: {
|
||||
'0': 'Îgó¯2àvñX_õ\u0014Ç\u0000öl\u001f4J>ÐÏ{`-Ü5¦',
|
||||
'4': 'c¿<µâ¼Xµ!Ù¯µ®[n<ìîúcoå©n\u0013"l]Ð',
|
||||
},
|
||||
chainKey: {
|
||||
counter: 5,
|
||||
key: 'Z{òÙ8سAÝdSZk\nÃ\u001cô¡\u001b[YÒ¶\u0016a°\u0004<',
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005+\u00134«1\u0000\u0013l *ãKçnºÖó³íTS&{ù Í>1': {
|
||||
messageKeys: {},
|
||||
chainKey: {
|
||||
counter: -1,
|
||||
key:
|
||||
"èB?7\u000f¯\u001e\u0010¨\u0007}:?¹\u0010$\\ë~ª\u0000gM0Õ'£\u0005",
|
||||
},
|
||||
chainType: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
version: 'v1',
|
||||
};
|
||||
|
||||
const expected = {
|
||||
currentSession: {
|
||||
sessionVersion: 1,
|
||||
localIdentityPublic: 'Baioqfzc/5JD6b+GNqapPouf6eHK7xr9ynLJHnvl+444',
|
||||
remoteIdentityPublic: 'BaioqfzP5JD6b+GNqepPouf6eHK7xr9ynLJHnvlpgVRA',
|
||||
rootKey: 'ywM1L/yc2ppnA1hl+/oQlwD8Ara7bzUcg5etpQTQ/6s=',
|
||||
previousCounter: 2,
|
||||
senderChain: {
|
||||
senderRatchetKey: 'BSsTNJarMQATbCAq40vnbrrW87PtVFOfJox7+SDNgj4x',
|
||||
senderRatchetKeyPrivate:
|
||||
'5JfjxauqiuD47SmI4QXBIoxzSk0uqKEScp0oTgw51Ag=',
|
||||
chainKey: {
|
||||
index: -1,
|
||||
key: '6EI/Nw+vHhCoB499OpM/kLkQJFzrfqoAZ00w1ZgnowU=',
|
||||
},
|
||||
},
|
||||
receiverChains: [
|
||||
{
|
||||
senderRatchetKey: 'BQo3HG1UhWIh6A7NBxZtNGezBZH8nElZjOqNCBHPzlBz',
|
||||
chainKey: {
|
||||
index: 5,
|
||||
key: 'Wnvy2TjYs0HdZFNahmsKw5cc9KEbW1nSwraDFmGwBDw=',
|
||||
},
|
||||
messageKeys: [
|
||||
{
|
||||
index: 0,
|
||||
cipherKey: 'xVreEbT7Vtrxs85JyGBj6Y+UWftQz4H72F5kWV4cxqM=',
|
||||
iv: 'TcRanSxZVWbuIq0xDRGnEw==',
|
||||
macKey: '5fW9aIKXhtwWp/5alNJUIXInZbztf2ywzQSpYrXoQ3A=',
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
cipherKey: 'A99HjM4pUugsQ5+2v48FGTGEhZPoW6wzW9MqSc11QQ4=',
|
||||
iv: 'bE8Ei2Rkaoz4SKRwdG4+tQ==',
|
||||
macKey: 'TOTdbAf0bCHOzcQ3lBaIm3yqmpEqvvldD0qTuDFmkAI=',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
senderRatchetKey: 'BTpb20+IlnBkryDC2ecQT96Hd3t9/Qh3ljnA3509kxRa',
|
||||
chainKey: {
|
||||
index: 1,
|
||||
},
|
||||
messageKeys: [
|
||||
{
|
||||
index: 0,
|
||||
cipherKey: 'aAbSz5jOagUTgQKo3aqExcl8hyZANrY+HvrLc/OgoQI=',
|
||||
iv: 'JcyLzw0fL67Kd4tfGJ2OUQ==',
|
||||
macKey: 'dt+RXeaeIx+ASrKSk7D4guwTE1IUYl3LiLG9aI4sZm8=',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
senderRatchetKey: 'Bd5nlMVr6YMBE5eh//tOWMgoOQakkneYri/YuVJpi0pJ',
|
||||
chainKey: {
|
||||
index: 11,
|
||||
},
|
||||
messageKeys: [
|
||||
{
|
||||
index: 0,
|
||||
cipherKey: 'pjcY/7MoRGtGHwNN/E8KqoKCx/5mdKp0VCmrmkBAj+M=',
|
||||
iv: 'eBpAEoDj94NsI0vsf+4Hrw==',
|
||||
macKey: 'P7Jz2KkOXC7B0mLkz7JaU/d0vdaYZjAfuKJ86xXB19U=',
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
cipherKey: 'EGDj0sc/1TMtSycYDCrpZdl6UCzCzDuMwlAvVVAs2OQ=',
|
||||
iv: 'A+1OA9M2Z8gGlARtA231RA==',
|
||||
macKey: 'oQ/PQxJDD52qrkShSy6hD3fASEfhWnlmY3qsSPuOY/o=',
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
cipherKey: 'WM3UUILGdECXjO8jZbBVYrPAnzRM8RdiU+PSAyHUT5U=',
|
||||
iv: 'CWuQIuIyGqApA6MQgnDR5Q==',
|
||||
macKey: 'hg+/xrOKFzn2eK1BnJ5C+ERsFgaWAOaBxQTc4q3b/g8=',
|
||||
},
|
||||
{
|
||||
index: 9,
|
||||
cipherKey: 'T0cBaGAseFz+s2njVr4sqbFf1pUH5PoPvdMBoizIT+Y=',
|
||||
iv: 'hkT2kqgqhlORAjBI7ZDsig==',
|
||||
macKey: 'uE/Dd4WSQWkYNRgolcQtOd+HpaHP5wGogMzErkZj+AQ=',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
senderRatchetKey: 'BYSxQO1OIs0ZSFN7JI/vF5Rb0VwaKjs+UAAfDkhOYfkp',
|
||||
chainKey: {
|
||||
index: 5,
|
||||
},
|
||||
messageKeys: [
|
||||
{
|
||||
index: 0,
|
||||
cipherKey: 'ni6XhRCoLFud2Zk1zoel4he8znDG/t+TWVBASO35GlQ=',
|
||||
iv: 'rKy/sxLmQ4j2DSxbDZTO5A==',
|
||||
macKey: 'MKxs29AmNOnp6zZOsIbrmSqcVXYJL01kuvIaqwjRNvQ=',
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
cipherKey: 'Pp7GOD72vfjvb3qx7qm1YVoZKPqnyXC2uqCt89ZA/yc=',
|
||||
iv: 'NuDf5iM0lD/o0YzjHZo4mA==',
|
||||
macKey: 'JkBZiaxmwFr1xh/zzTQE6mlUIVJmSIrqSIQVlaoTz7M=',
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
cipherKey: 'zORWRvJEUe2F4UnBwe2YRqPS4GzUFE1lWptcqMzWf2U=',
|
||||
iv: 'Og7jF9JJhiLtPD8W2OgTnw==',
|
||||
macKey: 'Lxbcl9fL9x5Javtdz7tOV7Bbr8ar3rWxSIsi1Focv9w=',
|
||||
},
|
||||
{
|
||||
index: 5,
|
||||
cipherKey: 'T/TZNw04+ZfB0s2ltOT9qbzRPnCFn7VvxqHHAvORFx0=',
|
||||
iv: 'DpOAK77ErIr2QFTsRnfOew==',
|
||||
macKey: 'k/fxafepBiA0dQOTpohL+EKm2+1jpFwRigVWt02U/Jg=',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
senderRatchetKey: 'BbXSFD/IoivRUvfnPzOaRLqDXEAwi4YEristfwiOj3IJ',
|
||||
chainKey: {
|
||||
index: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
senderRatchetKey: 'BRRAnr1NhizgCPPzmYV9qGBpvwCpSQH0Rx+UOtl78wUg',
|
||||
chainKey: {
|
||||
index: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
senderRatchetKey: 'BZvOKPA+kXiCg8TIP/52fu1reCDirC7wb5nyRGce3y4N',
|
||||
chainKey: {
|
||||
index: 6,
|
||||
},
|
||||
messageKeys: [
|
||||
{
|
||||
index: 4,
|
||||
cipherKey: 'PB44plPzHam/o2LZnyjo8HLRuAvp3uE6ixO5+GUCUsA=',
|
||||
iv: 'JBbgRb10X/dDsn0GKg69dA==',
|
||||
macKey: 'jKV1Rmlb0HATZHndLDIMONPgOXqT3kwE1QEstxXVe+o=',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
senderRatchetKey: 'Ba9q9bHjMHfbUNDCU8+0O7cmEcIluq+wk3/d2f7q+ThG',
|
||||
chainKey: {
|
||||
index: 3,
|
||||
},
|
||||
messageKeys: [
|
||||
{
|
||||
index: 0,
|
||||
cipherKey: '4buOJSqRFIpWwo4pXYwQTCTxas4+amBLpZ/CuEWXbPg=',
|
||||
iv: '9uD8ECO/fxtK28OvlCFXuQ==',
|
||||
macKey: 'LI0ZSdX7k+cd5bTgs6XEYYIWY+2cxhWI97vAGFpoZIc=',
|
||||
},
|
||||
{
|
||||
index: 1,
|
||||
cipherKey: 'oNbFxcy2eebUQhoD+NLf12fgkXzhn4EU0Pgqn1bVKOs=',
|
||||
iv: 'o1mm4rCN6Q0J1hA7I5jjgA==',
|
||||
macKey: 'dfHB14sCIdun+RaKnAoyaQPC6qRDMewjqOIDZGmn3Es=',
|
||||
},
|
||||
{
|
||||
index: 2,
|
||||
cipherKey: '/aU3zX2IdA91GAcB+7H57yzRe+6CgZ61tlW4M/rkCJI=',
|
||||
iv: 'v8VJF467QDD1ZCr1JD8pbQ==',
|
||||
macKey: 'MjK5iYjhZtQTJ4Eu3+qGOdYxn0G23EGRtTcusbzy9OA=',
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
senderRatchetKey: 'BTwX5SmcUeBG7mwyOZ3YgxyXIN0ktzuEdWTfBUmPfGYG',
|
||||
chainKey: {
|
||||
index: 1,
|
||||
},
|
||||
},
|
||||
{
|
||||
senderRatchetKey: 'BV7ECvKbwKIAD61BXDYr0xr3JtckuKzR1Hw8cVPWGtlo',
|
||||
chainKey: {
|
||||
index: 2,
|
||||
},
|
||||
},
|
||||
{
|
||||
senderRatchetKey: 'BTC7rQqoykGR5Aaix7RkAhI5fSXufc6pVGN9OIC8EW5c',
|
||||
chainKey: {
|
||||
index: 0,
|
||||
},
|
||||
},
|
||||
],
|
||||
remoteRegistrationId: 4243,
|
||||
localRegistrationId: 3554,
|
||||
aliceBaseKey: 'BVeHv5MAbMgKeaoO/G1CMBdqhC7bo7Mtc4EWxI0oT19N',
|
||||
},
|
||||
};
|
||||
|
||||
const recordCopy = getRecordCopy(record);
|
||||
|
||||
const actual = sessionRecordToProtobuf(record, ourData);
|
||||
|
||||
assert.deepEqual(expected, actual.toJSON());
|
||||
|
||||
// We want to ensure that conversion doesn't modify incoming data
|
||||
assert.deepEqual(record, recordCopy);
|
||||
});
|
||||
|
||||
it('Generates expected protobuf with pending prekey', () => {
|
||||
const record: any = {
|
||||
sessions: {
|
||||
'\u0005W¿\u0000lÈ\nyª\u000eümB0\u0017j.Û£³-s\u0016Ä(O_M': {
|
||||
registrationId: 4243,
|
||||
currentRatchet: {
|
||||
rootKey:
|
||||
'Ë\u00035/üÚg\u0003Xeûú\u0010\u0000ü\u0002¶»o5\u001c¥\u0004Ðÿ«',
|
||||
lastRemoteEphemeralKey:
|
||||
'\u0005\n7\u001cmT
b!è\u000eÍ\u0007\u0016m4g³\u0005üIYê\b\u0011ÏÎPs',
|
||||
previousCounter: 2,
|
||||
ephemeralKeyPair: {
|
||||
privKey: 'äãÅ«ªàøí)á\u0005Á"sJM.¨¡\u0012r(N\f9Ô\b',
|
||||
pubKey: '\u0005+\u00134«1\u0000\u0013l *ãKçnºÖó³íTS&{ù Í>1',
|
||||
},
|
||||
},
|
||||
indexInfo: {
|
||||
remoteIdentityKey: '\u0005¨¨©üÏäúoá©êO¢çúxr»Æ¿r²GùiT@',
|
||||
closed: -1,
|
||||
baseKey: '\u0005W¿\u0000lÈ\nyª\u000eümB0\u0017j.Û£³-s\u0016Ä(O_M',
|
||||
baseKeyType: 2,
|
||||
},
|
||||
pendingPreKey: {
|
||||
baseKey: '\u0005ui©üÏäúoá©êO¢çúxr»Æ¿r²GùiT@',
|
||||
signedKeyId: 38,
|
||||
preKeyId: 2,
|
||||
},
|
||||
oldRatchetList: [],
|
||||
'\u0005\n7\u001cmT
b!è\u000eÍ\u0007\u0016m4g³\u0005üIYê\b\u0011ÏÎPs': {
|
||||
messageKeys: {
|
||||
'0': 'Îgó¯2àvñX_õ\u0014Ç\u0000öl\u001f4J>ÐÏ{`-Ü5¦',
|
||||
'4': 'c¿<µâ¼Xµ!Ù¯µ®[n<ìîúcoå©n\u0013"l]Ð',
|
||||
},
|
||||
chainKey: {
|
||||
counter: 5,
|
||||
key: 'Z{òÙ8سAÝdSZk\nÃ\u001cô¡\u001b[YÒ¶\u0016a°\u0004<',
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005+\u00134«1\u0000\u0013l *ãKçnºÖó³íTS&{ù Í>1': {
|
||||
messageKeys: {},
|
||||
chainKey: {
|
||||
counter: -1,
|
||||
key:
|
||||
"èB?7\u000f¯\u001e\u0010¨\u0007}:?¹\u0010$\\ë~ª\u0000gM0Õ'£\u0005",
|
||||
},
|
||||
chainType: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
version: 'v1',
|
||||
};
|
||||
|
||||
const expected = {
|
||||
currentSession: {
|
||||
sessionVersion: 1,
|
||||
localIdentityPublic: 'Baioqfzc/5JD6b+GNqapPouf6eHK7xr9ynLJHnvl+444',
|
||||
remoteIdentityPublic: 'BaioqfzP5JD6b+GNqepPouf6eHK7xr9ynLJHnvlpgVRA',
|
||||
rootKey: 'ywM1L/yc2ppnA1hl+/oQlwD8Ara7bzUcg5etpQTQ/6s=',
|
||||
previousCounter: 2,
|
||||
senderChain: {
|
||||
senderRatchetKey: 'BSsTNJarMQATbCAq40vnbrrW87PtVFOfJox7+SDNgj4x',
|
||||
senderRatchetKeyPrivate:
|
||||
'5JfjxauqiuD47SmI4QXBIoxzSk0uqKEScp0oTgw51Ag=',
|
||||
chainKey: {
|
||||
index: -1,
|
||||
key: '6EI/Nw+vHhCoB499OpM/kLkQJFzrfqoAZ00w1ZgnowU=',
|
||||
},
|
||||
},
|
||||
receiverChains: [
|
||||
{
|
||||
senderRatchetKey: 'BQo3HG1UhWIh6A7NBxZtNGezBZH8nElZjOqNCBHPzlBz',
|
||||
chainKey: {
|
||||
index: 5,
|
||||
key: 'Wnvy2TjYs0HdZFNahmsKw5cc9KEbW1nSwraDFmGwBDw=',
|
||||
},
|
||||
messageKeys: [
|
||||
{
|
||||
index: 0,
|
||||
cipherKey: 'xVreEbT7Vtrxs85JyGBj6Y+UWftQz4H72F5kWV4cxqM=',
|
||||
iv: 'TcRanSxZVWbuIq0xDRGnEw==',
|
||||
macKey: '5fW9aIKXhtwWp/5alNJUIXInZbztf2ywzQSpYrXoQ3A=',
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
cipherKey: 'A99HjM4pUugsQ5+2v48FGTGEhZPoW6wzW9MqSc11QQ4=',
|
||||
iv: 'bE8Ei2Rkaoz4SKRwdG4+tQ==',
|
||||
macKey: 'TOTdbAf0bCHOzcQ3lBaIm3yqmpEqvvldD0qTuDFmkAI=',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
pendingPreKey: {
|
||||
preKeyId: 2,
|
||||
baseKey: 'BXVpqfzP5JD6b+GNqepPouf6eHK7xr9ynLJHnvlpgVRA',
|
||||
signedPreKeyId: 38,
|
||||
},
|
||||
remoteRegistrationId: 4243,
|
||||
localRegistrationId: 3554,
|
||||
aliceBaseKey: 'BVeHv5MAbMgKeaoO/G1CMBdqhC7bo7Mtc4EWxI0oT19N',
|
||||
},
|
||||
};
|
||||
|
||||
const recordCopy = getRecordCopy(record);
|
||||
|
||||
const actual = sessionRecordToProtobuf(record, ourData);
|
||||
|
||||
assert.deepEqual(expected, actual.toJSON());
|
||||
|
||||
// We want to ensure that conversion doesn't modify incoming data
|
||||
assert.deepEqual(record, recordCopy);
|
||||
});
|
||||
|
||||
it('Generates expected protobuf with multiple sessions', () => {
|
||||
const record: any = {
|
||||
sessions: {
|
||||
'\u0005W¿\u0000lÈ\nyª\u000eümB0\u0017j.Û£³-s\u0016Ä(O_M': {
|
||||
registrationId: 4243,
|
||||
currentRatchet: {
|
||||
rootKey:
|
||||
'Ë\u00035/üÚg\u0003Xeûú\u0010\u0000ü\u0002¶»o5\u001c¥\u0004Ðÿ«',
|
||||
lastRemoteEphemeralKey:
|
||||
'\u0005\n7\u001cmT
b!è\u000eÍ\u0007\u0016m4g³\u0005üIYê\b\u0011ÏÎPs',
|
||||
previousCounter: 2,
|
||||
ephemeralKeyPair: {
|
||||
privKey: 'äãÅ«ªàøí)á\u0005Á"sJM.¨¡\u0012r(N\f9Ô\b',
|
||||
pubKey: '\u0005+\u00134«1\u0000\u0013l *ãKçnºÖó³íTS&{ù Í>1',
|
||||
},
|
||||
},
|
||||
indexInfo: {
|
||||
remoteIdentityKey: '\u0005¨¨©üÏäúoá©êO¢çúxr»Æ¿r²GùiT@',
|
||||
closed: -1,
|
||||
baseKey: '\u0005W¿\u0000lÈ\nyª\u000eümB0\u0017j.Û£³-s\u0016Ä(O_M',
|
||||
baseKeyType: 2,
|
||||
},
|
||||
oldRatchetList: [],
|
||||
'\u0005\n7\u001cmT
b!è\u000eÍ\u0007\u0016m4g³\u0005üIYê\b\u0011ÏÎPs': {
|
||||
messageKeys: {
|
||||
'0': 'Îgó¯2àvñX_õ\u0014Ç\u0000öl\u001f4J>ÐÏ{`-Ü5¦',
|
||||
'4': 'c¿<µâ¼Xµ!Ù¯µ®[n<ìîúcoå©n\u0013"l]Ð',
|
||||
},
|
||||
chainKey: {
|
||||
counter: 5,
|
||||
key: 'Z{òÙ8سAÝdSZk\nÃ\u001cô¡\u001b[YÒ¶\u0016a°\u0004<',
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005+\u00134«1\u0000\u0013l *ãKçnºÖó³íTS&{ù Í>1': {
|
||||
messageKeys: {},
|
||||
chainKey: {
|
||||
counter: -1,
|
||||
key:
|
||||
"èB?7\u000f¯\u001e\u0010¨\u0007}:?¹\u0010$\\ë~ª\u0000gM0Õ'£\u0005",
|
||||
},
|
||||
chainType: 1,
|
||||
},
|
||||
},
|
||||
'\u0005BD¿Z\u0000lÈ\nyª\u000eümB0\u0017j.Û£³-s\u0016Ä(O_M': {
|
||||
registrationId: 3432,
|
||||
currentRatchet: {
|
||||
rootKey:
|
||||
'Ë\u00035/üÚg\u0003Xeûú\u0010\u0000ü\u0002¶»o5\u001c¥\u0004Ðÿ«',
|
||||
lastRemoteEphemeralKey:
|
||||
'\u0005\n7\u001cmT
b!è\u000eÍ\u0007\u0016m4g³\u0005üIYê\b\u0011ÏÎPs',
|
||||
previousCounter: 2,
|
||||
ephemeralKeyPair: {
|
||||
privKey: 'äãÅ«ªàøí)á\u0005Á"sJM.¨¡\u0012r(N\f9Ô\b',
|
||||
pubKey: '\u0005+\u00134«1\u0000\u0013l *ãKçnºÖó³íTS&{ù Í>1',
|
||||
},
|
||||
},
|
||||
indexInfo: {
|
||||
remoteIdentityKey: '\u0005¨¨©üÏäúoá©êO¢çúxr»Æ¿r²GùiT@',
|
||||
closed: 1605579954962,
|
||||
baseKey: '\u0005BD¿Z\u0000lÈ\nyª\u000eümB0\u0017j.Û£³-s\u0016Ä(O_M',
|
||||
baseKeyType: 2,
|
||||
},
|
||||
oldRatchetList: [],
|
||||
'\u0005\n7\u001cmT
b!è\u000eÍ\u0007\u0016m4g³\u0005üIYê\b\u0011ÏÎPs': {
|
||||
messageKeys: {
|
||||
'2': 'Îgó¯2àvñX_õ\u0014Ç\u0000öl\u001f4J>ÐÏ{`-Ü5¦',
|
||||
'3': 'c¿<µâ¼Xµ!Ù¯µ®[n<ìîúcoå©n\u0013"l]Ð',
|
||||
},
|
||||
chainKey: {
|
||||
counter: 5,
|
||||
key: 'Z{òÙ8سAÝdSZk\nÃ\u001cô¡\u001b[YÒ¶\u0016a°\u0004<',
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005+\u00134«1\u0000\u0013l *ãKçnºÖó³íTS&{ù Í>1': {
|
||||
messageKeys: {},
|
||||
chainKey: {
|
||||
counter: -1,
|
||||
key:
|
||||
"èB?7\u000f¯\u001e\u0010¨\u0007}:?¹\u0010$\\ë~ª\u0000gM0Õ'£\u0005",
|
||||
},
|
||||
chainType: 1,
|
||||
},
|
||||
},
|
||||
'\u0005AN¿C\u0000lÈ\nyª\u000eümB0\u0017j.Û£³-s\u0016Ä(O_M': {
|
||||
registrationId: 2312,
|
||||
currentRatchet: {
|
||||
rootKey:
|
||||
'Ë\u00035/üÚg\u0003Xeûú\u0010\u0000ü\u0002¶»o5\u001c¥\u0004Ðÿ«',
|
||||
lastRemoteEphemeralKey:
|
||||
'\u0005\n7\u001cmT
b!è\u000eÍ\u0007\u0016m4g³\u0005üIYê\b\u0011ÏÎPs',
|
||||
previousCounter: 2,
|
||||
ephemeralKeyPair: {
|
||||
privKey: 'äãÅ«ªàøí)á\u0005Á"sJM.¨¡\u0012r(N\f9Ô\b',
|
||||
pubKey: '\u0005+\u00134«1\u0000\u0013l *ãKçnºÖó³íTS&{ù Í>1',
|
||||
},
|
||||
},
|
||||
indexInfo: {
|
||||
remoteIdentityKey: '\u0005¨¨©üÏäúoá©êO¢çúxr»Æ¿r²GùiT@',
|
||||
closed: 1605580407000,
|
||||
baseKey: '\u0005AN¿C\u0000lÈ\nyª\u000eümB0\u0017j.Û£³-s\u0016Ä(O_M',
|
||||
baseKeyType: 2,
|
||||
},
|
||||
oldRatchetList: [],
|
||||
'\u0005\n7\u001cmT
b!è\u000eÍ\u0007\u0016m4g³\u0005üIYê\b\u0011ÏÎPs': {
|
||||
messageKeys: {
|
||||
'1': 'Îgó¯2àvñX_õ\u0014Ç\u0000öl\u001f4J>ÐÏ{`-Ü5¦',
|
||||
'5': 'c¿<µâ¼Xµ!Ù¯µ®[n<ìîúcoå©n\u0013"l]Ð',
|
||||
},
|
||||
chainKey: {
|
||||
counter: 5,
|
||||
key: 'Z{òÙ8سAÝdSZk\nÃ\u001cô¡\u001b[YÒ¶\u0016a°\u0004<',
|
||||
},
|
||||
chainType: 2,
|
||||
},
|
||||
'\u0005+\u00134«1\u0000\u0013l *ãKçnºÖó³íTS&{ù Í>1': {
|
||||
messageKeys: {},
|
||||
chainKey: {
|
||||
counter: -1,
|
||||
key:
|
||||
"èB?7\u000f¯\u001e\u0010¨\u0007}:?¹\u0010$\\ë~ª\u0000gM0Õ'£\u0005",
|
||||
},
|
||||
chainType: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
version: 'v1',
|
||||
};
|
||||
|
||||
const expected = {
|
||||
currentSession: {
|
||||
sessionVersion: 1,
|
||||
localIdentityPublic: 'Baioqfzc/5JD6b+GNqapPouf6eHK7xr9ynLJHnvl+444',
|
||||
remoteIdentityPublic: 'BaioqfzP5JD6b+GNqepPouf6eHK7xr9ynLJHnvlpgVRA',
|
||||
rootKey: 'ywM1L/yc2ppnA1hl+/oQlwD8Ara7bzUcg5etpQTQ/6s=',
|
||||
previousCounter: 2,
|
||||
senderChain: {
|
||||
senderRatchetKey: 'BSsTNJarMQATbCAq40vnbrrW87PtVFOfJox7+SDNgj4x',
|
||||
senderRatchetKeyPrivate:
|
||||
'5JfjxauqiuD47SmI4QXBIoxzSk0uqKEScp0oTgw51Ag=',
|
||||
chainKey: {
|
||||
index: -1,
|
||||
key: '6EI/Nw+vHhCoB499OpM/kLkQJFzrfqoAZ00w1ZgnowU=',
|
||||
},
|
||||
},
|
||||
receiverChains: [
|
||||
{
|
||||
senderRatchetKey: 'BQo3HG1UhWIh6A7NBxZtNGezBZH8nElZjOqNCBHPzlBz',
|
||||
chainKey: {
|
||||
index: 5,
|
||||
key: 'Wnvy2TjYs0HdZFNahmsKw5cc9KEbW1nSwraDFmGwBDw=',
|
||||
},
|
||||
messageKeys: [
|
||||
{
|
||||
index: 0,
|
||||
cipherKey: 'xVreEbT7Vtrxs85JyGBj6Y+UWftQz4H72F5kWV4cxqM=',
|
||||
iv: 'TcRanSxZVWbuIq0xDRGnEw==',
|
||||
macKey: '5fW9aIKXhtwWp/5alNJUIXInZbztf2ywzQSpYrXoQ3A=',
|
||||
},
|
||||
{
|
||||
index: 4,
|
||||
cipherKey: 'A99HjM4pUugsQ5+2v48FGTGEhZPoW6wzW9MqSc11QQ4=',
|
||||
iv: 'bE8Ei2Rkaoz4SKRwdG4+tQ==',
|
||||
macKey: 'TOTdbAf0bCHOzcQ3lBaIm3yqmpEqvvldD0qTuDFmkAI=',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
remoteRegistrationId: 4243,
|
||||
localRegistrationId: 3554,
|
||||
aliceBaseKey: 'BVeHv5MAbMgKeaoO/G1CMBdqhC7bo7Mtc4EWxI0oT19N',
|
||||
},
|
||||
previousSessions: [
|
||||
{
|
||||
sessionVersion: 1,
|
||||
localIdentityPublic: 'Baioqfzc/5JD6b+GNqapPouf6eHK7xr9ynLJHnvl+444',
|
||||
remoteIdentityPublic: 'BaioqfzP5JD6b+GNqepPouf6eHK7xr9ynLJHnvlpgVRA',
|
||||
rootKey: 'ywM1L/yc2ppnA1hl+/oQlwD8Ara7bzUcg5etpQTQ/6s=',
|
||||
previousCounter: 2,
|
||||
senderChain: {
|
||||
senderRatchetKey: 'BSsTNJarMQATbCAq40vnbrrW87PtVFOfJox7+SDNgj4x',
|
||||
senderRatchetKeyPrivate:
|
||||
'5JfjxauqiuD47SmI4QXBIoxzSk0uqKEScp0oTgw51Ag=',
|
||||
chainKey: {
|
||||
index: -1,
|
||||
key: '6EI/Nw+vHhCoB499OpM/kLkQJFzrfqoAZ00w1ZgnowU=',
|
||||
},
|
||||
},
|
||||
receiverChains: [
|
||||
{
|
||||
senderRatchetKey: 'BQo3HG1UhWIh6A7NBxZtNGezBZH8nElZjOqNCBHPzlBz',
|
||||
chainKey: {
|
||||
index: 5,
|
||||
key: 'Wnvy2TjYs0HdZFNahmsKw5cc9KEbW1nSwraDFmGwBDw=',
|
||||
},
|
||||
messageKeys: [
|
||||
{
|
||||
index: 1,
|
||||
cipherKey: 'xVreEbT7Vtrxs85JyGBj6Y+UWftQz4H72F5kWV4cxqM=',
|
||||
iv: 'TcRanSxZVWbuIq0xDRGnEw==',
|
||||
macKey: '5fW9aIKXhtwWp/5alNJUIXInZbztf2ywzQSpYrXoQ3A=',
|
||||
},
|
||||
{
|
||||
index: 5,
|
||||
cipherKey: 'A99HjM4pUugsQ5+2v48FGTGEhZPoW6wzW9MqSc11QQ4=',
|
||||
iv: 'bE8Ei2Rkaoz4SKRwdG4+tQ==',
|
||||
macKey: 'TOTdbAf0bCHOzcQ3lBaIm3yqmpEqvvldD0qTuDFmkAI=',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
remoteRegistrationId: 2312,
|
||||
localRegistrationId: 3554,
|
||||
aliceBaseKey: 'BUFOv0MAbMgKeaoO/G1CMBdqhC7bo7Mtc4EWxI0oT19N',
|
||||
},
|
||||
{
|
||||
sessionVersion: 1,
|
||||
localIdentityPublic: 'Baioqfzc/5JD6b+GNqapPouf6eHK7xr9ynLJHnvl+444',
|
||||
remoteIdentityPublic: 'BaioqfzP5JD6b+GNqepPouf6eHK7xr9ynLJHnvlpgVRA',
|
||||
rootKey: 'ywM1L/yc2ppnA1hl+/oQlwD8Ara7bzUcg5etpQTQ/6s=',
|
||||
previousCounter: 2,
|
||||
senderChain: {
|
||||
senderRatchetKey: 'BSsTNJarMQATbCAq40vnbrrW87PtVFOfJox7+SDNgj4x',
|
||||
senderRatchetKeyPrivate:
|
||||
'5JfjxauqiuD47SmI4QXBIoxzSk0uqKEScp0oTgw51Ag=',
|
||||
chainKey: {
|
||||
index: -1,
|
||||
key: '6EI/Nw+vHhCoB499OpM/kLkQJFzrfqoAZ00w1ZgnowU=',
|
||||
},
|
||||
},
|
||||
receiverChains: [
|
||||
{
|
||||
senderRatchetKey: 'BQo3HG1UhWIh6A7NBxZtNGezBZH8nElZjOqNCBHPzlBz',
|
||||
chainKey: {
|
||||
index: 5,
|
||||
key: 'Wnvy2TjYs0HdZFNahmsKw5cc9KEbW1nSwraDFmGwBDw=',
|
||||
},
|
||||
messageKeys: [
|
||||
{
|
||||
index: 2,
|
||||
cipherKey: 'xVreEbT7Vtrxs85JyGBj6Y+UWftQz4H72F5kWV4cxqM=',
|
||||
iv: 'TcRanSxZVWbuIq0xDRGnEw==',
|
||||
macKey: '5fW9aIKXhtwWp/5alNJUIXInZbztf2ywzQSpYrXoQ3A=',
|
||||
},
|
||||
{
|
||||
index: 3,
|
||||
cipherKey: 'A99HjM4pUugsQ5+2v48FGTGEhZPoW6wzW9MqSc11QQ4=',
|
||||
iv: 'bE8Ei2Rkaoz4SKRwdG4+tQ==',
|
||||
macKey: 'TOTdbAf0bCHOzcQ3lBaIm3yqmpEqvvldD0qTuDFmkAI=',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
remoteRegistrationId: 3432,
|
||||
localRegistrationId: 3554,
|
||||
aliceBaseKey: 'BUJEv1oAbMgKeaoO/G1CMBdqhC7bo7Mtc4EWxI0oT19N',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
const recordCopy = getRecordCopy(record);
|
||||
|
||||
const actual = sessionRecordToProtobuf(record, ourData);
|
||||
|
||||
assert.deepEqual(expected, actual.toJSON());
|
||||
|
||||
// We want to ensure that conversion doesn't modify incoming data
|
||||
assert.deepEqual(record, recordCopy);
|
||||
});
|
||||
|
||||
it('Generates expected protobuf with just-initialized session', () => {
|
||||
const record: any = {
|
||||
sessions: {
|
||||
'\u00055>=eV¹\u0019Ûn¾¯#߶_=\u0013.Nî\u001a¥%
-]ù_\n': {
|
||||
registrationId: 3188,
|
||||
currentRatchet: {
|
||||
rootKey: '\u001b16Êæðʨ¾>}Ú©ÄH¸sNÓ:ÈF¹³QÖi',
|
||||
lastRemoteEphemeralKey:
|
||||
'\u0005KÆ\\û«\u0003Ñ\u0005ÚûU±iú\u0012iÃ\u0011]¼åUà\u001f¯òÉ~&\u0003',
|
||||
previousCounter: 0,
|
||||
ephemeralKeyPair: {
|
||||
privKey:
|
||||
" -&\t]$\u0015P\u001fù\u000e\u001c\u001e'y
\u001eïËîEÑ+éaª± :wM",
|
||||
pubKey: '\u0005\u0014¦ç\u0002ò\u001aÆå\u001a{Ø1´ènnÇ(ÛK©8PË"h',
|
||||
},
|
||||
},
|
||||
indexInfo: {
|
||||
remoteIdentityKey: '\u0005\u0019Úä§\u0006×dâ°u§õ`EËTe%H¢!&Ù8cz*',
|
||||
closed: -1,
|
||||
baseKey: '\u00055>=eV¹\u0019Ûn¾¯#߶_=\u0013.Nî\u001a¥%
-]ù_\n',
|
||||
baseKeyType: 1,
|
||||
},
|
||||
oldRatchetList: [],
|
||||
'\u0005\u0014¦ç\u0002ò\u001aÆå\u001a{Ø1´ènnÇ(ÛK©8PË"h': {
|
||||
messageKeys: {},
|
||||
chainKey: {
|
||||
counter: 0,
|
||||
key: '¶^Do/jî\u000fU諪\u0011xnõ\u0011Æò}Ðó*äÇÊÂ\u0000',
|
||||
},
|
||||
chainType: 1,
|
||||
},
|
||||
pendingPreKey: {
|
||||
signedKeyId: 2995,
|
||||
baseKey: '\u00055>=eV¹\u0019Ûn¾¯#߶_=\u0013.Nî\u001a¥%
-]ù_\n',
|
||||
preKeyId: 386,
|
||||
},
|
||||
},
|
||||
},
|
||||
version: 'v1',
|
||||
};
|
||||
|
||||
const expected = {
|
||||
currentSession: {
|
||||
aliceBaseKey: 'BTU+PWVWuRnbiW6+ja+XI9+2Xz0TLk7uGqUlhS1d+V8K',
|
||||
localIdentityPublic: 'Baioqfzc/5JD6b+GNqapPouf6eHK7xr9ynLJHnvl+444',
|
||||
localRegistrationId: 3554,
|
||||
pendingPreKey: {
|
||||
baseKey: 'BTU+PWVWuRnbiW6+ja+XI9+2Xz0TLk7uGqUlhS1d+V8K',
|
||||
preKeyId: 386,
|
||||
signedPreKeyId: 2995,
|
||||
},
|
||||
previousCounter: 0,
|
||||
remoteIdentityPublic: 'BRmB2uSNpwbXZJjisIh1p/VgRctUZSVIoiEm2ThjiHoq',
|
||||
remoteRegistrationId: 3188,
|
||||
rootKey: 'GzGfNozK5vDKqL4+fdqpiMRIuHNOndM6iMhGubNR1mk=',
|
||||
senderChain: {
|
||||
chainKey: {
|
||||
index: 0,
|
||||
key: 'tl5Eby9q7n8PVeiriKoRjHhu9Y0RxvJ90PMq5MfKwgA=',
|
||||
},
|
||||
senderRatchetKey: 'BRSm55wC8hrG5Rp7l9gxtOhugp5ulcco20upOFCPyyJo',
|
||||
senderRatchetKeyPrivate:
|
||||
'IC0mCV0kFVAf+Q4cHid5hR7vy+5F0SvpYYaqsSA6d00=',
|
||||
},
|
||||
sessionVersion: 1,
|
||||
},
|
||||
};
|
||||
|
||||
const recordCopy = getRecordCopy(record);
|
||||
|
||||
const actual = sessionRecordToProtobuf(record, ourData);
|
||||
|
||||
assert.deepEqual(expected, actual.toJSON());
|
||||
|
||||
// We want to ensure that conversion doesn't modify incoming data
|
||||
assert.deepEqual(record, recordCopy);
|
||||
});
|
||||
});
|
|
@ -22,6 +22,10 @@ import { sleep } from './sleep';
|
|||
import { longRunningTaskWrapper } from './longRunningTaskWrapper';
|
||||
import { toWebSafeBase64, fromWebSafeBase64 } from './webSafeBase64';
|
||||
import { mapToSupportLocale } from './mapToSupportLocale';
|
||||
import {
|
||||
sessionRecordToProtobuf,
|
||||
sessionStructureToArrayBuffer,
|
||||
} from './sessionTranslation';
|
||||
import * as zkgroup from './zkgroup';
|
||||
|
||||
export {
|
||||
|
@ -45,6 +49,8 @@ export {
|
|||
missingCaseError,
|
||||
parseRemoteClientExpiration,
|
||||
Registration,
|
||||
sessionRecordToProtobuf,
|
||||
sessionStructureToArrayBuffer,
|
||||
sleep,
|
||||
toWebSafeBase64,
|
||||
zkgroup,
|
||||
|
|
|
@ -15241,78 +15241,6 @@
|
|||
"reasonCategory": "falseMatch",
|
||||
"updated": "2020-02-07T19:52:28.522Z"
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-before(",
|
||||
"path": "ts/test-electron/models/messages_test.js",
|
||||
"line": " before(async () => {",
|
||||
"lineNumber": 47,
|
||||
"reasonCategory": "testCode",
|
||||
"updated": "2020-10-21T00:45:53.649Z",
|
||||
"reasonDetail": "Test code and a false positive."
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-load(",
|
||||
"path": "ts/test-electron/models/messages_test.js",
|
||||
"line": " await window.ConversationController.load();",
|
||||
"lineNumber": 49,
|
||||
"reasonCategory": "testCode",
|
||||
"updated": "2020-10-21T00:45:53.649Z",
|
||||
"reasonDetail": "Test code and a false positive."
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-after(",
|
||||
"path": "ts/test-electron/models/messages_test.js",
|
||||
"line": " after(async () => {",
|
||||
"lineNumber": 53,
|
||||
"reasonCategory": "testCode",
|
||||
"updated": "2020-10-21T00:45:53.649Z",
|
||||
"reasonDetail": "Test code and a false positive."
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-before(",
|
||||
"path": "ts/test-electron/models/messages_test.ts",
|
||||
"line": " before(async () => {",
|
||||
"lineNumber": 29,
|
||||
"reasonCategory": "testCode",
|
||||
"updated": "2020-10-21T00:45:53.649Z",
|
||||
"reasonDetail": "Test code and a false positive."
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-load(",
|
||||
"path": "ts/test-electron/models/messages_test.ts",
|
||||
"line": " await window.ConversationController.load();",
|
||||
"lineNumber": 31,
|
||||
"reasonCategory": "testCode",
|
||||
"updated": "2020-10-21T00:45:53.649Z",
|
||||
"reasonDetail": "Test code and a false positive."
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-after(",
|
||||
"path": "ts/test-electron/models/messages_test.ts",
|
||||
"line": " after(async () => {",
|
||||
"lineNumber": 36,
|
||||
"reasonCategory": "testCode",
|
||||
"updated": "2020-10-21T00:45:53.649Z",
|
||||
"reasonDetail": "Test code and a false positive."
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-before(",
|
||||
"path": "ts/test-node/util/windowsZoneIdentifier_test.js",
|
||||
"line": " before(function thisNeeded() {",
|
||||
"lineNumber": 33,
|
||||
"reasonCategory": "testCode",
|
||||
"updated": "2020-09-02T18:59:59.432Z",
|
||||
"reasonDetail": "This is test code (and isn't jQuery code)."
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-before(",
|
||||
"path": "ts/test-node/util/windowsZoneIdentifier_test.ts",
|
||||
"line": " before(function thisNeeded() {",
|
||||
"lineNumber": 15,
|
||||
"reasonCategory": "testCode",
|
||||
"updated": "2020-09-02T18:59:59.432Z",
|
||||
"reasonDetail": "This is test code (and isn't jQuery code)."
|
||||
},
|
||||
{
|
||||
"rule": "jQuery-append(",
|
||||
"path": "ts/textsecure/ContactsParser.js",
|
||||
|
@ -15466,4 +15394,4 @@
|
|||
"updated": "2021-01-08T15:46:32.143Z",
|
||||
"reasonDetail": "Doesn't manipulate the DOM. This is just a function."
|
||||
}
|
||||
]
|
||||
]
|
||||
|
|
|
@ -72,6 +72,7 @@ const excludedFilesRegexps = [
|
|||
'^libtextsecure/test/test.js',
|
||||
'^sticker-creator/dist/bundle.js',
|
||||
'^test/test.js',
|
||||
'^ts/test[^/]*/.+',
|
||||
|
||||
// From libsignal-protocol-javascript project
|
||||
'^libtextsecure/libsignal-protocol.js',
|
||||
|
|
409
ts/util/sessionTranslation.ts
Normal file
409
ts/util/sessionTranslation.ts
Normal file
|
@ -0,0 +1,409 @@
|
|||
// Copyright 2021 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { get, isFinite, isInteger, isString } from 'lodash';
|
||||
import { HKDF } from 'libsignal-client';
|
||||
|
||||
import { signal } from '../protobuf/compiled';
|
||||
import {
|
||||
bytesFromString,
|
||||
fromEncodedBinaryToArrayBuffer,
|
||||
typedArrayToArrayBuffer,
|
||||
} from '../Crypto';
|
||||
|
||||
const { RecordStructure, SessionStructure } = signal.proto.storage;
|
||||
const { Chain } = SessionStructure;
|
||||
|
||||
type KeyPairType = {
|
||||
privKey?: string;
|
||||
pubKey?: string;
|
||||
};
|
||||
|
||||
type OldRatchetType = {
|
||||
added?: number;
|
||||
ephemeralKey?: string;
|
||||
};
|
||||
|
||||
type SessionType = {
|
||||
registrationId?: number;
|
||||
currentRatchet?: {
|
||||
rootKey?: string;
|
||||
lastRemoteEphemeralKey?: string;
|
||||
previousCounter?: number;
|
||||
ephemeralKeyPair?: KeyPairType;
|
||||
};
|
||||
indexInfo?: {
|
||||
remoteIdentityKey?: string;
|
||||
closed?: number;
|
||||
baseKey?: string;
|
||||
baseKeyType?: number;
|
||||
};
|
||||
pendingPreKey?: {
|
||||
baseKey?: string;
|
||||
signedPreKeyId?: number;
|
||||
// The first two are required; this one is optional
|
||||
preKeyId?: number;
|
||||
};
|
||||
oldRatchetList?: Array<OldRatchetType>;
|
||||
|
||||
// Note: ChainTypes are stored here, keyed by their baseKey. Typescript
|
||||
/// doesn't allow that kind of combination definition (known keys and
|
||||
// indexer), so we force session to `any` below whenever we access it like
|
||||
// `session[baseKey]`.
|
||||
};
|
||||
|
||||
type MessageKeyGroup = {
|
||||
[key: string]: string;
|
||||
};
|
||||
|
||||
type ChainType = {
|
||||
messageKeys?: MessageKeyGroup;
|
||||
chainKey?: {
|
||||
counter?: number;
|
||||
key?: string;
|
||||
};
|
||||
chainType: number;
|
||||
};
|
||||
|
||||
type SessionListType = {
|
||||
[key: string]: SessionType;
|
||||
};
|
||||
|
||||
type SessionRecordType = {
|
||||
sessions?: SessionListType;
|
||||
version?: 'v1';
|
||||
};
|
||||
|
||||
export type LocalUserDataType = {
|
||||
identityKeyPublic: ArrayBuffer;
|
||||
registrationId: number;
|
||||
};
|
||||
|
||||
export function sessionStructureToArrayBuffer(
|
||||
recordStructure: signal.proto.storage.RecordStructure
|
||||
): ArrayBuffer {
|
||||
return typedArrayToArrayBuffer(
|
||||
signal.proto.storage.RecordStructure.encode(recordStructure).finish()
|
||||
);
|
||||
}
|
||||
|
||||
export function sessionRecordToProtobuf(
|
||||
record: SessionRecordType,
|
||||
ourData: LocalUserDataType
|
||||
): signal.proto.storage.RecordStructure {
|
||||
const proto = new RecordStructure();
|
||||
|
||||
proto.previousSessions = [];
|
||||
|
||||
const sessionGroup = record.sessions || {};
|
||||
const sessions = Object.values(sessionGroup);
|
||||
|
||||
const first = sessions.find(session => {
|
||||
return session?.indexInfo?.closed === -1;
|
||||
});
|
||||
|
||||
if (first) {
|
||||
proto.currentSession = toProtobufSession(first, ourData);
|
||||
}
|
||||
|
||||
sessions.sort((left, right) => {
|
||||
// Descending - we want recently-closed sessions to be first
|
||||
return (right?.indexInfo?.closed || 0) - (left?.indexInfo?.closed || 0);
|
||||
});
|
||||
const onlyClosed = sessions.filter(
|
||||
session => session?.indexInfo?.closed !== -1
|
||||
);
|
||||
|
||||
if (onlyClosed.length < sessions.length - 1) {
|
||||
throw new Error('toProtobuf: More than one open session!');
|
||||
}
|
||||
|
||||
proto.previousSessions = [];
|
||||
onlyClosed.forEach(session => {
|
||||
proto.previousSessions.push(toProtobufSession(session, ourData));
|
||||
});
|
||||
|
||||
if (!proto.currentSession && proto.previousSessions.length === 0) {
|
||||
throw new Error('toProtobuf: Record had no sessions!');
|
||||
}
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
function toProtobufSession(
|
||||
session: SessionType,
|
||||
ourData: LocalUserDataType
|
||||
): signal.proto.storage.SessionStructure {
|
||||
const proto = new SessionStructure();
|
||||
|
||||
// Core Fields
|
||||
|
||||
proto.aliceBaseKey = binaryToUint8Array(session, 'indexInfo.baseKey', 33);
|
||||
proto.localIdentityPublic = new Uint8Array(ourData.identityKeyPublic);
|
||||
proto.localRegistrationId = ourData.registrationId;
|
||||
|
||||
proto.previousCounter = getInteger(session, 'currentRatchet.previousCounter');
|
||||
proto.remoteIdentityPublic = binaryToUint8Array(
|
||||
session,
|
||||
'indexInfo.remoteIdentityKey',
|
||||
33
|
||||
);
|
||||
proto.remoteRegistrationId = getInteger(session, 'registrationId');
|
||||
proto.rootKey = binaryToUint8Array(session, 'currentRatchet.rootKey', 32);
|
||||
proto.sessionVersion = 1;
|
||||
|
||||
// Note: currently unused
|
||||
// proto.needsRefresh = null;
|
||||
|
||||
// Pending PreKey
|
||||
|
||||
if (session.pendingPreKey) {
|
||||
proto.pendingPreKey = new signal.proto.storage.SessionStructure.PendingPreKey();
|
||||
proto.pendingPreKey.baseKey = binaryToUint8Array(
|
||||
session,
|
||||
'pendingPreKey.baseKey',
|
||||
33
|
||||
);
|
||||
proto.pendingPreKey.signedPreKeyId = getInteger(
|
||||
session,
|
||||
'pendingPreKey.signedKeyId'
|
||||
);
|
||||
|
||||
if (session.pendingPreKey.preKeyId !== undefined) {
|
||||
proto.pendingPreKey.preKeyId = getInteger(
|
||||
session,
|
||||
'pendingPreKey.preKeyId'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Sender Chain
|
||||
|
||||
const senderBaseKey = session.currentRatchet?.ephemeralKeyPair?.pubKey;
|
||||
if (!senderBaseKey) {
|
||||
throw new Error('toProtobufSession: No sender base key!');
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const senderChain = (session as any)[senderBaseKey] as ChainType | undefined;
|
||||
if (!senderChain) {
|
||||
throw new Error(
|
||||
'toProtobufSession: No matching chain found with senderBaseKey!'
|
||||
);
|
||||
}
|
||||
|
||||
if (senderChain.chainType !== 1) {
|
||||
throw new Error(
|
||||
`toProtobufSession: Expected sender chain type for senderChain, got ${senderChain.chainType}`
|
||||
);
|
||||
}
|
||||
|
||||
const protoSenderChain = toProtobufChain(senderChain);
|
||||
|
||||
protoSenderChain.senderRatchetKey = binaryToUint8Array(
|
||||
session,
|
||||
'currentRatchet.ephemeralKeyPair.pubKey',
|
||||
33
|
||||
);
|
||||
protoSenderChain.senderRatchetKeyPrivate = binaryToUint8Array(
|
||||
session,
|
||||
'currentRatchet.ephemeralKeyPair.privKey',
|
||||
32
|
||||
);
|
||||
|
||||
proto.senderChain = protoSenderChain;
|
||||
|
||||
// First Receiver Chain
|
||||
|
||||
proto.receiverChains = [];
|
||||
|
||||
const firstReceiverChainBaseKey =
|
||||
session.currentRatchet?.lastRemoteEphemeralKey;
|
||||
if (!firstReceiverChainBaseKey) {
|
||||
throw new Error('toProtobufSession: No receiver base key!');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const firstReceiverChain = (session as any)[firstReceiverChainBaseKey] as
|
||||
| ChainType
|
||||
| undefined;
|
||||
|
||||
// If the session was just initialized, then there will be no receiver chain
|
||||
if (firstReceiverChain) {
|
||||
const protoFirstReceiverChain = toProtobufChain(firstReceiverChain);
|
||||
|
||||
if (firstReceiverChain.chainType !== 2) {
|
||||
throw new Error(
|
||||
`toProtobufSession: Expected receiver chain type for firstReceiverChain, got ${firstReceiverChain.chainType}`
|
||||
);
|
||||
}
|
||||
|
||||
protoFirstReceiverChain.senderRatchetKey = binaryToUint8Array(
|
||||
session,
|
||||
'currentRatchet.lastRemoteEphemeralKey',
|
||||
33
|
||||
);
|
||||
|
||||
proto.receiverChains.push(protoFirstReceiverChain);
|
||||
}
|
||||
|
||||
// Old Receiver Chains
|
||||
|
||||
const oldChains = (session.oldRatchetList || [])
|
||||
.slice(0)
|
||||
.sort((left, right) => (right.added || 0) - (left.added || 0));
|
||||
oldChains.forEach(oldRatchet => {
|
||||
const baseKey = oldRatchet.ephemeralKey;
|
||||
if (!baseKey) {
|
||||
throw new Error('toProtobufSession: No base key for old receiver chain!');
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const chain = (session as any)[baseKey] as ChainType | undefined;
|
||||
if (!chain) {
|
||||
throw new Error(
|
||||
'toProtobufSession: No chain for old receiver chain base key!'
|
||||
);
|
||||
}
|
||||
|
||||
if (chain.chainType !== 2) {
|
||||
throw new Error(
|
||||
`toProtobufSession: Expected receiver chain type, got ${chain.chainType}`
|
||||
);
|
||||
}
|
||||
|
||||
const protoChain = toProtobufChain(chain);
|
||||
|
||||
protoChain.senderRatchetKey = binaryToUint8Array(
|
||||
oldRatchet,
|
||||
'ephemeralKey',
|
||||
33
|
||||
);
|
||||
|
||||
proto.receiverChains.push(protoChain);
|
||||
});
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
function toProtobufChain(
|
||||
chain: ChainType
|
||||
): signal.proto.storage.SessionStructure.Chain {
|
||||
const proto = new Chain();
|
||||
|
||||
const protoChainKey = new Chain.ChainKey();
|
||||
protoChainKey.index = getInteger(chain, 'chainKey.counter');
|
||||
if (chain.chainKey?.key !== undefined) {
|
||||
protoChainKey.key = binaryToUint8Array(chain, 'chainKey.key', 32);
|
||||
}
|
||||
proto.chainKey = protoChainKey;
|
||||
|
||||
const messageKeys = Object.entries(chain.messageKeys || {});
|
||||
proto.messageKeys = messageKeys.map(entry => {
|
||||
const protoMessageKey = new SessionStructure.Chain.MessageKey();
|
||||
protoMessageKey.index = getInteger(entry, '0');
|
||||
const key = binaryToUint8Array(entry, '1', 32);
|
||||
|
||||
const { cipherKey, macKey, iv } = translateMessageKey(key);
|
||||
|
||||
protoMessageKey.cipherKey = new Uint8Array(cipherKey);
|
||||
protoMessageKey.macKey = new Uint8Array(macKey);
|
||||
protoMessageKey.iv = new Uint8Array(iv);
|
||||
|
||||
return protoMessageKey;
|
||||
});
|
||||
|
||||
return proto;
|
||||
}
|
||||
|
||||
// Utility functions
|
||||
|
||||
const WHISPER_MESSAGE_KEYS = 'WhisperMessageKeys';
|
||||
|
||||
function deriveSecrets(
|
||||
input: ArrayBuffer,
|
||||
salt: ArrayBuffer,
|
||||
info: ArrayBuffer
|
||||
): Array<ArrayBuffer> {
|
||||
const hkdf = HKDF.new(3);
|
||||
const output = hkdf.deriveSecrets(
|
||||
3 * 32,
|
||||
Buffer.from(input),
|
||||
Buffer.from(info),
|
||||
Buffer.from(salt)
|
||||
);
|
||||
return [
|
||||
typedArrayToArrayBuffer(output.slice(0, 32)),
|
||||
typedArrayToArrayBuffer(output.slice(32, 64)),
|
||||
typedArrayToArrayBuffer(output.slice(64, 96)),
|
||||
];
|
||||
}
|
||||
|
||||
function translateMessageKey(key: Uint8Array) {
|
||||
const input = key.buffer;
|
||||
const salt = new ArrayBuffer(32);
|
||||
const info = bytesFromString(WHISPER_MESSAGE_KEYS);
|
||||
|
||||
const [cipherKey, macKey, ivContainer] = deriveSecrets(input, salt, info);
|
||||
|
||||
return {
|
||||
cipherKey,
|
||||
macKey,
|
||||
iv: ivContainer.slice(0, 16),
|
||||
};
|
||||
}
|
||||
|
||||
function binaryToUint8Array(
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
object: any,
|
||||
path: string,
|
||||
length: number
|
||||
): Uint8Array {
|
||||
const target = get(object, path);
|
||||
if (target === null || target === undefined) {
|
||||
throw new Error(`binaryToUint8Array: Falsey path ${path}`);
|
||||
}
|
||||
|
||||
if (!isString(target)) {
|
||||
throw new Error(`binaryToUint8Array: String not found at path ${path}`);
|
||||
}
|
||||
|
||||
const buffer = fromEncodedBinaryToArrayBuffer(target);
|
||||
if (length && buffer.byteLength !== length) {
|
||||
throw new Error(
|
||||
`binaryToUint8Array: Got unexpected length ${buffer.byteLength} instead of ${length} at path ${path}`
|
||||
);
|
||||
}
|
||||
|
||||
return new Uint8Array(buffer);
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
function getInteger(object: any, path: string): number {
|
||||
const target = get(object, path);
|
||||
if (target === null || target === undefined) {
|
||||
throw new Error(`getInteger: Falsey path ${path}`);
|
||||
}
|
||||
|
||||
if (isString(target)) {
|
||||
const result = parseInt(target, 10);
|
||||
if (!isFinite(result)) {
|
||||
throw new Error(
|
||||
`getInteger: Value could not be parsed as number at ${path}: {target}`
|
||||
);
|
||||
}
|
||||
|
||||
if (!isInteger(result)) {
|
||||
throw new Error(
|
||||
`getInteger: Parsed value not an integer at ${path}: {target}`
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!isInteger(target)) {
|
||||
throw new Error(`getInteger: Value not an integer at ${path}: {target}`);
|
||||
}
|
||||
|
||||
return target;
|
||||
}
|
Loading…
Add table
Reference in a new issue