Files
libsignal/node/ts/test/SealedSenderServerAPI.ts
2025-09-29 18:23:38 -07:00

262 lines
7.3 KiB
TypeScript

//
// Copyright 2023 Signal Messenger, LLC.
// SPDX-License-Identifier: AGPL-3.0-only
//
import { assert } from 'chai';
import { Buffer } from 'node:buffer';
import {
Recipient,
default as SealedSenderMultiRecipientMessage,
} from '../SealedSenderMultiRecipientMessage.js';
import * as util from './util.js';
import { assertArrayEquals } from './util.js';
util.initLogger();
function bufferFromHexStrings(...input: string[]): Buffer {
return Buffer.concat(input.map((s) => Buffer.from(s, 'hex')));
}
const VERSION_ACI_ONLY = '22';
const VERSION_SERVICE_ID_AWARE = '23';
const VERSION_RECIPIENT_MESSAGE = '22';
const ACI_MARKER = '00';
const PNI_MARKER = '01';
const ALICE_UUID = '9d0652a3-dcc3-4d11-975f-74d61598733f';
const ALICE_UUID_BYTES = '9d0652a3dcc34d11975f74d61598733f';
const ALICE_KEY_MATERIAL =
'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa';
const BOB_UUID = 'e80f7bbe-5b94-471e-bd8c-2173654ea3d1';
const BOB_UUID_BYTES = 'e80f7bbe5b94471ebd8c2173654ea3d1';
const BOB_KEY_MATERIAL =
'bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb';
const EVE_UUID = '3f0f4734-e331-4434-bd4f-6d8f6ea6dcc7';
const EVE_UUID_BYTES = '3f0f4734e3314434bd4f6d8f6ea6dcc7';
const MALLORY_UUID = '5d088142-6fd7-4dbd-af00-fdda1b3ce988';
const MALLORY_UUID_BYTES = '5d0881426fd74dbdaf00fdda1b3ce988';
const SHARED_BYTES =
'99999999999999999999999999999999999999999999999999999999999999999999';
function assertMessageForRecipient(
message: SealedSenderMultiRecipientMessage,
recipient: Recipient,
...expectedHexParts: string[]
): void {
const expected = bufferFromHexStrings(...expectedHexParts);
assertArrayEquals(expected, message.messageForRecipient(recipient));
}
describe('SealedSenderMultiRecipientMessage', () => {
it('can parse simple ACI-only messages', () => {
const input = bufferFromHexStrings(
VERSION_ACI_ONLY,
// Count
'03',
// Recipient 1: UUID, Device ID and Registration ID, Key Material
ALICE_UUID_BYTES,
'0111aa',
ALICE_KEY_MATERIAL,
// Recipient 2
BOB_UUID_BYTES,
'0111bb',
BOB_KEY_MATERIAL,
// Recipient 3 (note that it's another device of Bob's)
BOB_UUID_BYTES,
'0333bb',
BOB_KEY_MATERIAL,
// Shared data
SHARED_BYTES
);
const message = new SealedSenderMultiRecipientMessage(input);
assert.deepEqual(
Object.getOwnPropertyNames(message.recipientsByServiceIdString()),
[ALICE_UUID, BOB_UUID]
);
const alice = message.recipientsByServiceIdString()[ALICE_UUID];
assert.isNotNull(alice);
assert.deepEqual(alice.deviceIds, [0x01]);
assert.deepEqual(alice.registrationIds, [0x11aa]);
assertMessageForRecipient(
message,
alice,
VERSION_RECIPIENT_MESSAGE,
ALICE_KEY_MATERIAL,
SHARED_BYTES
);
const bob = message.recipientsByServiceIdString()[BOB_UUID];
assert.isNotNull(bob);
assert.deepEqual(bob.deviceIds, [0x01, 0x03]);
assert.deepEqual(bob.registrationIds, [0x11bb, 0x33bb]);
assertMessageForRecipient(
message,
bob,
VERSION_RECIPIENT_MESSAGE,
BOB_KEY_MATERIAL,
SHARED_BYTES
);
});
it('can parse ServiceId-based messages with compact device lists', () => {
const input = bufferFromHexStrings(
VERSION_SERVICE_ID_AWARE,
// Count
'02',
// Recipient 1: ServiceId, Device ID and Registration ID, Key Material
ACI_MARKER,
ALICE_UUID_BYTES,
'0111aa',
ALICE_KEY_MATERIAL,
// Recipient 2
PNI_MARKER,
BOB_UUID_BYTES,
'0191bb', // high bit in registration ID flags another device
'0333bb',
BOB_KEY_MATERIAL,
// Shared data
SHARED_BYTES
);
const message = new SealedSenderMultiRecipientMessage(input);
assert.deepEqual(
Object.getOwnPropertyNames(message.recipientsByServiceIdString()),
[ALICE_UUID, `PNI:${BOB_UUID}`]
);
const alice = message.recipientsByServiceIdString()[ALICE_UUID];
assert.isNotNull(alice);
assert.deepEqual(alice.deviceIds, [0x01]);
assert.deepEqual(alice.registrationIds, [0x11aa]);
assertMessageForRecipient(
message,
alice,
VERSION_RECIPIENT_MESSAGE,
ALICE_KEY_MATERIAL,
SHARED_BYTES
);
const bob = message.recipientsByServiceIdString()[`PNI:${BOB_UUID}`];
assert.isNotNull(bob);
assert.deepEqual(bob.deviceIds, [0x01, 0x03]);
assert.deepEqual(bob.registrationIds, [0x11bb, 0x33bb]);
assertMessageForRecipient(
message,
bob,
VERSION_RECIPIENT_MESSAGE,
BOB_KEY_MATERIAL,
SHARED_BYTES
);
});
it('can handle excluded recipients', () => {
const input = bufferFromHexStrings(
VERSION_SERVICE_ID_AWARE,
// Count
'04',
// Recipient 1: ServiceId, Device ID and Registration ID, Key Material
ACI_MARKER,
ALICE_UUID_BYTES,
'0191aa', // high bit in registration ID flags another device
'0333aa',
ALICE_KEY_MATERIAL,
// Recipient 2: excluded by device ID 0
ACI_MARKER,
EVE_UUID_BYTES,
'00',
// Recipient 3
PNI_MARKER,
BOB_UUID_BYTES,
'0111bb',
BOB_KEY_MATERIAL,
// Recipient 4 (also excluded)
ACI_MARKER,
MALLORY_UUID_BYTES,
'00',
// Shared data
SHARED_BYTES
);
const message = new SealedSenderMultiRecipientMessage(input);
assert.deepEqual(
Object.getOwnPropertyNames(message.recipientsByServiceIdString()),
[ALICE_UUID, `PNI:${BOB_UUID}`]
);
const alice = message.recipientsByServiceIdString()[ALICE_UUID];
assert.isNotNull(alice);
assert.deepEqual(alice.deviceIds, [0x01, 0x03]);
assert.deepEqual(alice.registrationIds, [0x11aa, 0x33aa]);
assertMessageForRecipient(
message,
alice,
VERSION_RECIPIENT_MESSAGE,
ALICE_KEY_MATERIAL,
SHARED_BYTES
);
const bob = message.recipientsByServiceIdString()[`PNI:${BOB_UUID}`];
assert.isNotNull(bob);
assert.deepEqual(bob.deviceIds, [0x01]);
assert.deepEqual(bob.registrationIds, [0x11bb]);
assertMessageForRecipient(
message,
bob,
VERSION_RECIPIENT_MESSAGE,
BOB_KEY_MATERIAL,
SHARED_BYTES
);
assert.deepEqual(message.excludedRecipientServiceIdStrings(), [
EVE_UUID,
MALLORY_UUID,
]);
});
it('rejects repeated excluded recipients', () => {
const input = bufferFromHexStrings(
VERSION_SERVICE_ID_AWARE,
// Count
'03',
// Recipient 1: ServiceId, Device ID and Registration ID, Key Material
ACI_MARKER,
ALICE_UUID_BYTES,
'0191aa', // high bit in registration ID flags another device
'0333aa',
ALICE_KEY_MATERIAL,
// Recipient 2: excluded by device ID 0
ACI_MARKER,
EVE_UUID_BYTES,
'00',
// Recipient 3 (same as #2)
ACI_MARKER,
EVE_UUID_BYTES,
'00',
// Shared data
SHARED_BYTES
);
assert.throws(() => new SealedSenderMultiRecipientMessage(input));
});
it('rejects unknown versions', () => {
assert.throws(
() => new SealedSenderMultiRecipientMessage(Uint8Array.of(0x11))
);
assert.throws(
() => new SealedSenderMultiRecipientMessage(Uint8Array.of(0x2f))
);
assert.throws(
() => new SealedSenderMultiRecipientMessage(Uint8Array.of(0x77))
);
});
});