474 lines
19 KiB
JavaScript
474 lines
19 KiB
JavaScript
const {
|
|
OlmMachine,
|
|
UserId,
|
|
DeviceId,
|
|
DeviceKeyId,
|
|
RoomId,
|
|
DeviceLists,
|
|
RequestType,
|
|
KeysUploadRequest,
|
|
KeysQueryRequest,
|
|
KeysClaimRequest,
|
|
EncryptionSettings,
|
|
DecryptedRoomEvent,
|
|
VerificationState,
|
|
CrossSigningStatus,
|
|
MaybeSignature,
|
|
StoreType,
|
|
ShieldColor,
|
|
} = require("../");
|
|
const path = require("path");
|
|
const os = require("os");
|
|
const fs = require("fs/promises");
|
|
|
|
describe("StoreType", () => {
|
|
test("has the correct variant values", () => {
|
|
expect(StoreType.Sled).toStrictEqual(0);
|
|
expect(StoreType.Sqlite).toStrictEqual(1);
|
|
});
|
|
});
|
|
|
|
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", () => {
|
|
for (const [store_type, store_name] of [
|
|
[StoreType.Sled, "sled"],
|
|
[StoreType.Sqlite, "sqlite"],
|
|
[null, "default"],
|
|
]) {
|
|
test(`with no passphrase (store: ${store_name})`, 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,
|
|
null,
|
|
store_type,
|
|
),
|
|
).toBeInstanceOf(OlmMachine);
|
|
});
|
|
|
|
test(`with a passphrase (store: ${store_name})`, 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",
|
|
store_type,
|
|
),
|
|
).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 drop/close, and then re-open", async () => {
|
|
const temp_directory = await fs.mkdtemp(path.join(os.tmpdir(), "matrix-sdk-crypto--"));
|
|
|
|
let m1 = await OlmMachine.initialize(
|
|
new UserId("@test:bar.org"),
|
|
new DeviceId("device"),
|
|
temp_directory,
|
|
"hello",
|
|
);
|
|
m1.close();
|
|
|
|
let m2 = await OlmMachine.initialize(
|
|
new UserId("@test:bar.org"),
|
|
new DeviceId("device"),
|
|
temp_directory,
|
|
"hello",
|
|
);
|
|
m2.close();
|
|
});
|
|
|
|
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.shieldState(true).color).toStrictEqual(ShieldColor.Red);
|
|
expect(decrypted.shieldState(false).color).toStrictEqual(ShieldColor.Red);
|
|
});
|
|
});
|
|
|
|
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();
|
|
}
|
|
});
|
|
});
|