signal-desktop/ts/test-electron/SignalProtocolStore_test.ts

1770 lines
56 KiB
TypeScript
Raw Normal View History

2022-01-28 00:28:41 +00:00
// Copyright 2015-2022 Signal Messenger, LLC
2020-10-30 20:34:04 +00:00
// SPDX-License-Identifier: AGPL-3.0-only
/* eslint-disable @typescript-eslint/no-explicit-any */
import chai, { assert } from 'chai';
import chaiAsPromised from 'chai-as-promised';
import {
Direction,
SenderKeyRecord,
SessionRecord,
} from '@signalapp/libsignal-client';
import { signal } from '../protobuf/compiled';
2021-09-24 00:49:05 +00:00
import { sessionStructureToBytes } from '../util/sessionTranslation';
2021-12-14 01:25:44 +00:00
import * as durations from '../util/durations';
2021-05-19 21:25:56 +00:00
import { Zone } from '../util/Zone';
2021-09-24 00:49:05 +00:00
import * as Bytes from '../Bytes';
import { getRandomBytes, constantTimeEqual } from '../Crypto';
import { clampPrivateKey, setPublicKeyTypeByte } from '../Curve';
import type { SignalProtocolStore } from '../SignalProtocolStore';
import { GLOBAL_ZONE } from '../SignalProtocolStore';
import { Address } from '../types/Address';
import { QualifiedAddress } from '../types/QualifiedAddress';
import { UUID } from '../types/UUID';
import type { IdentityKeyType, KeyPairType } from '../textsecure/Types.d';
chai.use(chaiAsPromised);
const {
RecordStructure,
SessionStructure,
SenderKeyRecordStructure,
SenderKeyStateStructure,
} = signal.proto.storage;
2018-11-02 18:02:53 +00:00
describe('SignalProtocolStore', () => {
2021-10-26 22:59:08 +00:00
const ourUuid = UUID.generate();
const theirUuid = UUID.generate();
let store: SignalProtocolStore;
let identityKey: KeyPairType;
let testKey: KeyPairType;
function getSessionRecord(isOpen?: boolean): SessionRecord {
const proto = new RecordStructure();
proto.previousSessions = [];
if (isOpen) {
proto.currentSession = new SessionStructure();
2021-09-24 00:49:05 +00:00
proto.currentSession.aliceBaseKey = getPublicKey();
proto.currentSession.localIdentityPublic = getPublicKey();
proto.currentSession.localRegistrationId = 435;
proto.currentSession.previousCounter = 1;
2021-09-24 00:49:05 +00:00
proto.currentSession.remoteIdentityPublic = getPublicKey();
proto.currentSession.remoteRegistrationId = 243;
2021-09-24 00:49:05 +00:00
proto.currentSession.rootKey = getPrivateKey();
proto.currentSession.sessionVersion = 3;
}
return SessionRecord.deserialize(
2021-09-24 00:49:05 +00:00
Buffer.from(sessionStructureToBytes(proto))
);
}
function getSenderKeyRecord(): SenderKeyRecord {
const proto = new SenderKeyRecordStructure();
const state = new SenderKeyStateStructure();
state.senderKeyId = 4;
const senderChainKey = new SenderKeyStateStructure.SenderChainKey();
senderChainKey.iteration = 10;
2021-09-24 00:49:05 +00:00
senderChainKey.seed = getPublicKey();
state.senderChainKey = senderChainKey;
const senderSigningKey = new SenderKeyStateStructure.SenderSigningKey();
2021-09-24 00:49:05 +00:00
senderSigningKey.public = getPublicKey();
senderSigningKey.private = getPrivateKey();
state.senderSigningKey = senderSigningKey;
state.senderMessageKeys = [];
const messageKey = new SenderKeyStateStructure.SenderMessageKey();
messageKey.iteration = 234;
2021-09-24 00:49:05 +00:00
messageKey.seed = getPublicKey();
state.senderMessageKeys.push(messageKey);
proto.senderKeyStates = [];
proto.senderKeyStates.push(state);
return SenderKeyRecord.deserialize(
Buffer.from(
signal.proto.storage.SenderKeyRecordStructure.encode(proto).finish()
)
);
}
function getPrivateKey() {
const key = getRandomBytes(32);
clampPrivateKey(key);
return key;
}
function getPublicKey() {
const key = getRandomBytes(33);
setPublicKeyTypeByte(key);
return key;
}
before(async () => {
store = window.textsecure.storage.protocol;
store.hydrateCaches();
identityKey = {
pubKey: getPublicKey(),
privKey: getPrivateKey(),
};
testKey = {
pubKey: getPublicKey(),
privKey: getPrivateKey(),
};
setPublicKeyTypeByte(identityKey.pubKey);
setPublicKeyTypeByte(testKey.pubKey);
clampPrivateKey(identityKey.privKey);
clampPrivateKey(testKey.privKey);
window.storage.put('registrationIdMap', { [ourUuid.toString()]: 1337 });
window.storage.put('identityKeyMap', {
[ourUuid.toString()]: {
2021-09-24 00:49:05 +00:00
privKey: Bytes.toBase64(identityKey.privKey),
pubKey: Bytes.toBase64(identityKey.pubKey),
},
});
await window.storage.fetch();
window.ConversationController.reset();
await window.ConversationController.load();
await window.ConversationController.getOrCreateAndWait(
theirUuid.toString(),
'private'
);
});
2018-11-02 18:02:53 +00:00
describe('getLocalRegistrationId', () => {
it('retrieves my registration id', async () => {
2019-09-26 19:56:31 +00:00
await store.hydrateCaches();
const id = await store.getLocalRegistrationId(ourUuid);
2018-10-18 01:01:21 +00:00
assert.strictEqual(id, 1337);
});
});
2018-11-02 18:02:53 +00:00
describe('getIdentityKeyPair', () => {
it('retrieves my identity key', async () => {
2019-09-26 19:56:31 +00:00
await store.hydrateCaches();
const key = await store.getIdentityKeyPair(ourUuid);
if (!key) {
throw new Error('Missing key!');
}
assert.isTrue(constantTimeEqual(key.pubKey, identityKey.pubKey));
assert.isTrue(constantTimeEqual(key.privKey, identityKey.privKey));
});
});
2017-06-13 20:57:46 +00:00
describe('senderKeys', () => {
it('roundtrips in memory', async () => {
2021-10-26 22:59:08 +00:00
const distributionId = UUID.generate().toString();
const expected = getSenderKeyRecord();
const deviceId = 1;
const qualifiedAddress = new QualifiedAddress(
ourUuid,
new Address(theirUuid, deviceId)
);
await store.saveSenderKey(qualifiedAddress, distributionId, expected);
const actual = await store.getSenderKey(qualifiedAddress, distributionId);
if (!actual) {
throw new Error('getSenderKey returned nothing!');
}
assert.isTrue(
2021-09-24 00:49:05 +00:00
constantTimeEqual(expected.serialize(), actual.serialize())
);
2021-05-25 22:40:04 +00:00
await store.removeSenderKey(qualifiedAddress, distributionId);
2021-05-25 22:40:04 +00:00
const postDeleteGet = await store.getSenderKey(
qualifiedAddress,
2021-05-25 22:40:04 +00:00
distributionId
);
assert.isUndefined(postDeleteGet);
});
it('roundtrips through database', async () => {
2021-10-26 22:59:08 +00:00
const distributionId = UUID.generate().toString();
const expected = getSenderKeyRecord();
const deviceId = 1;
const qualifiedAddress = new QualifiedAddress(
ourUuid,
new Address(theirUuid, deviceId)
);
await store.saveSenderKey(qualifiedAddress, distributionId, expected);
// Re-fetch from the database to ensure we get the latest database value
await store.hydrateCaches();
const actual = await store.getSenderKey(qualifiedAddress, distributionId);
if (!actual) {
throw new Error('getSenderKey returned nothing!');
}
assert.isTrue(
2021-09-24 00:49:05 +00:00
constantTimeEqual(expected.serialize(), actual.serialize())
);
2021-05-25 22:40:04 +00:00
await store.removeSenderKey(qualifiedAddress, distributionId);
2021-05-25 22:40:04 +00:00
// Re-fetch from the database to ensure we get the latest database value
await store.hydrateCaches();
const postDeleteGet = await store.getSenderKey(
qualifiedAddress,
2021-05-25 22:40:04 +00:00
distributionId
);
assert.isUndefined(postDeleteGet);
});
});
2018-11-02 18:02:53 +00:00
describe('saveIdentity', () => {
const identifier = new Address(theirUuid, 1);
2018-10-18 01:01:21 +00:00
2018-11-02 18:02:53 +00:00
it('stores identity keys', async () => {
2018-10-18 01:01:21 +00:00
await store.saveIdentity(identifier, testKey.pubKey);
const key = await store.loadIdentityKey(theirUuid);
if (!key) {
throw new Error('Missing key!');
}
2018-10-18 01:01:21 +00:00
assert.isTrue(constantTimeEqual(key, testKey.pubKey));
2015-07-22 19:48:08 +00:00
});
2018-11-02 18:02:53 +00:00
it('allows key changes', async () => {
const newIdentity = getPublicKey();
2018-10-18 01:01:21 +00:00
await store.saveIdentity(identifier, testKey.pubKey);
await store.saveIdentity(identifier, newIdentity);
2016-05-04 07:09:44 +00:00
});
2018-11-02 18:02:53 +00:00
describe('When there is no existing key (first use)', () => {
before(async () => {
await store.removeIdentityKey(theirUuid);
2018-10-18 01:01:21 +00:00
await store.saveIdentity(identifier, testKey.pubKey);
});
2018-11-02 18:02:53 +00:00
it('marks the key firstUse', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert(identity.firstUse);
});
2018-11-02 18:02:53 +00:00
it('sets the timestamp', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert(identity.timestamp);
});
2018-11-02 18:02:53 +00:00
it('sets the verified status to DEFAULT', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT);
2017-06-13 20:57:46 +00:00
});
});
2018-11-02 18:02:53 +00:00
describe('When there is a different existing key (non first use)', () => {
const newIdentity = getPublicKey();
2018-10-18 01:01:21 +00:00
const oldTimestamp = Date.now();
2018-11-02 18:02:53 +00:00
before(async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-10-18 01:01:21 +00:00
publicKey: testKey.pubKey,
firstUse: true,
timestamp: oldTimestamp,
nonblockingApproval: false,
verified: store.VerifiedStatus.DEFAULT,
});
await store.hydrateCaches();
2018-10-18 01:01:21 +00:00
await store.saveIdentity(identifier, newIdentity);
});
2018-11-02 18:02:53 +00:00
it('marks the key not firstUse', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert(!identity.firstUse);
});
2018-11-02 18:02:53 +00:00
it('updates the timestamp', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.notEqual(identity.timestamp, oldTimestamp);
});
2018-11-02 18:02:53 +00:00
describe('The previous verified status was DEFAULT', () => {
before(async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
firstUse: true,
timestamp: oldTimestamp,
nonblockingApproval: false,
verified: store.VerifiedStatus.DEFAULT,
});
await store.hydrateCaches();
2017-06-13 20:57:46 +00:00
2018-10-18 01:01:21 +00:00
await store.saveIdentity(identifier, newIdentity);
2017-06-13 20:57:46 +00:00
});
2018-11-02 18:02:53 +00:00
it('sets the new key to default', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT);
2017-06-13 20:57:46 +00:00
});
});
2018-11-02 18:02:53 +00:00
describe('The previous verified status was VERIFIED', () => {
before(async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-10-18 01:01:21 +00:00
publicKey: testKey.pubKey,
firstUse: true,
timestamp: oldTimestamp,
nonblockingApproval: false,
verified: store.VerifiedStatus.VERIFIED,
});
await store.hydrateCaches();
2018-10-18 01:01:21 +00:00
await store.saveIdentity(identifier, newIdentity);
2017-06-13 20:57:46 +00:00
});
2018-11-02 18:02:53 +00:00
it('sets the new key to unverified', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-04-27 21:25:04 +00:00
assert.strictEqual(
2018-10-18 01:01:21 +00:00
identity.verified,
2018-04-27 21:25:04 +00:00
store.VerifiedStatus.UNVERIFIED
);
2017-06-13 20:57:46 +00:00
});
});
2018-11-02 18:02:53 +00:00
describe('The previous verified status was UNVERIFIED', () => {
before(async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-10-18 01:01:21 +00:00
publicKey: testKey.pubKey,
firstUse: true,
timestamp: oldTimestamp,
nonblockingApproval: false,
verified: store.VerifiedStatus.UNVERIFIED,
});
await store.hydrateCaches();
2018-10-18 01:01:21 +00:00
await store.saveIdentity(identifier, newIdentity);
2017-06-13 20:57:46 +00:00
});
2018-11-02 18:02:53 +00:00
it('sets the new key to unverified', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-04-27 21:25:04 +00:00
assert.strictEqual(
2018-10-18 01:01:21 +00:00
identity.verified,
2018-04-27 21:25:04 +00:00
store.VerifiedStatus.UNVERIFIED
);
2017-06-13 20:57:46 +00:00
});
});
});
2018-11-02 18:02:53 +00:00
describe('When the key has not changed', () => {
const oldTimestamp = Date.now();
before(async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-10-18 01:01:21 +00:00
publicKey: testKey.pubKey,
timestamp: oldTimestamp,
nonblockingApproval: false,
firstUse: false,
2018-10-18 01:01:21 +00:00
verified: store.VerifiedStatus.DEFAULT,
});
await store.hydrateCaches();
});
2018-11-02 18:02:53 +00:00
describe('If it is marked firstUse', () => {
before(async () => {
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
identity.firstUse = true;
await window.Signal.Data.createOrUpdateIdentityKey(identity);
await store.hydrateCaches();
});
2018-11-02 18:02:53 +00:00
it('nothing changes', async () => {
2018-10-18 01:01:21 +00:00
await store.saveIdentity(identifier, testKey.pubKey, true);
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert(!identity.nonblockingApproval);
assert.strictEqual(identity.timestamp, oldTimestamp);
});
});
2018-11-02 18:02:53 +00:00
describe('If it is not marked firstUse', () => {
before(async () => {
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
identity.firstUse = false;
await window.Signal.Data.createOrUpdateIdentityKey(identity);
await store.hydrateCaches();
});
2018-11-02 18:02:53 +00:00
describe('If nonblocking approval is required', () => {
let now: number;
2018-11-02 18:02:53 +00:00
before(async () => {
now = Date.now();
2018-10-18 01:01:21 +00:00
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
2018-10-18 01:01:21 +00:00
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
identity.timestamp = now;
await window.Signal.Data.createOrUpdateIdentityKey(identity);
await store.hydrateCaches();
});
2018-11-02 18:02:53 +00:00
it('sets non-blocking approval', async () => {
2018-10-18 01:01:21 +00:00
await store.saveIdentity(identifier, testKey.pubKey, true);
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
2018-10-18 01:01:21 +00:00
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(identity.nonblockingApproval, true);
assert.strictEqual(identity.timestamp, now);
assert.strictEqual(identity.firstUse, false);
});
});
});
});
});
2018-11-02 18:02:53 +00:00
describe('saveIdentityWithAttributes', () => {
let now: number;
let validAttributes: IdentityKeyType;
2018-11-02 18:02:53 +00:00
before(async () => {
now = Date.now();
validAttributes = {
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
firstUse: true,
timestamp: now,
verified: store.VerifiedStatus.VERIFIED,
nonblockingApproval: false,
};
await store.removeIdentityKey(theirUuid);
2017-06-13 20:57:46 +00:00
});
2018-11-02 18:02:53 +00:00
describe('with valid attributes', () => {
before(async () => {
await store.saveIdentityWithAttributes(theirUuid, validAttributes);
2017-06-13 20:57:46 +00:00
});
2018-11-02 18:02:53 +00:00
it('publicKey is saved', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
assert.isTrue(constantTimeEqual(identity.publicKey, testKey.pubKey));
2017-06-13 20:57:46 +00:00
});
2018-11-02 18:02:53 +00:00
it('firstUse is saved', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(identity.firstUse, true);
2017-06-13 20:57:46 +00:00
});
2018-11-02 18:02:53 +00:00
it('timestamp is saved', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(identity.timestamp, now);
2017-06-13 20:57:46 +00:00
});
2018-11-02 18:02:53 +00:00
it('verified is saved', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(identity.verified, store.VerifiedStatus.VERIFIED);
2017-06-13 20:57:46 +00:00
});
2018-11-02 18:02:53 +00:00
it('nonblockingApproval is saved', async () => {
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(identity.nonblockingApproval, false);
2017-06-13 20:57:46 +00:00
});
});
2018-11-02 18:02:53 +00:00
describe('with invalid attributes', () => {
let attributes: IdentityKeyType;
2018-11-02 18:02:53 +00:00
beforeEach(() => {
attributes = window._.clone(validAttributes);
2017-06-13 20:57:46 +00:00
});
2018-10-18 01:01:21 +00:00
async function testInvalidAttributes() {
try {
await store.saveIdentityWithAttributes(theirUuid, attributes);
2018-10-18 01:01:21 +00:00
throw new Error('saveIdentityWithAttributes should have failed');
} catch (error) {
// good. we expect to fail with invalid attributes.
}
2017-06-13 20:57:46 +00:00
}
2018-11-02 18:02:53 +00:00
it('rejects an invalid publicKey', async () => {
attributes.publicKey = 'a string' as any;
2018-10-18 01:01:21 +00:00
await testInvalidAttributes();
2017-06-13 20:57:46 +00:00
});
2018-11-02 18:02:53 +00:00
it('rejects invalid firstUse', async () => {
attributes.firstUse = 0 as any;
2018-10-18 01:01:21 +00:00
await testInvalidAttributes();
2017-06-13 20:57:46 +00:00
});
2018-11-02 18:02:53 +00:00
it('rejects invalid timestamp', async () => {
attributes.timestamp = NaN as any;
2018-10-18 01:01:21 +00:00
await testInvalidAttributes();
2017-06-13 20:57:46 +00:00
});
2018-11-02 18:02:53 +00:00
it('rejects invalid verified', async () => {
attributes.verified = null as any;
2018-10-18 01:01:21 +00:00
await testInvalidAttributes();
2017-06-13 20:57:46 +00:00
});
2018-11-02 18:02:53 +00:00
it('rejects invalid nonblockingApproval', async () => {
attributes.nonblockingApproval = 0 as any;
2018-10-18 01:01:21 +00:00
await testInvalidAttributes();
2017-06-13 20:57:46 +00:00
});
});
});
2018-11-02 18:02:53 +00:00
describe('setApproval', () => {
it('sets nonblockingApproval', async () => {
await store.setApproval(theirUuid, true);
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(identity.nonblockingApproval, true);
2017-06-13 20:57:46 +00:00
});
});
2018-11-02 18:02:53 +00:00
describe('setVerified', () => {
2018-10-18 01:01:21 +00:00
async function saveRecordDefault() {
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
firstUse: true,
timestamp: Date.now(),
verified: store.VerifiedStatus.DEFAULT,
nonblockingApproval: false,
});
await store.hydrateCaches();
2017-06-13 20:57:46 +00:00
}
2018-11-02 18:02:53 +00:00
describe('with no public key argument', () => {
before(saveRecordDefault);
2018-11-02 18:02:53 +00:00
it('updates the verified status', async () => {
await store.setVerified(theirUuid, store.VerifiedStatus.VERIFIED);
2018-10-18 01:01:21 +00:00
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(identity.verified, store.VerifiedStatus.VERIFIED);
assert.isTrue(constantTimeEqual(identity.publicKey, testKey.pubKey));
});
});
2018-11-02 18:02:53 +00:00
describe('with the current public key', () => {
before(saveRecordDefault);
2018-11-02 18:02:53 +00:00
it('updates the verified status', async () => {
2018-10-18 01:01:21 +00:00
await store.setVerified(
theirUuid,
2018-10-18 01:01:21 +00:00
store.VerifiedStatus.VERIFIED,
testKey.pubKey
);
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(identity.verified, store.VerifiedStatus.VERIFIED);
assert.isTrue(constantTimeEqual(identity.publicKey, testKey.pubKey));
});
});
2018-11-02 18:02:53 +00:00
describe('with a mismatching public key', () => {
const newIdentity = getPublicKey();
before(saveRecordDefault);
2018-11-02 18:02:53 +00:00
it('does not change the record.', async () => {
2018-10-18 01:01:21 +00:00
await store.setVerified(
theirUuid,
2018-10-18 01:01:21 +00:00
store.VerifiedStatus.VERIFIED,
newIdentity
);
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT);
assert.isTrue(constantTimeEqual(identity.publicKey, testKey.pubKey));
});
2017-06-13 20:57:46 +00:00
});
});
describe('processVerifiedMessage', () => {
const newIdentity = getPublicKey();
let keychangeTriggered: number;
2018-11-02 18:02:53 +00:00
beforeEach(() => {
keychangeTriggered = 0;
2018-11-02 18:02:53 +00:00
store.bind('keychange', () => {
keychangeTriggered += 1;
});
});
2018-11-02 18:02:53 +00:00
afterEach(() => {
store.unbind('keychange');
});
2018-11-02 18:02:53 +00:00
describe('when the new verified status is DEFAULT', () => {
describe('when there is no existing record', () => {
before(async () => {
await window.Signal.Data.removeIdentityKeyById(theirUuid.toString());
await store.hydrateCaches();
});
2017-06-17 03:56:06 +00:00
it('sets the identity key', async () => {
await store.processVerifiedMessage(
theirUuid,
2018-10-18 01:01:21 +00:00
store.VerifiedStatus.DEFAULT,
newIdentity
);
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
assert.isTrue(
identity?.publicKey &&
constantTimeEqual(identity.publicKey, newIdentity)
);
2018-10-18 01:01:21 +00:00
assert.strictEqual(keychangeTriggered, 0);
2017-06-17 03:56:06 +00:00
});
});
2018-11-02 18:02:53 +00:00
describe('when the record exists', () => {
describe('when the existing key is different', () => {
before(async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
firstUse: true,
timestamp: Date.now(),
verified: store.VerifiedStatus.VERIFIED,
nonblockingApproval: false,
});
await store.hydrateCaches();
});
it('updates the identity', async () => {
await store.processVerifiedMessage(
theirUuid,
2018-10-18 01:01:21 +00:00
store.VerifiedStatus.DEFAULT,
newIdentity
);
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
2018-10-18 01:01:21 +00:00
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT);
assert.isTrue(constantTimeEqual(identity.publicKey, newIdentity));
assert.strictEqual(keychangeTriggered, 1);
});
});
2018-11-02 18:02:53 +00:00
describe('when the existing key is the same but VERIFIED', () => {
before(async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
firstUse: true,
timestamp: Date.now(),
verified: store.VerifiedStatus.VERIFIED,
nonblockingApproval: false,
});
await store.hydrateCaches();
2017-06-17 03:56:06 +00:00
});
2018-11-02 18:02:53 +00:00
it('updates the verified status', async () => {
await store.processVerifiedMessage(
theirUuid,
2018-10-18 01:01:21 +00:00
store.VerifiedStatus.DEFAULT,
testKey.pubKey
);
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
2018-10-18 01:01:21 +00:00
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(identity.verified, store.VerifiedStatus.DEFAULT);
assert.isTrue(
constantTimeEqual(identity.publicKey, testKey.pubKey)
);
2018-10-18 01:01:21 +00:00
assert.strictEqual(keychangeTriggered, 0);
2017-06-17 03:56:06 +00:00
});
});
2018-11-02 18:02:53 +00:00
describe('when the existing key is the same and already DEFAULT', () => {
before(async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
firstUse: true,
timestamp: Date.now(),
verified: store.VerifiedStatus.DEFAULT,
nonblockingApproval: false,
});
await store.hydrateCaches();
});
2018-11-02 18:02:53 +00:00
it('does not hang', async () => {
await store.processVerifiedMessage(
theirUuid,
2018-10-18 01:01:21 +00:00
store.VerifiedStatus.DEFAULT,
testKey.pubKey
);
assert.strictEqual(keychangeTriggered, 0);
});
});
});
});
2018-11-02 18:02:53 +00:00
describe('when the new verified status is UNVERIFIED', () => {
describe('when there is no existing record', () => {
before(async () => {
await window.Signal.Data.removeIdentityKeyById(theirUuid.toString());
await store.hydrateCaches();
});
it('saves the new identity and marks it UNVERIFIED', async () => {
await store.processVerifiedMessage(
theirUuid,
2018-10-18 01:01:21 +00:00
store.VerifiedStatus.UNVERIFIED,
newIdentity
);
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(
identity.verified,
store.VerifiedStatus.UNVERIFIED
);
assert.isTrue(constantTimeEqual(identity.publicKey, newIdentity));
2018-10-18 01:01:21 +00:00
assert.strictEqual(keychangeTriggered, 0);
2017-06-17 03:56:06 +00:00
});
});
2018-11-02 18:02:53 +00:00
describe('when the record exists', () => {
describe('when the existing key is different', () => {
before(async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
firstUse: true,
timestamp: Date.now(),
verified: store.VerifiedStatus.VERIFIED,
nonblockingApproval: false,
});
await store.hydrateCaches();
});
2018-11-02 18:02:53 +00:00
it('saves the new identity and marks it UNVERIFIED', async () => {
await store.processVerifiedMessage(
theirUuid,
2018-10-18 01:01:21 +00:00
store.VerifiedStatus.UNVERIFIED,
newIdentity
);
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
2018-10-18 01:01:21 +00:00
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(
identity.verified,
store.VerifiedStatus.UNVERIFIED
);
assert.isTrue(constantTimeEqual(identity.publicKey, newIdentity));
2018-10-18 01:01:21 +00:00
assert.strictEqual(keychangeTriggered, 1);
});
});
2018-11-02 18:02:53 +00:00
describe('when the key exists and is DEFAULT', () => {
before(async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
firstUse: true,
timestamp: Date.now(),
verified: store.VerifiedStatus.DEFAULT,
nonblockingApproval: false,
});
await store.hydrateCaches();
});
2018-11-02 18:02:53 +00:00
it('updates the verified status', async () => {
await store.processVerifiedMessage(
theirUuid,
2018-10-18 01:01:21 +00:00
store.VerifiedStatus.UNVERIFIED,
testKey.pubKey
);
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
2018-10-18 01:01:21 +00:00
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(
identity.verified,
store.VerifiedStatus.UNVERIFIED
);
assert.isTrue(
constantTimeEqual(identity.publicKey, testKey.pubKey)
);
2018-10-18 01:01:21 +00:00
assert.strictEqual(keychangeTriggered, 0);
});
});
2018-11-02 18:02:53 +00:00
describe('when the key exists and is already UNVERIFIED', () => {
before(async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
firstUse: true,
timestamp: Date.now(),
verified: store.VerifiedStatus.UNVERIFIED,
nonblockingApproval: false,
});
await store.hydrateCaches();
});
2018-11-02 18:02:53 +00:00
it('does not hang', async () => {
await store.processVerifiedMessage(
theirUuid,
2018-10-18 01:01:21 +00:00
store.VerifiedStatus.UNVERIFIED,
testKey.pubKey
);
assert.strictEqual(keychangeTriggered, 0);
});
});
});
2017-06-17 03:56:06 +00:00
});
2018-11-02 18:02:53 +00:00
describe('when the new verified status is VERIFIED', () => {
describe('when there is no existing record', () => {
before(async () => {
await window.Signal.Data.removeIdentityKeyById(theirUuid.toString());
await store.hydrateCaches();
2017-06-17 03:56:06 +00:00
});
2018-11-02 18:02:53 +00:00
it('saves the new identity and marks it verified', async () => {
await store.processVerifiedMessage(
theirUuid,
2018-10-18 01:01:21 +00:00
store.VerifiedStatus.VERIFIED,
newIdentity
);
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(identity.verified, store.VerifiedStatus.VERIFIED);
assert.isTrue(constantTimeEqual(identity.publicKey, newIdentity));
2018-10-18 01:01:21 +00:00
assert.strictEqual(keychangeTriggered, 0);
2017-06-17 03:56:06 +00:00
});
});
2018-11-02 18:02:53 +00:00
describe('when the record exists', () => {
describe('when the existing key is different', () => {
before(async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
firstUse: true,
timestamp: Date.now(),
verified: store.VerifiedStatus.VERIFIED,
nonblockingApproval: false,
2017-06-17 03:56:06 +00:00
});
await store.hydrateCaches();
});
2018-11-02 18:02:53 +00:00
it('saves the new identity and marks it VERIFIED', async () => {
await store.processVerifiedMessage(
theirUuid,
2018-10-18 01:01:21 +00:00
store.VerifiedStatus.VERIFIED,
newIdentity
);
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
2018-10-18 01:01:21 +00:00
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(
identity.verified,
store.VerifiedStatus.VERIFIED
);
assert.isTrue(constantTimeEqual(identity.publicKey, newIdentity));
2018-10-18 01:01:21 +00:00
assert.strictEqual(keychangeTriggered, 1);
2017-06-17 03:56:06 +00:00
});
});
2018-11-02 18:02:53 +00:00
describe('when the existing key is the same but UNVERIFIED', () => {
before(async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
firstUse: true,
timestamp: Date.now(),
verified: store.VerifiedStatus.UNVERIFIED,
nonblockingApproval: false,
2017-06-17 03:56:06 +00:00
});
await store.hydrateCaches();
});
2017-06-17 03:56:06 +00:00
2018-11-02 18:02:53 +00:00
it('saves the identity and marks it verified', async () => {
await store.processVerifiedMessage(
theirUuid,
2018-10-18 01:01:21 +00:00
store.VerifiedStatus.VERIFIED,
testKey.pubKey
);
const identity = await window.Signal.Data.getIdentityKeyById(
theirUuid.toString()
2018-10-18 01:01:21 +00:00
);
if (!identity) {
throw new Error('Missing identity!');
}
2018-10-18 01:01:21 +00:00
assert.strictEqual(
identity.verified,
store.VerifiedStatus.VERIFIED
);
assert.isTrue(
constantTimeEqual(identity.publicKey, testKey.pubKey)
);
2018-10-18 01:01:21 +00:00
assert.strictEqual(keychangeTriggered, 0);
2017-06-17 03:56:06 +00:00
});
});
2018-11-02 18:02:53 +00:00
describe('when the existing key is the same and already VERIFIED', () => {
before(async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
firstUse: true,
timestamp: Date.now(),
verified: store.VerifiedStatus.VERIFIED,
nonblockingApproval: false,
});
await store.hydrateCaches();
});
2018-11-02 18:02:53 +00:00
it('does not hang', async () => {
await store.processVerifiedMessage(
theirUuid,
2018-10-18 01:01:21 +00:00
store.VerifiedStatus.VERIFIED,
testKey.pubKey
);
assert.strictEqual(keychangeTriggered, 0);
});
});
2017-06-17 03:56:06 +00:00
});
});
});
2018-11-02 18:02:53 +00:00
describe('isUntrusted', () => {
it('returns false if identity key old enough', async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
timestamp: Date.now() - 10 * 1000 * 60,
verified: store.VerifiedStatus.DEFAULT,
firstUse: false,
nonblockingApproval: false,
});
2018-10-18 01:01:21 +00:00
await store.hydrateCaches();
const untrusted = await store.isUntrusted(theirUuid);
2018-10-18 01:01:21 +00:00
assert.strictEqual(untrusted, false);
});
2018-11-02 18:02:53 +00:00
it('returns false if new but nonblockingApproval is true', async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
timestamp: Date.now(),
verified: store.VerifiedStatus.DEFAULT,
firstUse: false,
nonblockingApproval: true,
});
await store.hydrateCaches();
2018-10-18 01:01:21 +00:00
const untrusted = await store.isUntrusted(theirUuid);
2018-10-18 01:01:21 +00:00
assert.strictEqual(untrusted, false);
});
2018-11-02 18:02:53 +00:00
it('returns false if new but firstUse is true', async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
timestamp: Date.now(),
verified: store.VerifiedStatus.DEFAULT,
firstUse: true,
nonblockingApproval: false,
});
await store.hydrateCaches();
2018-10-18 01:01:21 +00:00
const untrusted = await store.isUntrusted(theirUuid);
2018-10-18 01:01:21 +00:00
assert.strictEqual(untrusted, false);
});
2018-11-02 18:02:53 +00:00
it('returns true if new, and no flags are set', async () => {
2018-10-18 01:01:21 +00:00
await window.Signal.Data.createOrUpdateIdentityKey({
id: theirUuid.toString(),
2018-04-27 21:25:04 +00:00
publicKey: testKey.pubKey,
timestamp: Date.now(),
verified: store.VerifiedStatus.DEFAULT,
firstUse: false,
nonblockingApproval: false,
});
await store.hydrateCaches();
const untrusted = await store.isUntrusted(theirUuid);
2018-10-18 01:01:21 +00:00
assert.strictEqual(untrusted, true);
});
});
2018-11-02 18:02:53 +00:00
describe('getVerified', () => {
before(async () => {
await store.setVerified(theirUuid, store.VerifiedStatus.VERIFIED);
2017-06-13 20:57:46 +00:00
});
2018-11-02 18:02:53 +00:00
it('resolves to the verified status', async () => {
const result = await store.getVerified(theirUuid);
2018-10-18 01:01:21 +00:00
assert.strictEqual(result, store.VerifiedStatus.VERIFIED);
2017-06-13 20:57:46 +00:00
});
});
2018-11-02 18:02:53 +00:00
describe('isTrustedIdentity', () => {
const identifier = new Address(theirUuid, 1);
2018-10-18 01:01:21 +00:00
2018-11-02 18:02:53 +00:00
describe('When invalid direction is given', () => {
it('should fail', async () => {
2022-01-28 00:28:41 +00:00
await assert.isRejected(
store.isTrustedIdentity(identifier, testKey.pubKey, 'dir' as any)
);
});
2016-05-04 07:09:44 +00:00
});
2018-11-02 18:02:53 +00:00
describe('When direction is RECEIVING', () => {
it('always returns true', async () => {
const newIdentity = getPublicKey();
2018-10-18 01:01:21 +00:00
await store.saveIdentity(identifier, testKey.pubKey);
const trusted = await store.isTrustedIdentity(
identifier,
newIdentity,
Direction.Receiving
2018-10-18 01:01:21 +00:00
);
if (!trusted) {
throw new Error('isTrusted returned false when receiving');
}
});
});
2018-11-02 18:02:53 +00:00
describe('When direction is SENDING', () => {
describe('When there is no existing key (first use)', () => {
before(async () => {
await store.removeIdentityKey(theirUuid);
});
2018-11-02 18:02:53 +00:00
it('returns true', async () => {
const newIdentity = getPublicKey();
2018-10-18 01:01:21 +00:00
const trusted = await store.isTrustedIdentity(
identifier,
newIdentity,
Direction.Sending
2018-10-18 01:01:21 +00:00
);
if (!trusted) {
throw new Error('isTrusted returned false on first use');
}
});
});
2018-11-02 18:02:53 +00:00
describe('When there is an existing key', () => {
before(async () => {
2018-10-18 01:01:21 +00:00
await store.saveIdentity(identifier, testKey.pubKey);
});
2018-11-02 18:02:53 +00:00
describe('When the existing key is different', () => {
it('returns false', async () => {
const newIdentity = getPublicKey();
2018-10-18 01:01:21 +00:00
const trusted = await store.isTrustedIdentity(
identifier,
newIdentity,
Direction.Sending
2018-10-18 01:01:21 +00:00
);
if (trusted) {
throw new Error('isTrusted returned true on untrusted key');
}
});
2016-05-04 07:30:42 +00:00
});
2018-11-02 18:02:53 +00:00
describe('When the existing key matches the new key', () => {
const newIdentity = getPublicKey();
2018-11-02 18:02:53 +00:00
before(async () => {
2018-10-18 01:01:21 +00:00
await store.saveIdentity(identifier, newIdentity);
});
2018-11-02 18:02:53 +00:00
it('returns false if keys match but we just received this new identiy', async () => {
2018-10-18 01:01:21 +00:00
const trusted = await store.isTrustedIdentity(
identifier,
newIdentity,
Direction.Sending
2018-10-18 01:01:21 +00:00
);
if (trusted) {
throw new Error('isTrusted returned true on untrusted key');
}
2018-04-27 21:25:04 +00:00
});
2018-11-02 18:02:53 +00:00
it('returns true if we have already approved identity', async () => {
2018-10-18 01:01:21 +00:00
await store.saveIdentity(identifier, newIdentity, true);
const trusted = await store.isTrustedIdentity(
identifier,
newIdentity,
Direction.Sending
2018-10-18 01:01:21 +00:00
);
if (!trusted) {
throw new Error('isTrusted returned false on an approved key');
}
});
});
});
});
});
2018-11-02 18:02:53 +00:00
describe('storePreKey', () => {
it('stores prekeys', async () => {
await store.storePreKey(ourUuid, 1, testKey);
const key = await store.loadPreKey(ourUuid, 1);
if (!key) {
throw new Error('Missing key!');
}
const keyPair = {
2021-09-24 00:49:05 +00:00
pubKey: key.publicKey().serialize(),
privKey: key.privateKey().serialize(),
};
assert.isTrue(constantTimeEqual(keyPair.pubKey, testKey.pubKey));
assert.isTrue(constantTimeEqual(keyPair.privKey, testKey.privKey));
2016-05-04 07:30:42 +00:00
});
});
2018-11-02 18:02:53 +00:00
describe('removePreKey', () => {
before(async () => {
await store.storePreKey(ourUuid, 2, testKey);
});
2018-11-02 18:02:53 +00:00
it('deletes prekeys', async () => {
await store.removePreKey(ourUuid, 2);
2018-10-18 01:01:21 +00:00
const key = await store.loadPreKey(ourUuid, 2);
2018-10-18 01:01:21 +00:00
assert.isUndefined(key);
});
});
2018-11-02 18:02:53 +00:00
describe('storeSignedPreKey', () => {
it('stores signed prekeys', async () => {
await store.storeSignedPreKey(ourUuid, 3, testKey);
const key = await store.loadSignedPreKey(ourUuid, 3);
if (!key) {
throw new Error('Missing key!');
}
const keyPair = {
2021-09-24 00:49:05 +00:00
pubKey: key.publicKey().serialize(),
privKey: key.privateKey().serialize(),
};
assert.isTrue(constantTimeEqual(keyPair.pubKey, testKey.pubKey));
assert.isTrue(constantTimeEqual(keyPair.privKey, testKey.privKey));
});
});
2018-11-02 18:02:53 +00:00
describe('removeSignedPreKey', () => {
before(async () => {
await store.storeSignedPreKey(ourUuid, 4, testKey);
});
2018-11-02 18:02:53 +00:00
it('deletes signed prekeys', async () => {
await store.removeSignedPreKey(ourUuid, 4);
2018-10-18 01:01:21 +00:00
const key = await store.loadSignedPreKey(ourUuid, 4);
2018-10-18 01:01:21 +00:00
assert.isUndefined(key);
});
});
2018-11-02 18:02:53 +00:00
describe('storeSession', () => {
it('stores sessions', async () => {
const testRecord = getSessionRecord();
const id = new QualifiedAddress(ourUuid, new Address(theirUuid, 1));
await store.storeSession(id, testRecord);
const record = await store.loadSession(id);
if (!record) {
throw new Error('Missing record!');
}
2018-10-18 01:01:21 +00:00
assert.equal(record, testRecord);
2016-05-04 07:30:42 +00:00
});
});
2018-11-02 18:02:53 +00:00
describe('removeAllSessions', () => {
it('removes all sessions for a uuid', async () => {
const devices = [1, 2, 3].map(
deviceId =>
new QualifiedAddress(ourUuid, new Address(theirUuid, deviceId))
);
2018-10-18 01:01:21 +00:00
await Promise.all(
devices.map(async encodedAddress => {
await store.storeSession(encodedAddress, getSessionRecord());
2018-04-27 21:25:04 +00:00
})
2018-10-18 01:01:21 +00:00
);
await store.removeAllSessions(theirUuid.toString());
2018-10-18 01:01:21 +00:00
const records = await Promise.all(
devices.map(device => store.loadSession(device))
2018-10-18 01:01:21 +00:00
);
2018-11-02 18:02:53 +00:00
for (let i = 0, max = records.length; i < max; i += 1) {
2018-10-18 01:01:21 +00:00
assert.isUndefined(records[i]);
}
});
});
2018-11-02 18:02:53 +00:00
describe('clearSessionStore', () => {
it('clears the session store', async () => {
const testRecord = getSessionRecord();
const id = new QualifiedAddress(ourUuid, new Address(theirUuid, 1));
await store.storeSession(id, testRecord);
2018-10-18 01:01:21 +00:00
await store.clearSessionStore();
const record = await store.loadSession(id);
2018-10-18 01:01:21 +00:00
assert.isUndefined(record);
2016-05-04 07:30:42 +00:00
});
});
2018-11-02 18:02:53 +00:00
describe('getDeviceIds', () => {
it('returns deviceIds for a uuid', async () => {
const openRecord = getSessionRecord(true);
const openDevices = [1, 2, 3, 10].map(
deviceId =>
new QualifiedAddress(ourUuid, new Address(theirUuid, deviceId))
);
2018-10-18 01:01:21 +00:00
await Promise.all(
openDevices.map(async address => {
await store.storeSession(address, openRecord);
2018-04-27 21:25:04 +00:00
})
2018-10-18 01:01:21 +00:00
);
const closedRecord = getSessionRecord(false);
await store.storeSession(
new QualifiedAddress(ourUuid, new Address(theirUuid, 11)),
closedRecord
);
const deviceIds = await store.getDeviceIds({
ourUuid,
identifier: theirUuid.toString(),
});
assert.sameMembers(deviceIds, [1, 2, 3, 10]);
});
it('returns empty array for a uuid with no device ids', async () => {
const deviceIds = await store.getDeviceIds({
ourUuid,
identifier: 'foo',
});
2018-10-18 01:01:21 +00:00
assert.sameMembers(deviceIds, []);
});
});
2021-05-25 22:40:04 +00:00
describe('getOpenDevices', () => {
it('returns all open devices for a uuid', async () => {
2021-05-25 22:40:04 +00:00
const openRecord = getSessionRecord(true);
const openDevices = [1, 2, 3, 10].map(
deviceId =>
new QualifiedAddress(ourUuid, new Address(theirUuid, deviceId))
);
2021-05-25 22:40:04 +00:00
await Promise.all(
openDevices.map(async address => {
await store.storeSession(address, openRecord);
2021-05-25 22:40:04 +00:00
})
);
const closedRecord = getSessionRecord(false);
await store.storeSession(
new QualifiedAddress(ourUuid, new Address(theirUuid, 11)),
closedRecord
);
2021-05-25 22:40:04 +00:00
const result = await store.getOpenDevices(ourUuid, [
theirUuid.toString(),
'blah',
'blah2',
]);
assert.deepStrictEqual(
{
...result,
devices: result.devices.map(({ id, identifier, registrationId }) => ({
id,
identifier: identifier.toString(),
registrationId,
})),
},
{
devices: [
{
id: 1,
identifier: theirUuid.toString(),
registrationId: 243,
},
{
id: 2,
identifier: theirUuid.toString(),
registrationId: 243,
},
{
id: 3,
identifier: theirUuid.toString(),
registrationId: 243,
},
{
id: 10,
identifier: theirUuid.toString(),
registrationId: 243,
},
],
emptyIdentifiers: ['blah', 'blah2'],
}
);
2021-05-25 22:40:04 +00:00
});
it('returns empty array for a uuid with no device ids', async () => {
const result = await store.getOpenDevices(ourUuid, ['foo']);
2021-05-25 22:40:04 +00:00
assert.deepEqual(result, {
devices: [],
emptyIdentifiers: ['foo'],
});
});
});
2021-05-19 21:25:56 +00:00
describe('zones', () => {
const distributionId = UUID.generate().toString();
2021-05-19 21:25:56 +00:00
const zone = new Zone('zone', {
pendingSenderKeys: true,
2021-05-19 21:25:56 +00:00
pendingSessions: true,
pendingUnprocessed: true,
});
beforeEach(async () => {
await store.removeAllUnprocessed();
await store.removeAllSessions(theirUuid.toString());
await store.removeAllSenderKeys();
});
2021-05-19 21:25:56 +00:00
it('should not store pending sessions in global zone', async () => {
const id = new QualifiedAddress(ourUuid, new Address(theirUuid, 1));
2021-05-19 21:25:56 +00:00
const testRecord = getSessionRecord();
await assert.isRejected(
store.withZone(GLOBAL_ZONE, 'test', async () => {
await store.storeSession(id, testRecord);
throw new Error('Failure');
}),
'Failure'
);
assert.equal(await store.loadSession(id), testRecord);
});
it('should not store pending sender keys in global zone', async () => {
const id = new QualifiedAddress(ourUuid, new Address(theirUuid, 1));
const testRecord = getSenderKeyRecord();
await assert.isRejected(
store.withZone(GLOBAL_ZONE, 'test', async () => {
await store.saveSenderKey(id, distributionId, testRecord);
throw new Error('Failure');
}),
'Failure'
);
assert.equal(await store.getSenderKey(id, distributionId), testRecord);
});
it('commits sender keys, sessions and unprocessed on success', async () => {
const id = new QualifiedAddress(ourUuid, new Address(theirUuid, 1));
const testSession = getSessionRecord();
const testSenderKey = getSenderKeyRecord();
2021-05-19 21:25:56 +00:00
await store.withZone(zone, 'test', async () => {
await store.storeSession(id, testSession, { zone });
await store.saveSenderKey(id, distributionId, testSenderKey, { zone });
2021-05-19 21:25:56 +00:00
await store.addUnprocessed(
{
id: '2-two',
version: 2,
2021-05-19 21:25:56 +00:00
attempts: 0,
envelope: 'second',
receivedAtCounter: 0,
timestamp: Date.now() + 2,
urgent: true,
2021-05-19 21:25:56 +00:00
},
{ zone }
);
assert.equal(await store.loadSession(id, { zone }), testSession);
assert.equal(
await store.getSenderKey(id, distributionId, { zone }),
testSenderKey
);
});
assert.equal(await store.loadSession(id), testSession);
assert.equal(await store.getSenderKey(id, distributionId), testSenderKey);
const allUnprocessed =
await store.getAllUnprocessedAndIncrementAttempts();
assert.deepEqual(
allUnprocessed.map(({ envelope }) => envelope),
['second']
);
});
it('reverts sender keys, sessions and unprocessed on error', async () => {
const id = new QualifiedAddress(ourUuid, new Address(theirUuid, 1));
const testSession = getSessionRecord();
const failedSession = getSessionRecord();
const testSenderKey = getSenderKeyRecord();
const failedSenderKey = getSenderKeyRecord();
await store.storeSession(id, testSession);
assert.equal(await store.loadSession(id), testSession);
await store.saveSenderKey(id, distributionId, testSenderKey);
assert.equal(await store.getSenderKey(id, distributionId), testSenderKey);
await assert.isRejected(
2021-05-19 21:25:56 +00:00
store.withZone(zone, 'test', async () => {
await store.storeSession(id, failedSession, { zone });
assert.equal(await store.loadSession(id, { zone }), failedSession);
await store.saveSenderKey(id, distributionId, failedSenderKey, {
zone,
});
assert.equal(
await store.getSenderKey(id, distributionId, { zone }),
failedSenderKey
);
2021-05-19 21:25:56 +00:00
await store.addUnprocessed(
{
id: '2-two',
version: 2,
2021-05-19 21:25:56 +00:00
attempts: 0,
envelope: 'second',
receivedAtCounter: 0,
timestamp: 2,
urgent: true,
2021-05-19 21:25:56 +00:00
},
{ zone }
);
throw new Error('Failure');
}),
'Failure'
);
assert.equal(await store.loadSession(id), testSession);
assert.equal(await store.getSenderKey(id, distributionId), testSenderKey);
assert.deepEqual(await store.getAllUnprocessedAndIncrementAttempts(), []);
});
it('can be re-entered', async () => {
const id = new QualifiedAddress(ourUuid, new Address(theirUuid, 1));
const testRecord = getSessionRecord();
2021-05-19 21:25:56 +00:00
await store.withZone(zone, 'test', async () => {
await store.withZone(zone, 'nested', async () => {
await store.storeSession(id, testRecord, { zone });
2021-05-19 21:25:56 +00:00
assert.equal(await store.loadSession(id, { zone }), testRecord);
});
2021-05-19 21:25:56 +00:00
assert.equal(await store.loadSession(id, { zone }), testRecord);
});
assert.equal(await store.loadSession(id), testRecord);
});
it('can be re-entered after waiting', async () => {
const a = new Zone('a');
const b = new Zone('b');
const order: Array<number> = [];
const promises: Array<Promise<unknown>> = [];
// What happens below is briefly following:
// 1. We enter zone "a"
// 2. We wait for zone "a" to be left to enter zone "b"
// 3. Skip few ticks to trigger leave of zone "a" and resolve the waiting
// queue promise for zone "b"
// 4. Enter zone "a" while resolution was the promise above is queued in
// microtasks queue.
promises.push(store.withZone(a, 'a', async () => order.push(1)));
promises.push(store.withZone(b, 'b', async () => order.push(2)));
await Promise.resolve();
await Promise.resolve();
promises.push(store.withZone(a, 'a again', async () => order.push(3)));
await Promise.all(promises);
assert.deepEqual(order, [1, 2, 3]);
});
2021-05-28 23:09:17 +00:00
it('should not deadlock in archiveSiblingSessions', async () => {
const id = new QualifiedAddress(ourUuid, new Address(theirUuid, 1));
const sibling = new QualifiedAddress(ourUuid, new Address(theirUuid, 2));
2021-05-28 23:09:17 +00:00
await store.storeSession(id, getSessionRecord(true));
await store.storeSession(sibling, getSessionRecord(true));
await store.archiveSiblingSessions(id.address, { zone });
2021-05-28 23:09:17 +00:00
});
2021-08-24 21:07:40 +00:00
it('can be concurrently re-entered after waiting', async () => {
const a = new Zone('a');
const b = new Zone('b');
const order: Array<number> = [];
const promises: Array<Promise<unknown>> = [];
// 1. Enter zone "a"
// 2. Wait for zone "a" to be left to enter zone "b" twice
// 3. Verify that both zone "b" tasks ran in parallel
promises.push(store.withZone(a, 'a', async () => order.push(1)));
promises.push(
store.withZone(b, 'b', async () => {
order.push(2);
await Promise.resolve();
order.push(22);
})
);
promises.push(
store.withZone(b, 'b', async () => {
order.push(3);
await Promise.resolve();
order.push(33);
})
);
await Promise.resolve();
await Promise.resolve();
await Promise.all(promises);
assert.deepEqual(order, [1, 2, 3, 22, 33]);
});
});
2018-11-02 18:02:53 +00:00
describe('Not yet processed messages', () => {
2021-12-14 01:25:44 +00:00
const NOW = Date.now();
2018-11-02 18:02:53 +00:00
beforeEach(async () => {
2018-10-18 01:01:21 +00:00
await store.removeAllUnprocessed();
const items = await store.getAllUnprocessedAndIncrementAttempts();
2018-10-18 01:01:21 +00:00
assert.strictEqual(items.length, 0);
});
it('adds three and gets them back', async () => {
2018-10-18 01:01:21 +00:00
await Promise.all([
2021-12-14 01:25:44 +00:00
store.addUnprocessed({
id: '0-dropped',
version: 2,
2021-12-14 01:25:44 +00:00
attempts: 0,
envelope: 'old envelope',
receivedAtCounter: -1,
timestamp: NOW - 2 * durations.MONTH,
urgent: true,
2021-12-14 01:25:44 +00:00
}),
store.addUnprocessed({
id: '2-two',
version: 2,
attempts: 0,
envelope: 'second',
receivedAtCounter: 1,
timestamp: NOW + 2,
urgent: true,
}),
store.addUnprocessed({
id: '3-three',
version: 2,
attempts: 0,
envelope: 'third',
receivedAtCounter: 2,
timestamp: NOW + 3,
urgent: true,
}),
store.addUnprocessed({
id: '1-one',
version: 2,
attempts: 0,
envelope: 'first',
receivedAtCounter: 0,
timestamp: NOW + 1,
urgent: true,
}),
2018-10-18 01:01:21 +00:00
]);
const items = await store.getAllUnprocessedAndIncrementAttempts();
2018-10-18 01:01:21 +00:00
assert.strictEqual(items.length, 3);
// they are in the proper order because the collection comparator is
// 'receivedAtCounter'
assert.strictEqual(items[0].envelope, 'first');
assert.strictEqual(items[1].envelope, 'second');
assert.strictEqual(items[2].envelope, 'third');
});
2021-05-27 15:45:45 +00:00
it('can updates items', async () => {
const id = '1-one';
await store.addUnprocessed({
id,
version: 2,
attempts: 0,
envelope: 'first',
receivedAtCounter: 0,
timestamp: NOW + 1,
urgent: false,
});
await store.updateUnprocessedWithData(id, { decrypted: 'updated' });
2018-10-18 01:01:21 +00:00
const items = await store.getAllUnprocessedAndIncrementAttempts();
2018-10-18 01:01:21 +00:00
assert.strictEqual(items.length, 1);
assert.strictEqual(items[0].decrypted, 'updated');
2021-12-14 01:25:44 +00:00
assert.strictEqual(items[0].timestamp, NOW + 1);
assert.strictEqual(items[0].attempts, 1);
assert.strictEqual(items[0].urgent, false);
});
2018-11-02 18:02:53 +00:00
it('removeUnprocessed successfully deletes item', async () => {
const id = '1-one';
await store.addUnprocessed({
id,
version: 2,
attempts: 0,
envelope: 'first',
receivedAtCounter: 0,
timestamp: NOW + 1,
urgent: true,
});
2018-10-18 01:01:21 +00:00
await store.removeUnprocessed(id);
const items = await store.getAllUnprocessedAndIncrementAttempts();
assert.strictEqual(items.length, 0);
});
it('getAllUnprocessedAndIncrementAttempts deletes items', async () => {
await store.addUnprocessed({
id: '1-one',
version: 2,
attempts: 3,
envelope: 'first',
receivedAtCounter: 0,
timestamp: NOW + 1,
urgent: true,
});
const items = await store.getAllUnprocessedAndIncrementAttempts();
2018-10-18 01:01:21 +00:00
assert.strictEqual(items.length, 0);
});
});
});