mirror of
https://github.com/matrix-org/matrix-rust-sdk.git
synced 2026-05-15 11:36:07 -04:00
Add general extension framework for sliding sync, implementing the e2ee, to-device and account-data extensions as per existing proxy implementation. Add a new (ffi exposed) function to use activate the extensions. Also extends jack-in to have permanent login and storage support now, rather than posting an access token and expose messages inside the sliding-sync layer to actually use the decrypted messages if given. Contains a lot of fixes around these aspects, too, like uploading any remaining messages from the olm-machine on every sliding-sync-request or processing even if no room data is present (which can happen now as processing only extensions might takes place).
401 lines
17 KiB
JavaScript
401 lines
17 KiB
JavaScript
const { OlmMachine, UserId, DeviceId, DeviceKeyId, RoomId, DeviceLists, RequestType, KeysUploadRequest, KeysQueryRequest, KeysClaimRequest, EncryptionSettings, DecryptedRoomEvent, VerificationState, CrossSigningStatus, MaybeSignature } = require('../');
|
|
const path = require('path');
|
|
const os = require('os');
|
|
const fs = require('fs/promises');
|
|
|
|
describe(OlmMachine.name, () => {
|
|
test('cannot be instantiated with the constructor', () => {
|
|
expect(() => { new OlmMachine() }).toThrow();
|
|
});
|
|
|
|
test('can be instantiated with the async initializer', async () => {
|
|
expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'))).toBeInstanceOf(OlmMachine);
|
|
});
|
|
|
|
describe('can be instantiated with a store', () => {
|
|
test('with no passphrase', async () => {
|
|
const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), 'matrix-sdk-crypto--'));
|
|
|
|
expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), temp_directory)).toBeInstanceOf(OlmMachine);
|
|
});
|
|
|
|
test('with a passphrase', async () => {
|
|
const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), 'matrix-sdk-crypto--'));
|
|
|
|
expect(await OlmMachine.initialize(new UserId('@foo:bar.org'), new DeviceId('baz'), temp_directory, 'hello')).toBeInstanceOf(OlmMachine);
|
|
});
|
|
});
|
|
|
|
const user = new UserId('@alice:example.org');
|
|
const device = new DeviceId('foobar');
|
|
const room = new RoomId('!baz:matrix.org');
|
|
|
|
function machine(new_user, new_device) {
|
|
return OlmMachine.initialize(new_user || user, new_device || device);
|
|
}
|
|
|
|
test('can read user ID', async () => {
|
|
expect((await machine()).userId.toString()).toStrictEqual(user.toString());
|
|
});
|
|
|
|
test('can read device ID', async () => {
|
|
expect((await machine()).deviceId.toString()).toStrictEqual(device.toString());
|
|
});
|
|
|
|
test('can read identity keys', async () => {
|
|
const identityKeys = (await machine()).identityKeys;
|
|
|
|
expect(identityKeys.ed25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
|
|
expect(identityKeys.curve25519.toBase64()).toMatch(/^[A-Za-z0-9+/]+$/);
|
|
});
|
|
|
|
test('can receive sync changes', async () => {
|
|
const m = await machine();
|
|
const toDeviceEvents = JSON.stringify([]);
|
|
const changedDevices = new DeviceLists();
|
|
const oneTimeKeyCounts = {};
|
|
const unusedFallbackKeys = [];
|
|
|
|
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
|
|
|
expect(receiveSyncChanges).toEqual([]);
|
|
});
|
|
|
|
test('can get the outgoing requests that need to be send out', async () => {
|
|
const m = await machine();
|
|
const toDeviceEvents = JSON.stringify([]);
|
|
const changedDevices = new DeviceLists();
|
|
const oneTimeKeyCounts = {};
|
|
const unusedFallbackKeys = [];
|
|
|
|
const receiveSyncChanges = JSON.parse(await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys));
|
|
|
|
expect(receiveSyncChanges).toEqual([]);
|
|
|
|
const outgoingRequests = await m.outgoingRequests();
|
|
|
|
expect(outgoingRequests).toHaveLength(2);
|
|
|
|
{
|
|
expect(outgoingRequests[0]).toBeInstanceOf(KeysUploadRequest);
|
|
expect(outgoingRequests[0].id).toBeDefined();
|
|
expect(outgoingRequests[0].type).toStrictEqual(RequestType.KeysUpload);
|
|
|
|
const body = JSON.parse(outgoingRequests[0].body);
|
|
expect(body.device_keys).toBeDefined();
|
|
expect(body.one_time_keys).toBeDefined();
|
|
}
|
|
|
|
{
|
|
expect(outgoingRequests[1]).toBeInstanceOf(KeysQueryRequest);
|
|
expect(outgoingRequests[1].id).toBeDefined();
|
|
expect(outgoingRequests[1].type).toStrictEqual(RequestType.KeysQuery);
|
|
|
|
const body = JSON.parse(outgoingRequests[1].body);
|
|
expect(body.timeout).toBeDefined();
|
|
expect(body.device_keys).toBeDefined();
|
|
expect(body.token).toBeDefined();
|
|
}
|
|
});
|
|
|
|
describe('setup workflow to mark requests as sent', () => {
|
|
let m;
|
|
let ougoingRequests;
|
|
|
|
beforeAll(async () => {
|
|
m = await machine(new UserId('@alice:example.org'), new DeviceId('DEVICEID'));
|
|
|
|
const toDeviceEvents = JSON.stringify([]);
|
|
const changedDevices = new DeviceLists();
|
|
const oneTimeKeyCounts = {};
|
|
const unusedFallbackKeys = [];
|
|
|
|
await m.receiveSyncChanges(toDeviceEvents, changedDevices, oneTimeKeyCounts, unusedFallbackKeys);
|
|
outgoingRequests = await m.outgoingRequests();
|
|
|
|
expect(outgoingRequests).toHaveLength(2);
|
|
});
|
|
|
|
test('can mark requests as sent', async () => {
|
|
{
|
|
const request = outgoingRequests[0];
|
|
expect(request).toBeInstanceOf(KeysUploadRequest);
|
|
|
|
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysupload
|
|
const hypothetical_response = JSON.stringify({
|
|
"one_time_key_counts": {
|
|
"curve25519": 10,
|
|
"signed_curve25519": 20
|
|
}
|
|
});
|
|
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
|
|
expect(marked).toStrictEqual(true);
|
|
}
|
|
|
|
{
|
|
const request = outgoingRequests[1];
|
|
expect(request).toBeInstanceOf(KeysQueryRequest);
|
|
|
|
// https://spec.matrix.org/v1.2/client-server-api/#post_matrixclientv3keysquery
|
|
const hypothetical_response = JSON.stringify({
|
|
"device_keys": {
|
|
"@alice:example.org": {
|
|
"JLAFKJWSCS": {
|
|
"algorithms": [
|
|
"m.olm.v1.curve25519-aes-sha2",
|
|
"m.megolm.v1.aes-sha2"
|
|
],
|
|
"device_id": "JLAFKJWSCS",
|
|
"keys": {
|
|
"curve25519:JLAFKJWSCS": "wjLpTLRqbqBzLs63aYaEv2Boi6cFEbbM/sSRQ2oAKk4",
|
|
"ed25519:JLAFKJWSCS": "nE6W2fCblxDcOFmeEtCHNl8/l8bXcu7GKyAswA4r3mM"
|
|
},
|
|
"signatures": {
|
|
"@alice:example.org": {
|
|
"ed25519:JLAFKJWSCS": "m53Wkbh2HXkc3vFApZvCrfXcX3AI51GsDHustMhKwlv3TuOJMj4wistcOTM8q2+e/Ro7rWFUb9ZfnNbwptSUBA"
|
|
}
|
|
},
|
|
"unsigned": {
|
|
"device_display_name": "Alice's mobile phone"
|
|
},
|
|
"user_id": "@alice:example.org"
|
|
}
|
|
}
|
|
},
|
|
"failures": {}
|
|
});
|
|
const marked = await m.markRequestAsSent(request.id, request.type, hypothetical_response);
|
|
expect(marked).toStrictEqual(true);
|
|
}
|
|
});
|
|
});
|
|
|
|
describe('setup workflow to encrypt/decrypt events', () => {
|
|
let m;
|
|
const user = new UserId('@alice:example.org');
|
|
const device = new DeviceId('JLAFKJWSCS');
|
|
const room = new RoomId('!test:localhost');
|
|
|
|
beforeAll(async () => {
|
|
m = await machine(user, device);
|
|
});
|
|
|
|
test('can pass keysquery and keysclaim requests directly', async () => {
|
|
{
|
|
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_query.json
|
|
const hypothetical_response = JSON.stringify({
|
|
"device_keys": {
|
|
"@example:localhost": {
|
|
"AFGUOBTZWM": {
|
|
"algorithms": [
|
|
"m.olm.v1.curve25519-aes-sha2",
|
|
"m.megolm.v1.aes-sha2"
|
|
],
|
|
"device_id": "AFGUOBTZWM",
|
|
"keys": {
|
|
"curve25519:AFGUOBTZWM": "boYjDpaC+7NkECQEeMh5dC+I1+AfriX0VXG2UV7EUQo",
|
|
"ed25519:AFGUOBTZWM": "NayrMQ33ObqMRqz6R9GosmHdT6HQ6b/RX/3QlZ2yiec"
|
|
},
|
|
"signatures": {
|
|
"@example:localhost": {
|
|
"ed25519:AFGUOBTZWM": "RoSWvru1jj6fs2arnTedWsyIyBmKHMdOu7r9gDi0BZ61h9SbCK2zLXzuJ9ZFLao2VvA0yEd7CASCmDHDLYpXCA"
|
|
}
|
|
},
|
|
"user_id": "@example:localhost",
|
|
"unsigned": {
|
|
"device_display_name": "rust-sdk"
|
|
}
|
|
},
|
|
}
|
|
},
|
|
"failures": {},
|
|
"master_keys": {
|
|
"@example:localhost": {
|
|
"user_id": "@example:localhost",
|
|
"usage": [
|
|
"master"
|
|
],
|
|
"keys": {
|
|
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU"
|
|
},
|
|
"signatures": {
|
|
"@example:localhost": {
|
|
"ed25519:TCSJXPWGVS": "+j9G3L41I1fe0++wwusTTQvbboYW0yDtRWUEujhwZz4MAltjLSfJvY0hxhnz+wHHmuEXvQDen39XOpr1p29sAg"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"self_signing_keys": {
|
|
"@example:localhost": {
|
|
"user_id": "@example:localhost",
|
|
"usage": [
|
|
"self_signing"
|
|
],
|
|
"keys": {
|
|
"ed25519:kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI": "kQXOuy639Yt47mvNTdrIluoC6DMvfbZLYbxAmwiDyhI"
|
|
},
|
|
"signatures": {
|
|
"@example:localhost": {
|
|
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "q32ifix/qyRpvmegw2BEJklwoBCAJldDNkcX+fp+lBA4Rpyqtycxge6BA4hcJdxYsy3oV0IHRuugS8rJMMFyAA"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
"user_signing_keys": {
|
|
"@example:localhost": {
|
|
"user_id": "@example:localhost",
|
|
"usage": [
|
|
"user_signing"
|
|
],
|
|
"keys": {
|
|
"ed25519:g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s": "g4ED07Fnqf3GzVWNN1pZ0IFrPQVdqQf+PYoJNH4eE0s"
|
|
},
|
|
"signatures": {
|
|
"@example:localhost": {
|
|
"ed25519:n2lpJGx0LiKnuNE1IucZP3QExrD4SeRP0veBHPe3XUU": "nKQu8alQKDefNbZz9luYPcNj+Z+ouQSot4fU/A23ELl1xrI06QVBku/SmDx0sIW1ytso0Cqwy1a+3PzCa1XABg"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
});
|
|
const marked = await m.markRequestAsSent('foo', RequestType.KeysQuery, hypothetical_response);
|
|
}
|
|
|
|
{
|
|
// derived from https://github.com/matrix-org/matrix-rust-sdk/blob/7f49618d350fab66b7e1dc4eaf64ec25ceafd658/benchmarks/benches/crypto_bench/keys_claim.json
|
|
const hypothetical_response = JSON.stringify({
|
|
"one_time_keys": {
|
|
"@example:localhost": {
|
|
"AFGUOBTZWM": {
|
|
"signed_curve25519:AAAABQ": {
|
|
"key": "9IGouMnkB6c6HOd4xUsNv4i3Dulb4IS96TzDordzOws",
|
|
"signatures": {
|
|
"@example:localhost": {
|
|
"ed25519:AFGUOBTZWM": "2bvUbbmJegrV0eVP/vcJKuIWC3kud+V8+C0dZtg4dVovOSJdTP/iF36tQn2bh5+rb9xLlSeztXBdhy4c+LiOAg"
|
|
}
|
|
}
|
|
}
|
|
},
|
|
}
|
|
},
|
|
"failures": {}
|
|
});
|
|
const marked = await m.markRequestAsSent('bar', RequestType.KeysClaim, hypothetical_response);
|
|
}
|
|
});
|
|
|
|
test('can share a room key', async () => {
|
|
const other_users = [new UserId('@example:localhost')];
|
|
|
|
const requests = JSON.parse(await m.shareRoomKey(room, other_users, new EncryptionSettings()));
|
|
|
|
expect(requests).toHaveLength(1);
|
|
expect(requests[0].event_type).toBeDefined();
|
|
expect(requests[0].txn_id).toBeDefined();
|
|
expect(requests[0].messages).toBeDefined();
|
|
expect(requests[0].messages['@example:localhost']).toBeDefined();
|
|
});
|
|
|
|
let encrypted;
|
|
|
|
test('can encrypt an event', async () => {
|
|
encrypted = JSON.parse(await m.encryptRoomEvent(
|
|
room,
|
|
'm.room.message',
|
|
JSON.stringify({
|
|
"hello": "world"
|
|
}),
|
|
));
|
|
|
|
expect(encrypted.algorithm).toBeDefined();
|
|
expect(encrypted.ciphertext).toBeDefined();
|
|
expect(encrypted.sender_key).toBeDefined();
|
|
expect(encrypted.device_id).toStrictEqual(device.toString());
|
|
expect(encrypted.session_id).toBeDefined();
|
|
});
|
|
|
|
test('can decrypt an event', async () => {
|
|
const decrypted = await m.decryptRoomEvent(
|
|
JSON.stringify({
|
|
"type": "m.room.encrypted",
|
|
"event_id": "$xxxxx:example.org",
|
|
"origin_server_ts": Date.now(),
|
|
"sender": user.toString(),
|
|
content: encrypted,
|
|
unsigned: {
|
|
"age": 1234
|
|
}
|
|
}),
|
|
room,
|
|
);
|
|
|
|
expect(decrypted).toBeInstanceOf(DecryptedRoomEvent);
|
|
|
|
const event = JSON.parse(decrypted.event);
|
|
expect(event.content.hello).toStrictEqual("world");
|
|
|
|
expect(decrypted.sender.toString()).toStrictEqual(user.toString());
|
|
expect(decrypted.senderDevice.toString()).toStrictEqual(device.toString());
|
|
expect(decrypted.senderCurve25519Key).toBeDefined();
|
|
expect(decrypted.senderClaimedEd25519Key).toBeDefined();
|
|
expect(decrypted.forwardingCurve25519KeyChain).toHaveLength(0);
|
|
expect(decrypted.verificationState).toStrictEqual(VerificationState.Trusted);
|
|
});
|
|
});
|
|
|
|
test('can update tracked users', async () => {
|
|
const m = await machine();
|
|
|
|
expect(await m.updateTrackedUsers([user])).toStrictEqual(undefined);
|
|
});
|
|
|
|
test('can read cross-signing status', async () => {
|
|
const m = await machine();
|
|
const crossSigningStatus = await m.crossSigningStatus();
|
|
|
|
expect(crossSigningStatus).toBeInstanceOf(CrossSigningStatus);
|
|
expect(crossSigningStatus.hasMaster).toStrictEqual(false);
|
|
expect(crossSigningStatus.hasSelfSigning).toStrictEqual(false);
|
|
expect(crossSigningStatus.hasUserSigning).toStrictEqual(false);
|
|
});
|
|
|
|
test('can sign a message', async () => {
|
|
const m = await machine();
|
|
const signatures = await m.sign('foo');
|
|
|
|
expect(signatures.isEmpty).toStrictEqual(false);
|
|
expect(signatures.count).toStrictEqual(1n);
|
|
|
|
let base64;
|
|
|
|
// `get`
|
|
{
|
|
const signature = signatures.get(user);
|
|
|
|
expect(signature).toMatchObject({
|
|
"ed25519:foobar": expect.any(MaybeSignature),
|
|
});
|
|
expect(signature['ed25519:foobar'].isValid).toStrictEqual(true);
|
|
expect(signature['ed25519:foobar'].isInvalid).toStrictEqual(false);
|
|
expect(signature['ed25519:foobar'].invalidSignatureSource).toBeNull();
|
|
|
|
base64 = signature['ed25519:foobar'].signature.toBase64();
|
|
|
|
expect(base64).toMatch(/^[A-Za-z0-9\+/]+$/);
|
|
expect(signature['ed25519:foobar'].signature.ed25519.toBase64()).toStrictEqual(base64);
|
|
}
|
|
|
|
// `getSignature`
|
|
{
|
|
const signature = signatures.getSignature(user, new DeviceKeyId('ed25519:foobar'));
|
|
expect(signature.toBase64()).toStrictEqual(base64);
|
|
}
|
|
|
|
// Unknown signatures.
|
|
{
|
|
expect(signatures.get(new UserId('@hello:example.org'))).toBeNull();
|
|
expect(signatures.getSignature(user, new DeviceKeyId('world:foobar'))).toBeNull();
|
|
}
|
|
});
|
|
});
|