You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-08-06 12:02:40 +03:00
Convert more of js-sdk crypto and fix underscored field accesses
This commit is contained in:
@@ -165,7 +165,7 @@ describe("DeviceList management:", function() {
|
|||||||
aliceTestClient.httpBackend.flush('/keys/query', 1).then(
|
aliceTestClient.httpBackend.flush('/keys/query', 1).then(
|
||||||
() => aliceTestClient.httpBackend.flush('/send/', 1),
|
() => aliceTestClient.httpBackend.flush('/send/', 1),
|
||||||
),
|
),
|
||||||
aliceTestClient.client.crypto._deviceList.saveIfDirty(),
|
aliceTestClient.client.crypto.deviceList.saveIfDirty(),
|
||||||
]);
|
]);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
||||||
@@ -202,7 +202,7 @@ describe("DeviceList management:", function() {
|
|||||||
return aliceTestClient.httpBackend.flush('/keys/query', 1);
|
return aliceTestClient.httpBackend.flush('/keys/query', 1);
|
||||||
}).then((flushed) => {
|
}).then((flushed) => {
|
||||||
expect(flushed).toEqual(0);
|
expect(flushed).toEqual(0);
|
||||||
return aliceTestClient.client.crypto._deviceList.saveIfDirty();
|
return aliceTestClient.client.crypto.deviceList.saveIfDirty();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
||||||
const bobStat = data.trackingStatus['@bob:xyz'];
|
const bobStat = data.trackingStatus['@bob:xyz'];
|
||||||
@@ -235,7 +235,7 @@ describe("DeviceList management:", function() {
|
|||||||
// wait for the client to stop processing the response
|
// wait for the client to stop processing the response
|
||||||
return aliceTestClient.client.downloadKeys(['@bob:xyz']);
|
return aliceTestClient.client.downloadKeys(['@bob:xyz']);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return aliceTestClient.client.crypto._deviceList.saveIfDirty();
|
return aliceTestClient.client.crypto.deviceList.saveIfDirty();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
||||||
const bobStat = data.trackingStatus['@bob:xyz'];
|
const bobStat = data.trackingStatus['@bob:xyz'];
|
||||||
@@ -256,7 +256,7 @@ describe("DeviceList management:", function() {
|
|||||||
// wait for the client to stop processing the response
|
// wait for the client to stop processing the response
|
||||||
return aliceTestClient.client.downloadKeys(['@chris:abc']);
|
return aliceTestClient.client.downloadKeys(['@chris:abc']);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return aliceTestClient.client.crypto._deviceList.saveIfDirty();
|
return aliceTestClient.client.crypto.deviceList.saveIfDirty();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
||||||
const bobStat = data.trackingStatus['@bob:xyz'];
|
const bobStat = data.trackingStatus['@bob:xyz'];
|
||||||
@@ -286,7 +286,7 @@ describe("DeviceList management:", function() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
await aliceTestClient.httpBackend.flush('/keys/query', 1);
|
await aliceTestClient.httpBackend.flush('/keys/query', 1);
|
||||||
await aliceTestClient.client.crypto._deviceList.saveIfDirty();
|
await aliceTestClient.client.crypto.deviceList.saveIfDirty();
|
||||||
|
|
||||||
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
||||||
const bobStat = data.trackingStatus['@bob:xyz'];
|
const bobStat = data.trackingStatus['@bob:xyz'];
|
||||||
@@ -322,7 +322,7 @@ describe("DeviceList management:", function() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await aliceTestClient.flushSync();
|
await aliceTestClient.flushSync();
|
||||||
await aliceTestClient.client.crypto._deviceList.saveIfDirty();
|
await aliceTestClient.client.crypto.deviceList.saveIfDirty();
|
||||||
|
|
||||||
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
||||||
const bobStat = data.trackingStatus['@bob:xyz'];
|
const bobStat = data.trackingStatus['@bob:xyz'];
|
||||||
@@ -358,7 +358,7 @@ describe("DeviceList management:", function() {
|
|||||||
);
|
);
|
||||||
|
|
||||||
await aliceTestClient.flushSync();
|
await aliceTestClient.flushSync();
|
||||||
await aliceTestClient.client.crypto._deviceList.saveIfDirty();
|
await aliceTestClient.client.crypto.deviceList.saveIfDirty();
|
||||||
|
|
||||||
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
aliceTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
||||||
const bobStat = data.trackingStatus['@bob:xyz'];
|
const bobStat = data.trackingStatus['@bob:xyz'];
|
||||||
@@ -379,7 +379,7 @@ describe("DeviceList management:", function() {
|
|||||||
anotherTestClient.httpBackend.when('GET', '/sync').respond(
|
anotherTestClient.httpBackend.when('GET', '/sync').respond(
|
||||||
200, getSyncResponse([]));
|
200, getSyncResponse([]));
|
||||||
await anotherTestClient.flushSync();
|
await anotherTestClient.flushSync();
|
||||||
await anotherTestClient.client.crypto._deviceList.saveIfDirty();
|
await anotherTestClient.client.crypto.deviceList.saveIfDirty();
|
||||||
|
|
||||||
anotherTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
anotherTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
||||||
const bobStat = data.trackingStatus['@bob:xyz'];
|
const bobStat = data.trackingStatus['@bob:xyz'];
|
||||||
|
@@ -159,7 +159,7 @@ function aliDownloadsKeys() {
|
|||||||
// check that the localStorage is updated as we expect (not sure this is
|
// check that the localStorage is updated as we expect (not sure this is
|
||||||
// an integration test, but meh)
|
// an integration test, but meh)
|
||||||
return Promise.all([p1, p2]).then(() => {
|
return Promise.all([p1, p2]).then(() => {
|
||||||
return aliTestClient.client.crypto._deviceList.saveIfDirty();
|
return aliTestClient.client.crypto.deviceList.saveIfDirty();
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
aliTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
aliTestClient.cryptoStore.getEndToEndDeviceData(null, (data) => {
|
||||||
const devices = data.devices[bobUserId];
|
const devices = data.devices[bobUserId];
|
||||||
|
@@ -336,7 +336,7 @@ describe("MatrixClient", function() {
|
|||||||
var b = JSON.parse(JSON.stringify(o));
|
var b = JSON.parse(JSON.stringify(o));
|
||||||
delete(b.signatures);
|
delete(b.signatures);
|
||||||
delete(b.unsigned);
|
delete(b.unsigned);
|
||||||
return client.crypto._olmDevice.sign(anotherjson.stringify(b));
|
return client.crypto.olmDevice.sign(anotherjson.stringify(b));
|
||||||
};
|
};
|
||||||
|
|
||||||
logger.log("Ed25519: " + ed25519key);
|
logger.log("Ed25519: " + ed25519key);
|
||||||
|
@@ -1013,7 +1013,7 @@ describe("megolm", function() {
|
|||||||
event: true,
|
event: true,
|
||||||
});
|
});
|
||||||
event.senderCurve25519Key = testSenderKey;
|
event.senderCurve25519Key = testSenderKey;
|
||||||
return testClient.client.crypto._onRoomKeyEvent(event);
|
return testClient.client.crypto.onRoomKeyEvent(event);
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
const event = testUtils.mkEvent({
|
const event = testUtils.mkEvent({
|
||||||
event: true,
|
event: true,
|
||||||
|
@@ -65,7 +65,7 @@ describe("Crypto", function() {
|
|||||||
'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI';
|
'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI';
|
||||||
device.keys["ed25519:FLIBBLE"] =
|
device.keys["ed25519:FLIBBLE"] =
|
||||||
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
|
'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
|
||||||
client.crypto._deviceList.getDeviceByIdentityKey = () => device;
|
client.crypto.deviceList.getDeviceByIdentityKey = () => device;
|
||||||
|
|
||||||
encryptionInfo = client.getEventEncryptionInfo(event);
|
encryptionInfo = client.getEventEncryptionInfo(event);
|
||||||
expect(encryptionInfo.encrypted).toBeTruthy();
|
expect(encryptionInfo.encrypted).toBeTruthy();
|
||||||
@@ -213,7 +213,7 @@ describe("Crypto", function() {
|
|||||||
|
|
||||||
async function keyshareEventForEvent(event, index) {
|
async function keyshareEventForEvent(event, index) {
|
||||||
const eventContent = event.getWireContent();
|
const eventContent = event.getWireContent();
|
||||||
const key = await aliceClient.crypto._olmDevice
|
const key = await aliceClient.crypto.olmDevice
|
||||||
.getInboundGroupSessionKey(
|
.getInboundGroupSessionKey(
|
||||||
roomId, eventContent.sender_key, eventContent.session_id,
|
roomId, eventContent.sender_key, eventContent.session_id,
|
||||||
index,
|
index,
|
||||||
@@ -285,7 +285,7 @@ describe("Crypto", function() {
|
|||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const bobDecryptor = bobClient.crypto._getRoomDecryptor(
|
const bobDecryptor = bobClient.crypto.getRoomDecryptor(
|
||||||
roomId, olmlib.MEGOLM_ALGORITHM,
|
roomId, olmlib.MEGOLM_ALGORITHM,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -377,7 +377,7 @@ describe("Crypto", function() {
|
|||||||
// key requests get queued until the sync has finished, but we don't
|
// key requests get queued until the sync has finished, but we don't
|
||||||
// let the client set up enough for that to happen, so gut-wrench a bit
|
// let the client set up enough for that to happen, so gut-wrench a bit
|
||||||
// to force it to send now.
|
// to force it to send now.
|
||||||
aliceClient.crypto._outgoingRoomKeyRequestManager.sendQueuedRequests();
|
aliceClient.crypto.outgoingRoomKeyRequestManager.sendQueuedRequests();
|
||||||
jest.runAllTimers();
|
jest.runAllTimers();
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
expect(aliceClient.sendToDevice).toBeCalledTimes(1);
|
expect(aliceClient.sendToDevice).toBeCalledTimes(1);
|
||||||
|
@@ -365,9 +365,9 @@ describe("MegolmDecryption", function() {
|
|||||||
bobClient1.initCrypto(),
|
bobClient1.initCrypto(),
|
||||||
bobClient2.initCrypto(),
|
bobClient2.initCrypto(),
|
||||||
]);
|
]);
|
||||||
const aliceDevice = aliceClient.crypto._olmDevice;
|
const aliceDevice = aliceClient.crypto.olmDevice;
|
||||||
const bobDevice1 = bobClient1.crypto._olmDevice;
|
const bobDevice1 = bobClient1.crypto.olmDevice;
|
||||||
const bobDevice2 = bobClient2.crypto._olmDevice;
|
const bobDevice2 = bobClient2.crypto.olmDevice;
|
||||||
|
|
||||||
const encryptionCfg = {
|
const encryptionCfg = {
|
||||||
"algorithm": "m.megolm.v1.aes-sha2",
|
"algorithm": "m.megolm.v1.aes-sha2",
|
||||||
@@ -404,10 +404,10 @@ describe("MegolmDecryption", function() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
aliceClient.crypto._deviceList.storeDevicesForUser(
|
aliceClient.crypto.deviceList.storeDevicesForUser(
|
||||||
"@bob:example.com", BOB_DEVICES,
|
"@bob:example.com", BOB_DEVICES,
|
||||||
);
|
);
|
||||||
aliceClient.crypto._deviceList.downloadKeys = async function(userIds) {
|
aliceClient.crypto.deviceList.downloadKeys = async function(userIds) {
|
||||||
return this._getDevicesFromStore(userIds);
|
return this._getDevicesFromStore(userIds);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -468,8 +468,8 @@ describe("MegolmDecryption", function() {
|
|||||||
aliceClient.initCrypto(),
|
aliceClient.initCrypto(),
|
||||||
bobClient.initCrypto(),
|
bobClient.initCrypto(),
|
||||||
]);
|
]);
|
||||||
const aliceDevice = aliceClient.crypto._olmDevice;
|
const aliceDevice = aliceClient.crypto.olmDevice;
|
||||||
const bobDevice = bobClient.crypto._olmDevice;
|
const bobDevice = bobClient.crypto.olmDevice;
|
||||||
|
|
||||||
const encryptionCfg = {
|
const encryptionCfg = {
|
||||||
"algorithm": "m.megolm.v1.aes-sha2",
|
"algorithm": "m.megolm.v1.aes-sha2",
|
||||||
@@ -508,10 +508,10 @@ describe("MegolmDecryption", function() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
aliceClient.crypto._deviceList.storeDevicesForUser(
|
aliceClient.crypto.deviceList.storeDevicesForUser(
|
||||||
"@bob:example.com", BOB_DEVICES,
|
"@bob:example.com", BOB_DEVICES,
|
||||||
);
|
);
|
||||||
aliceClient.crypto._deviceList.downloadKeys = async function(userIds) {
|
aliceClient.crypto.deviceList.downloadKeys = async function(userIds) {
|
||||||
return this._getDevicesFromStore(userIds);
|
return this._getDevicesFromStore(userIds);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -561,11 +561,11 @@ describe("MegolmDecryption", function() {
|
|||||||
aliceClient.initCrypto(),
|
aliceClient.initCrypto(),
|
||||||
bobClient.initCrypto(),
|
bobClient.initCrypto(),
|
||||||
]);
|
]);
|
||||||
const bobDevice = bobClient.crypto._olmDevice;
|
const bobDevice = bobClient.crypto.olmDevice;
|
||||||
|
|
||||||
const roomId = "!someroom";
|
const roomId = "!someroom";
|
||||||
|
|
||||||
aliceClient.crypto._onToDeviceEvent(new MatrixEvent({
|
aliceClient.crypto.onToDeviceEvent(new MatrixEvent({
|
||||||
type: "org.matrix.room_key.withheld",
|
type: "org.matrix.room_key.withheld",
|
||||||
sender: "@bob:example.com",
|
sender: "@bob:example.com",
|
||||||
content: {
|
content: {
|
||||||
@@ -605,13 +605,13 @@ describe("MegolmDecryption", function() {
|
|||||||
bobClient.initCrypto(),
|
bobClient.initCrypto(),
|
||||||
]);
|
]);
|
||||||
aliceClient.crypto.downloadKeys = async () => {};
|
aliceClient.crypto.downloadKeys = async () => {};
|
||||||
const bobDevice = bobClient.crypto._olmDevice;
|
const bobDevice = bobClient.crypto.olmDevice;
|
||||||
|
|
||||||
const roomId = "!someroom";
|
const roomId = "!someroom";
|
||||||
|
|
||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
aliceClient.crypto._onToDeviceEvent(new MatrixEvent({
|
aliceClient.crypto.onToDeviceEvent(new MatrixEvent({
|
||||||
type: "org.matrix.room_key.withheld",
|
type: "org.matrix.room_key.withheld",
|
||||||
sender: "@bob:example.com",
|
sender: "@bob:example.com",
|
||||||
content: {
|
content: {
|
||||||
@@ -655,7 +655,7 @@ describe("MegolmDecryption", function() {
|
|||||||
aliceClient.initCrypto(),
|
aliceClient.initCrypto(),
|
||||||
bobClient.initCrypto(),
|
bobClient.initCrypto(),
|
||||||
]);
|
]);
|
||||||
const bobDevice = bobClient.crypto._olmDevice;
|
const bobDevice = bobClient.crypto.olmDevice;
|
||||||
aliceClient.crypto.downloadKeys = async () => {};
|
aliceClient.crypto.downloadKeys = async () => {};
|
||||||
|
|
||||||
const roomId = "!someroom";
|
const roomId = "!someroom";
|
||||||
@@ -663,7 +663,7 @@ describe("MegolmDecryption", function() {
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
|
|
||||||
// pretend we got an event that we can't decrypt
|
// pretend we got an event that we can't decrypt
|
||||||
aliceClient.crypto._onToDeviceEvent(new MatrixEvent({
|
aliceClient.crypto.onToDeviceEvent(new MatrixEvent({
|
||||||
type: "m.room.encrypted",
|
type: "m.room.encrypted",
|
||||||
sender: "@bob:example.com",
|
sender: "@bob:example.com",
|
||||||
content: {
|
content: {
|
||||||
|
@@ -296,7 +296,7 @@ describe("MegolmBackup", function() {
|
|||||||
resolve();
|
resolve();
|
||||||
return Promise.resolve({});
|
return Promise.resolve({});
|
||||||
};
|
};
|
||||||
client.crypto._backupManager.backupGroupSession(
|
client.crypto.backupManager.backupGroupSession(
|
||||||
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
||||||
groupSession.session_id(),
|
groupSession.session_id(),
|
||||||
);
|
);
|
||||||
@@ -478,7 +478,7 @@ describe("MegolmBackup", function() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
client.crypto._backupManager.backupGroupSession(
|
client.crypto.backupManager.backupGroupSession(
|
||||||
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
"F0Q2NmyJNgUVj9DGsb4ZQt3aVxhVcUQhg7+gvW0oyKI",
|
||||||
groupSession.session_id(),
|
groupSession.session_id(),
|
||||||
);
|
);
|
||||||
|
@@ -64,8 +64,8 @@ describe("Cross Signing", function() {
|
|||||||
);
|
);
|
||||||
alice.uploadDeviceSigningKeys = jest.fn(async (auth, keys) => {
|
alice.uploadDeviceSigningKeys = jest.fn(async (auth, keys) => {
|
||||||
await olmlib.verifySignature(
|
await olmlib.verifySignature(
|
||||||
alice.crypto._olmDevice, keys.master_key, "@alice:example.com",
|
alice.crypto.olmDevice, keys.master_key, "@alice:example.com",
|
||||||
"Osborne2", alice.crypto._olmDevice.deviceEd25519Key,
|
"Osborne2", alice.crypto.olmDevice.deviceEd25519Key,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
alice.uploadKeySignatures = async () => {};
|
alice.uploadKeySignatures = async () => {};
|
||||||
@@ -138,7 +138,7 @@ describe("Cross Signing", function() {
|
|||||||
// set Alice's cross-signing key
|
// set Alice's cross-signing key
|
||||||
await resetCrossSigningKeys(alice);
|
await resetCrossSigningKeys(alice);
|
||||||
// Alice downloads Bob's device key
|
// Alice downloads Bob's device key
|
||||||
alice.crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
|
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
||||||
keys: {
|
keys: {
|
||||||
master: {
|
master: {
|
||||||
user_id: "@bob:example.com",
|
user_id: "@bob:example.com",
|
||||||
@@ -203,12 +203,12 @@ describe("Cross Signing", function() {
|
|||||||
alice.uploadKeySignatures = jest.fn(async (content) => {
|
alice.uploadKeySignatures = jest.fn(async (content) => {
|
||||||
try {
|
try {
|
||||||
await olmlib.verifySignature(
|
await olmlib.verifySignature(
|
||||||
alice.crypto._olmDevice,
|
alice.crypto.olmDevice,
|
||||||
content["@alice:example.com"][
|
content["@alice:example.com"][
|
||||||
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
|
"nqOvzeuGWT/sRx3h7+MHoInYj3Uk2LD/unI9kDYcHwk"
|
||||||
],
|
],
|
||||||
"@alice:example.com",
|
"@alice:example.com",
|
||||||
"Osborne2", alice.crypto._olmDevice.deviceEd25519Key,
|
"Osborne2", alice.crypto.olmDevice.deviceEd25519Key,
|
||||||
);
|
);
|
||||||
olmlib.pkVerify(
|
olmlib.pkVerify(
|
||||||
content["@alice:example.com"]["Osborne2"],
|
content["@alice:example.com"]["Osborne2"],
|
||||||
@@ -222,7 +222,7 @@ describe("Cross Signing", function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const deviceInfo = alice.crypto._deviceList._devices["@alice:example.com"]
|
const deviceInfo = alice.crypto.deviceList._devices["@alice:example.com"]
|
||||||
.Osborne2;
|
.Osborne2;
|
||||||
const aliceDevice = {
|
const aliceDevice = {
|
||||||
user_id: "@alice:example.com",
|
user_id: "@alice:example.com",
|
||||||
@@ -230,7 +230,7 @@ describe("Cross Signing", function() {
|
|||||||
};
|
};
|
||||||
aliceDevice.keys = deviceInfo.keys;
|
aliceDevice.keys = deviceInfo.keys;
|
||||||
aliceDevice.algorithms = deviceInfo.algorithms;
|
aliceDevice.algorithms = deviceInfo.algorithms;
|
||||||
await alice.crypto._signObject(aliceDevice);
|
await alice.crypto.signObject(aliceDevice);
|
||||||
olmlib.pkSign(aliceDevice, selfSigningKey, "@alice:example.com");
|
olmlib.pkSign(aliceDevice, selfSigningKey, "@alice:example.com");
|
||||||
|
|
||||||
// feed sync result that includes master key, ssk, device key
|
// feed sync result that includes master key, ssk, device key
|
||||||
@@ -358,7 +358,7 @@ describe("Cross Signing", function() {
|
|||||||
["ed25519:" + bobMasterPubkey]: sskSig,
|
["ed25519:" + bobMasterPubkey]: sskSig,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
alice.crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
|
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
||||||
keys: {
|
keys: {
|
||||||
master: {
|
master: {
|
||||||
user_id: "@bob:example.com",
|
user_id: "@bob:example.com",
|
||||||
@@ -387,7 +387,7 @@ describe("Cross Signing", function() {
|
|||||||
["ed25519:" + bobPubkey]: sig,
|
["ed25519:" + bobPubkey]: sig,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
alice.crypto._deviceList.storeDevicesForUser("@bob:example.com", {
|
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
|
||||||
Dynabook: bobDevice,
|
Dynabook: bobDevice,
|
||||||
});
|
});
|
||||||
// Bob's device key should be TOFU
|
// Bob's device key should be TOFU
|
||||||
@@ -421,8 +421,8 @@ describe("Cross Signing", function() {
|
|||||||
null,
|
null,
|
||||||
aliceKeys,
|
aliceKeys,
|
||||||
);
|
);
|
||||||
alice.crypto._deviceList.startTrackingDeviceList("@bob:example.com");
|
alice.crypto.deviceList.startTrackingDeviceList("@bob:example.com");
|
||||||
alice.crypto._deviceList.stopTrackingAllDeviceLists = () => {};
|
alice.crypto.deviceList.stopTrackingAllDeviceLists = () => {};
|
||||||
alice.uploadDeviceSigningKeys = async () => {};
|
alice.uploadDeviceSigningKeys = async () => {};
|
||||||
alice.uploadKeySignatures = async () => {};
|
alice.uploadKeySignatures = async () => {};
|
||||||
|
|
||||||
@@ -437,14 +437,14 @@ describe("Cross Signing", function() {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const keyChangePromise = new Promise((resolve, reject) => {
|
const keyChangePromise = new Promise((resolve, reject) => {
|
||||||
alice.crypto._deviceList.once("userCrossSigningUpdated", (userId) => {
|
alice.crypto.deviceList.once("userCrossSigningUpdated", (userId) => {
|
||||||
if (userId === "@bob:example.com") {
|
if (userId === "@bob:example.com") {
|
||||||
resolve();
|
resolve();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
const deviceInfo = alice.crypto._deviceList._devices["@alice:example.com"]
|
const deviceInfo = alice.crypto.deviceList._devices["@alice:example.com"]
|
||||||
.Osborne2;
|
.Osborne2;
|
||||||
const aliceDevice = {
|
const aliceDevice = {
|
||||||
user_id: "@alice:example.com",
|
user_id: "@alice:example.com",
|
||||||
@@ -452,7 +452,7 @@ describe("Cross Signing", function() {
|
|||||||
};
|
};
|
||||||
aliceDevice.keys = deviceInfo.keys;
|
aliceDevice.keys = deviceInfo.keys;
|
||||||
aliceDevice.algorithms = deviceInfo.algorithms;
|
aliceDevice.algorithms = deviceInfo.algorithms;
|
||||||
await alice.crypto._signObject(aliceDevice);
|
await alice.crypto.signObject(aliceDevice);
|
||||||
|
|
||||||
const bobOlmAccount = new global.Olm.Account();
|
const bobOlmAccount = new global.Olm.Account();
|
||||||
bobOlmAccount.create();
|
bobOlmAccount.create();
|
||||||
@@ -606,7 +606,7 @@ describe("Cross Signing", function() {
|
|||||||
["ed25519:" + bobMasterPubkey]: sskSig,
|
["ed25519:" + bobMasterPubkey]: sskSig,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
alice.crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
|
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
||||||
keys: {
|
keys: {
|
||||||
master: {
|
master: {
|
||||||
user_id: "@bob:example.com",
|
user_id: "@bob:example.com",
|
||||||
@@ -629,7 +629,7 @@ describe("Cross Signing", function() {
|
|||||||
"ed25519:Dynabook": "someOtherPubkey",
|
"ed25519:Dynabook": "someOtherPubkey",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
alice.crypto._deviceList.storeDevicesForUser("@bob:example.com", {
|
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
|
||||||
Dynabook: bobDevice,
|
Dynabook: bobDevice,
|
||||||
});
|
});
|
||||||
// Bob's device key should be untrusted
|
// Bob's device key should be untrusted
|
||||||
@@ -673,7 +673,7 @@ describe("Cross Signing", function() {
|
|||||||
["ed25519:" + bobMasterPubkey]: sskSig,
|
["ed25519:" + bobMasterPubkey]: sskSig,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
alice.crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
|
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
||||||
keys: {
|
keys: {
|
||||||
master: {
|
master: {
|
||||||
user_id: "@bob:example.com",
|
user_id: "@bob:example.com",
|
||||||
@@ -701,7 +701,7 @@ describe("Cross Signing", function() {
|
|||||||
bobDevice.signatures = {};
|
bobDevice.signatures = {};
|
||||||
bobDevice.signatures["@bob:example.com"] = {};
|
bobDevice.signatures["@bob:example.com"] = {};
|
||||||
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey] = sig;
|
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey] = sig;
|
||||||
alice.crypto._deviceList.storeDevicesForUser("@bob:example.com", {
|
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
|
||||||
Dynabook: bobDevice,
|
Dynabook: bobDevice,
|
||||||
});
|
});
|
||||||
// Alice verifies Bob's SSK
|
// Alice verifies Bob's SSK
|
||||||
@@ -733,7 +733,7 @@ describe("Cross Signing", function() {
|
|||||||
["ed25519:" + bobMasterPubkey2]: sskSig2,
|
["ed25519:" + bobMasterPubkey2]: sskSig2,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
alice.crypto._deviceList.storeCrossSigningForUser("@bob:example.com", {
|
alice.crypto.deviceList.storeCrossSigningForUser("@bob:example.com", {
|
||||||
keys: {
|
keys: {
|
||||||
master: {
|
master: {
|
||||||
user_id: "@bob:example.com",
|
user_id: "@bob:example.com",
|
||||||
@@ -770,7 +770,7 @@ describe("Cross Signing", function() {
|
|||||||
// Alice gets new signature for device
|
// Alice gets new signature for device
|
||||||
const sig2 = bobSigning2.sign(bobDeviceString);
|
const sig2 = bobSigning2.sign(bobDeviceString);
|
||||||
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey2] = sig2;
|
bobDevice.signatures["@bob:example.com"]["ed25519:" + bobPubkey2] = sig2;
|
||||||
alice.crypto._deviceList.storeDevicesForUser("@bob:example.com", {
|
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
|
||||||
Dynabook: bobDevice,
|
Dynabook: bobDevice,
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -805,20 +805,20 @@ describe("Cross Signing", function() {
|
|||||||
bob.uploadKeySignatures = async () => {};
|
bob.uploadKeySignatures = async () => {};
|
||||||
// set Bob's cross-signing key
|
// set Bob's cross-signing key
|
||||||
await resetCrossSigningKeys(bob);
|
await resetCrossSigningKeys(bob);
|
||||||
alice.crypto._deviceList.storeDevicesForUser("@bob:example.com", {
|
alice.crypto.deviceList.storeDevicesForUser("@bob:example.com", {
|
||||||
Dynabook: {
|
Dynabook: {
|
||||||
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
|
algorithms: ["m.olm.curve25519-aes-sha256", "m.megolm.v1.aes-sha"],
|
||||||
keys: {
|
keys: {
|
||||||
"curve25519:Dynabook": bob.crypto._olmDevice.deviceCurve25519Key,
|
"curve25519:Dynabook": bob.crypto.olmDevice.deviceCurve25519Key,
|
||||||
"ed25519:Dynabook": bob.crypto._olmDevice.deviceEd25519Key,
|
"ed25519:Dynabook": bob.crypto.olmDevice.deviceEd25519Key,
|
||||||
},
|
},
|
||||||
verified: 1,
|
verified: 1,
|
||||||
known: true,
|
known: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
alice.crypto._deviceList.storeCrossSigningForUser(
|
alice.crypto.deviceList.storeCrossSigningForUser(
|
||||||
"@bob:example.com",
|
"@bob:example.com",
|
||||||
bob.crypto._crossSigningInfo.toStorage(),
|
bob.crypto.crossSigningInfo.toStorage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
alice.uploadDeviceSigningKeys = async () => {};
|
alice.uploadDeviceSigningKeys = async () => {};
|
||||||
@@ -838,7 +838,7 @@ describe("Cross Signing", function() {
|
|||||||
expect(bobTrust.isTofu()).toBeTruthy();
|
expect(bobTrust.isTofu()).toBeTruthy();
|
||||||
|
|
||||||
// "forget" that Bob is trusted
|
// "forget" that Bob is trusted
|
||||||
delete alice.crypto._deviceList._crossSigningInfo["@bob:example.com"]
|
delete alice.crypto.deviceList.crossSigningInfo["@bob:example.com"]
|
||||||
.keys.master.signatures["@alice:example.com"];
|
.keys.master.signatures["@alice:example.com"];
|
||||||
|
|
||||||
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
|
const bobTrust2 = alice.checkUserTrust("@bob:example.com");
|
||||||
@@ -848,7 +848,7 @@ describe("Cross Signing", function() {
|
|||||||
upgradePromise = new Promise((resolve) => {
|
upgradePromise = new Promise((resolve) => {
|
||||||
upgradeResolveFunc = resolve;
|
upgradeResolveFunc = resolve;
|
||||||
});
|
});
|
||||||
alice.crypto._deviceList.emit("userCrossSigningUpdated", "@bob:example.com");
|
alice.crypto.deviceList.emit("userCrossSigningUpdated", "@bob:example.com");
|
||||||
await new Promise((resolve) => {
|
await new Promise((resolve) => {
|
||||||
alice.crypto.on("userTrustStatusChanged", resolve);
|
alice.crypto.on("userTrustStatusChanged", resolve);
|
||||||
});
|
});
|
||||||
|
@@ -8,22 +8,22 @@ export async function resetCrossSigningKeys(client, {
|
|||||||
} = {}) {
|
} = {}) {
|
||||||
const crypto = client.crypto;
|
const crypto = client.crypto;
|
||||||
|
|
||||||
const oldKeys = Object.assign({}, crypto._crossSigningInfo.keys);
|
const oldKeys = Object.assign({}, crypto.crossSigningInfo.keys);
|
||||||
try {
|
try {
|
||||||
await crypto._crossSigningInfo.resetKeys(level);
|
await crypto.crossSigningInfo.resetKeys(level);
|
||||||
await crypto._signObject(crypto._crossSigningInfo.keys.master);
|
await crypto._signObject(crypto.crossSigningInfo.keys.master);
|
||||||
// write a copy locally so we know these are trusted keys
|
// write a copy locally so we know these are trusted keys
|
||||||
await crypto._cryptoStore.doTxn(
|
await crypto._cryptoStore.doTxn(
|
||||||
'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT],
|
'readwrite', [IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
crypto._cryptoStore.storeCrossSigningKeys(
|
crypto._cryptoStore.storeCrossSigningKeys(
|
||||||
txn, crypto._crossSigningInfo.keys);
|
txn, crypto.crossSigningInfo.keys);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// If anything failed here, revert the keys so we know to try again from the start
|
// If anything failed here, revert the keys so we know to try again from the start
|
||||||
// next time.
|
// next time.
|
||||||
crypto._crossSigningInfo.keys = oldKeys;
|
crypto.crossSigningInfo.keys = oldKeys;
|
||||||
throw e;
|
throw e;
|
||||||
}
|
}
|
||||||
crypto._baseApis.emit("crossSigning.keysChanged", {});
|
crypto._baseApis.emit("crossSigning.keysChanged", {});
|
||||||
|
@@ -99,11 +99,11 @@ describe("Secrets", function() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
alice.crypto._crossSigningInfo.setKeys({
|
alice.crypto.crossSigningInfo.setKeys({
|
||||||
master: signingkeyInfo,
|
master: signingkeyInfo,
|
||||||
});
|
});
|
||||||
|
|
||||||
const secretStorage = alice.crypto._secretStorage;
|
const secretStorage = alice.crypto.secretStorage;
|
||||||
|
|
||||||
alice.setAccountData = async function(eventType, contents, callback) {
|
alice.setAccountData = async function(eventType, contents, callback) {
|
||||||
alice.store.storeAccountDataEvents([
|
alice.store.storeAccountDataEvents([
|
||||||
@@ -120,7 +120,7 @@ describe("Secrets", function() {
|
|||||||
const keyAccountData = {
|
const keyAccountData = {
|
||||||
algorithm: SECRET_STORAGE_ALGORITHM_V1_AES,
|
algorithm: SECRET_STORAGE_ALGORITHM_V1_AES,
|
||||||
};
|
};
|
||||||
await alice.crypto._crossSigningInfo.signObject(keyAccountData, 'master');
|
await alice.crypto.crossSigningInfo.signObject(keyAccountData, 'master');
|
||||||
|
|
||||||
alice.store.storeAccountDataEvents([
|
alice.store.storeAccountDataEvents([
|
||||||
new MatrixEvent({
|
new MatrixEvent({
|
||||||
@@ -234,11 +234,11 @@ describe("Secrets", function() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const vaxDevice = vax.client.crypto._olmDevice;
|
const vaxDevice = vax.client.crypto.olmDevice;
|
||||||
const osborne2Device = osborne2.client.crypto._olmDevice;
|
const osborne2Device = osborne2.client.crypto.olmDevice;
|
||||||
const secretStorage = osborne2.client.crypto._secretStorage;
|
const secretStorage = osborne2.client.crypto.secretStorage;
|
||||||
|
|
||||||
osborne2.client.crypto._deviceList.storeDevicesForUser("@alice:example.com", {
|
osborne2.client.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
|
||||||
"VAX": {
|
"VAX": {
|
||||||
user_id: "@alice:example.com",
|
user_id: "@alice:example.com",
|
||||||
device_id: "VAX",
|
device_id: "VAX",
|
||||||
@@ -249,7 +249,7 @@ describe("Secrets", function() {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
vax.client.crypto._deviceList.storeDevicesForUser("@alice:example.com", {
|
vax.client.crypto.deviceList.storeDevicesForUser("@alice:example.com", {
|
||||||
"Osborne2": {
|
"Osborne2": {
|
||||||
user_id: "@alice:example.com",
|
user_id: "@alice:example.com",
|
||||||
device_id: "Osborne2",
|
device_id: "Osborne2",
|
||||||
@@ -265,7 +265,7 @@ describe("Secrets", function() {
|
|||||||
const otks = (await osborne2Device.getOneTimeKeys()).curve25519;
|
const otks = (await osborne2Device.getOneTimeKeys()).curve25519;
|
||||||
await osborne2Device.markKeysAsPublished();
|
await osborne2Device.markKeysAsPublished();
|
||||||
|
|
||||||
await vax.client.crypto._olmDevice.createOutboundSession(
|
await vax.client.crypto.olmDevice.createOutboundSession(
|
||||||
osborne2Device.deviceCurve25519Key,
|
osborne2Device.deviceCurve25519Key,
|
||||||
Object.values(otks)[0],
|
Object.values(otks)[0],
|
||||||
);
|
);
|
||||||
@@ -334,8 +334,8 @@ describe("Secrets", function() {
|
|||||||
createSecretStorageKey,
|
createSecretStorageKey,
|
||||||
});
|
});
|
||||||
|
|
||||||
const crossSigning = bob.crypto._crossSigningInfo;
|
const crossSigning = bob.crypto.crossSigningInfo;
|
||||||
const secretStorage = bob.crypto._secretStorage;
|
const secretStorage = bob.crypto.secretStorage;
|
||||||
|
|
||||||
expect(crossSigning.getId()).toBeTruthy();
|
expect(crossSigning.getId()).toBeTruthy();
|
||||||
expect(await crossSigning.isStoredInSecretStorage(secretStorage))
|
expect(await crossSigning.isStoredInSecretStorage(secretStorage))
|
||||||
@@ -376,10 +376,10 @@ describe("Secrets", function() {
|
|||||||
]);
|
]);
|
||||||
this.emit("accountData", event);
|
this.emit("accountData", event);
|
||||||
};
|
};
|
||||||
bob.crypto._backupManager.checkKeyBackup = async () => {};
|
bob.crypto.backupManager.checkKeyBackup = async () => {};
|
||||||
|
|
||||||
const crossSigning = bob.crypto._crossSigningInfo;
|
const crossSigning = bob.crypto.crossSigningInfo;
|
||||||
const secretStorage = bob.crypto._secretStorage;
|
const secretStorage = bob.crypto.secretStorage;
|
||||||
|
|
||||||
// Set up cross-signing keys from scratch with specific storage key
|
// Set up cross-signing keys from scratch with specific storage key
|
||||||
await bob.bootstrapCrossSigning({
|
await bob.bootstrapCrossSigning({
|
||||||
@@ -394,7 +394,7 @@ describe("Secrets", function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Clear local cross-signing keys and read from secret storage
|
// Clear local cross-signing keys and read from secret storage
|
||||||
bob.crypto._deviceList.storeCrossSigningForUser(
|
bob.crypto.deviceList.storeCrossSigningForUser(
|
||||||
"@bob:example.com",
|
"@bob:example.com",
|
||||||
crossSigning.toStorage(),
|
crossSigning.toStorage(),
|
||||||
);
|
);
|
||||||
@@ -479,7 +479,7 @@ describe("Secrets", function() {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
alice.crypto._deviceList.storeCrossSigningForUser("@alice:example.com", {
|
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
|
||||||
keys: {
|
keys: {
|
||||||
master: {
|
master: {
|
||||||
user_id: "@alice:example.com",
|
user_id: "@alice:example.com",
|
||||||
@@ -619,7 +619,7 @@ describe("Secrets", function() {
|
|||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
alice.crypto._deviceList.storeCrossSigningForUser("@alice:example.com", {
|
alice.crypto.deviceList.storeCrossSigningForUser("@alice:example.com", {
|
||||||
keys: {
|
keys: {
|
||||||
master: {
|
master: {
|
||||||
user_id: "@alice:example.com",
|
user_id: "@alice:example.com",
|
||||||
|
@@ -49,7 +49,7 @@ describe("verification request integration tests with crypto layer", function()
|
|||||||
verificationMethods: [verificationMethods.SAS],
|
verificationMethods: [verificationMethods.SAS],
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
alice.client.crypto._deviceList.getRawStoredDevicesForUser = function() {
|
alice.client.crypto.deviceList.getRawStoredDevicesForUser = function() {
|
||||||
return {
|
return {
|
||||||
Dynabook: {
|
Dynabook: {
|
||||||
keys: {
|
keys: {
|
||||||
|
@@ -87,8 +87,8 @@ describe("SAS verification", function() {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
const aliceDevice = alice.client.crypto._olmDevice;
|
const aliceDevice = alice.client.crypto.olmDevice;
|
||||||
const bobDevice = bob.client.crypto._olmDevice;
|
const bobDevice = bob.client.crypto.olmDevice;
|
||||||
|
|
||||||
ALICE_DEVICES = {
|
ALICE_DEVICES = {
|
||||||
Osborne2: {
|
Osborne2: {
|
||||||
@@ -114,14 +114,14 @@ describe("SAS verification", function() {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
alice.client.crypto._deviceList.storeDevicesForUser(
|
alice.client.crypto.deviceList.storeDevicesForUser(
|
||||||
"@bob:example.com", BOB_DEVICES,
|
"@bob:example.com", BOB_DEVICES,
|
||||||
);
|
);
|
||||||
alice.client.downloadKeys = () => {
|
alice.client.downloadKeys = () => {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
};
|
};
|
||||||
|
|
||||||
bob.client.crypto._deviceList.storeDevicesForUser(
|
bob.client.crypto.deviceList.storeDevicesForUser(
|
||||||
"@alice:example.com", ALICE_DEVICES,
|
"@alice:example.com", ALICE_DEVICES,
|
||||||
);
|
);
|
||||||
bob.client.downloadKeys = () => {
|
bob.client.downloadKeys = () => {
|
||||||
@@ -296,9 +296,9 @@ describe("SAS verification", function() {
|
|||||||
|
|
||||||
await resetCrossSigningKeys(bob.client);
|
await resetCrossSigningKeys(bob.client);
|
||||||
|
|
||||||
bob.client.crypto._deviceList.storeCrossSigningForUser(
|
bob.client.crypto.deviceList.storeCrossSigningForUser(
|
||||||
"@alice:example.com", {
|
"@alice:example.com", {
|
||||||
keys: alice.client.crypto._crossSigningInfo.keys,
|
keys: alice.client.crypto.crossSigningInfo.keys,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@@ -48,7 +48,7 @@ import {
|
|||||||
retryNetworkOperation,
|
retryNetworkOperation,
|
||||||
} from "./http-api";
|
} from "./http-api";
|
||||||
import { Crypto, fixBackupKey, IBootstrapCrossSigningOpts, isCryptoAvailable } from './crypto';
|
import { Crypto, fixBackupKey, IBootstrapCrossSigningOpts, isCryptoAvailable } from './crypto';
|
||||||
import { DeviceInfo } from "./crypto/deviceinfo";
|
import { DeviceInfo, IDevice } from "./crypto/deviceinfo";
|
||||||
import { decodeRecoveryKey } from './crypto/recoverykey';
|
import { decodeRecoveryKey } from './crypto/recoverykey';
|
||||||
import { keyFromAuthData } from './crypto/key_passphrase';
|
import { keyFromAuthData } from './crypto/key_passphrase';
|
||||||
import { User } from "./models/user";
|
import { User } from "./models/user";
|
||||||
@@ -64,7 +64,7 @@ import {
|
|||||||
import { IIdentityServerProvider } from "./@types/IIdentityServerProvider";
|
import { IIdentityServerProvider } from "./@types/IIdentityServerProvider";
|
||||||
import type Request from "request";
|
import type Request from "request";
|
||||||
import { MatrixScheduler } from "./scheduler";
|
import { MatrixScheduler } from "./scheduler";
|
||||||
import { ICryptoCallbacks, IDeviceTrustLevel, ISecretStorageKeyInfo, NotificationCountType } from "./matrix";
|
import { ICryptoCallbacks, ISecretStorageKeyInfo, NotificationCountType } from "./matrix";
|
||||||
import { MemoryCryptoStore } from "./crypto/store/memory-crypto-store";
|
import { MemoryCryptoStore } from "./crypto/store/memory-crypto-store";
|
||||||
import { LocalStorageCryptoStore } from "./crypto/store/localStorage-crypto-store";
|
import { LocalStorageCryptoStore } from "./crypto/store/localStorage-crypto-store";
|
||||||
import { IndexedDBCryptoStore } from "./crypto/store/indexeddb-crypto-store";
|
import { IndexedDBCryptoStore } from "./crypto/store/indexeddb-crypto-store";
|
||||||
@@ -85,7 +85,7 @@ import {
|
|||||||
IRecoveryKey,
|
IRecoveryKey,
|
||||||
ISecretStorageKey,
|
ISecretStorageKey,
|
||||||
} from "./crypto/api";
|
} from "./crypto/api";
|
||||||
import { CrossSigningInfo, UserTrustLevel } from "./crypto/CrossSigning";
|
import { CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from "./crypto/CrossSigning";
|
||||||
import { Room } from "./models/room";
|
import { Room } from "./models/room";
|
||||||
import {
|
import {
|
||||||
ICreateRoomOpts,
|
ICreateRoomOpts,
|
||||||
@@ -1265,7 +1265,7 @@ export class MatrixClient extends EventEmitter {
|
|||||||
public downloadKeys(
|
public downloadKeys(
|
||||||
userIds: string[],
|
userIds: string[],
|
||||||
forceDownload?: boolean,
|
forceDownload?: boolean,
|
||||||
): Promise<Record<string, Record<string, DeviceInfo>>> {
|
): Promise<Record<string, Record<string, IDevice>>> {
|
||||||
if (!this.crypto) {
|
if (!this.crypto) {
|
||||||
return Promise.reject(new Error("End-to-end encryption disabled"));
|
return Promise.reject(new Error("End-to-end encryption disabled"));
|
||||||
}
|
}
|
||||||
@@ -1571,9 +1571,9 @@ export class MatrixClient extends EventEmitter {
|
|||||||
* @param {string} userId The ID of the user whose devices is to be checked.
|
* @param {string} userId The ID of the user whose devices is to be checked.
|
||||||
* @param {string} deviceId The ID of the device to check
|
* @param {string} deviceId The ID of the device to check
|
||||||
*
|
*
|
||||||
* @returns {IDeviceTrustLevel}
|
* @returns {DeviceTrustLevel}
|
||||||
*/
|
*/
|
||||||
public checkDeviceTrust(userId: string, deviceId: string): IDeviceTrustLevel {
|
public checkDeviceTrust(userId: string, deviceId: string): DeviceTrustLevel {
|
||||||
if (!this.crypto) {
|
if (!this.crypto) {
|
||||||
throw new Error("End-to-end encryption disabled");
|
throw new Error("End-to-end encryption disabled");
|
||||||
}
|
}
|
||||||
@@ -1948,7 +1948,7 @@ export class MatrixClient extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @return {Promise<module:crypto/deviceinfo?>}
|
* @return {Promise<module:crypto/deviceinfo?>}
|
||||||
*/
|
*/
|
||||||
public getEventSenderDeviceInfo(event: MatrixEvent): Promise<DeviceInfo> {
|
public async getEventSenderDeviceInfo(event: MatrixEvent): Promise<DeviceInfo> {
|
||||||
if (!this.crypto) {
|
if (!this.crypto) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -2488,15 +2488,13 @@ export class MatrixClient extends EventEmitter {
|
|||||||
targetRoomId: string,
|
targetRoomId: string,
|
||||||
targetSessionId: string,
|
targetSessionId: string,
|
||||||
backupInfo: IKeyBackupVersion,
|
backupInfo: IKeyBackupVersion,
|
||||||
opts: IKeyBackupRestoreOpts,
|
opts?: IKeyBackupRestoreOpts,
|
||||||
): Promise<IKeyBackupRestoreResult> {
|
): Promise<IKeyBackupRestoreResult> {
|
||||||
const privKey = await this.crypto.getSessionBackupPrivateKey();
|
const privKey = await this.crypto.getSessionBackupPrivateKey();
|
||||||
if (!privKey) {
|
if (!privKey) {
|
||||||
throw new Error("Couldn't get key");
|
throw new Error("Couldn't get key");
|
||||||
}
|
}
|
||||||
return this.restoreKeyBackup(
|
return this.restoreKeyBackup(privKey, targetRoomId, targetSessionId, backupInfo, opts);
|
||||||
privKey, targetRoomId, targetSessionId, backupInfo, opts,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async restoreKeyBackup(
|
private async restoreKeyBackup(
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2019 New Vector Ltd
|
Copyright 2019 - 2021 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -20,22 +19,43 @@ limitations under the License.
|
|||||||
* @module crypto/CrossSigning
|
* @module crypto/CrossSigning
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { decodeBase64, encodeBase64, pkSign, pkVerify } from './olmlib';
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
|
import { decodeBase64, encodeBase64, pkSign, pkVerify } from './olmlib';
|
||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
import { IndexedDBCryptoStore } from '../crypto/store/indexeddb-crypto-store';
|
import { IndexedDBCryptoStore } from '../crypto/store/indexeddb-crypto-store';
|
||||||
import { decryptAES, encryptAES } from './aes';
|
import { decryptAES, encryptAES } from './aes';
|
||||||
|
import { PkSigning } from "@matrix-org/olm";
|
||||||
|
import { DeviceInfo } from "./deviceinfo";
|
||||||
|
import { SecretStorage } from "./SecretStorage";
|
||||||
|
import { CryptoStore, MatrixClient } from "../client";
|
||||||
|
import { OlmDevice } from "./OlmDevice";
|
||||||
|
import { ICryptoCallbacks } from "../matrix";
|
||||||
|
|
||||||
const KEY_REQUEST_TIMEOUT_MS = 1000 * 60;
|
const KEY_REQUEST_TIMEOUT_MS = 1000 * 60;
|
||||||
|
|
||||||
function publicKeyFromKeyInfo(keyInfo) {
|
function publicKeyFromKeyInfo(keyInfo: any): any { // TODO types
|
||||||
// `keys` is an object with { [`ed25519:${pubKey}`]: pubKey }
|
// `keys` is an object with { [`ed25519:${pubKey}`]: pubKey }
|
||||||
// We assume only a single key, and we want the bare form without type
|
// We assume only a single key, and we want the bare form without type
|
||||||
// prefix, so we select the values.
|
// prefix, so we select the values.
|
||||||
return Object.values(keyInfo.keys)[0];
|
return Object.values(keyInfo.keys)[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ICacheCallbacks {
|
||||||
|
getCrossSigningKeyCache?(type: string, expectedPublicKey?: string): Promise<Uint8Array>;
|
||||||
|
storeCrossSigningKeyCache?(type: string, key: Uint8Array): Promise<void>;
|
||||||
|
}
|
||||||
|
|
||||||
export class CrossSigningInfo extends EventEmitter {
|
export class CrossSigningInfo extends EventEmitter {
|
||||||
|
public keys: Record<string, any> = {}; // TODO types
|
||||||
|
public firstUse = true;
|
||||||
|
// This tracks whether we've ever verified this user with any identity.
|
||||||
|
// When you verify a user, any devices online at the time that receive
|
||||||
|
// the verifying signature via the homeserver will latch this to true
|
||||||
|
// and can use it in the future to detect cases where the user has
|
||||||
|
// become unverified later for any reason.
|
||||||
|
private crossSigningVerifiedBefore = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Information about a user's cross-signing keys
|
* Information about a user's cross-signing keys
|
||||||
*
|
*
|
||||||
@@ -46,27 +66,15 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
* Requires getCrossSigningKey and saveCrossSigningKeys
|
* Requires getCrossSigningKey and saveCrossSigningKeys
|
||||||
* @param {object} cacheCallbacks Callbacks used to interact with the cache
|
* @param {object} cacheCallbacks Callbacks used to interact with the cache
|
||||||
*/
|
*/
|
||||||
constructor(userId, callbacks, cacheCallbacks) {
|
constructor(
|
||||||
|
public readonly userId: string,
|
||||||
|
private callbacks: ICryptoCallbacks = {},
|
||||||
|
private cacheCallbacks: ICacheCallbacks = {},
|
||||||
|
) {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
// you can't change the userId
|
|
||||||
Object.defineProperty(this, 'userId', {
|
|
||||||
enumerable: true,
|
|
||||||
value: userId,
|
|
||||||
});
|
|
||||||
this._callbacks = callbacks || {};
|
|
||||||
this._cacheCallbacks = cacheCallbacks || {};
|
|
||||||
this.keys = {};
|
|
||||||
this.firstUse = true;
|
|
||||||
// This tracks whether we've ever verified this user with any identity.
|
|
||||||
// When you verify a user, any devices online at the time that receive
|
|
||||||
// the verifying signature via the homeserver will latch this to true
|
|
||||||
// and can use it in the future to detect cases where the user has
|
|
||||||
// become unverifed later for any reason.
|
|
||||||
this.crossSigningVerifiedBefore = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static fromStorage(obj, userId) {
|
public static fromStorage(obj: object, userId: string): CrossSigningInfo {
|
||||||
const res = new CrossSigningInfo(userId);
|
const res = new CrossSigningInfo(userId);
|
||||||
for (const prop in obj) {
|
for (const prop in obj) {
|
||||||
if (obj.hasOwnProperty(prop)) {
|
if (obj.hasOwnProperty(prop)) {
|
||||||
@@ -76,7 +84,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
toStorage() {
|
public toStorage(): object {
|
||||||
return {
|
return {
|
||||||
keys: this.keys,
|
keys: this.keys,
|
||||||
firstUse: this.firstUse,
|
firstUse: this.firstUse,
|
||||||
@@ -92,10 +100,10 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
* the stored public key for the given key type.
|
* the stored public key for the given key type.
|
||||||
* @returns {Array} An array with [ public key, Olm.PkSigning ]
|
* @returns {Array} An array with [ public key, Olm.PkSigning ]
|
||||||
*/
|
*/
|
||||||
async getCrossSigningKey(type, expectedPubkey) {
|
public async getCrossSigningKey(type: string, expectedPubkey?: string): Promise<[string, PkSigning]> {
|
||||||
const shouldCache = ["master", "self_signing", "user_signing"].indexOf(type) >= 0;
|
const shouldCache = ["master", "self_signing", "user_signing"].indexOf(type) >= 0;
|
||||||
|
|
||||||
if (!this._callbacks.getCrossSigningKey) {
|
if (!this.callbacks.getCrossSigningKey) {
|
||||||
throw new Error("No getCrossSigningKey callback supplied");
|
throw new Error("No getCrossSigningKey callback supplied");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,7 +111,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
expectedPubkey = this.getId(type);
|
expectedPubkey = this.getId(type);
|
||||||
}
|
}
|
||||||
|
|
||||||
function validateKey(key) {
|
function validateKey(key: Uint8Array): [string, PkSigning] {
|
||||||
if (!key) return;
|
if (!key) return;
|
||||||
const signing = new global.Olm.PkSigning();
|
const signing = new global.Olm.PkSigning();
|
||||||
const gotPubkey = signing.init_with_seed(key);
|
const gotPubkey = signing.init_with_seed(key);
|
||||||
@@ -114,9 +122,8 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let privkey;
|
let privkey;
|
||||||
if (this._cacheCallbacks.getCrossSigningKeyCache && shouldCache) {
|
if (this.cacheCallbacks.getCrossSigningKeyCache && shouldCache) {
|
||||||
privkey = await this._cacheCallbacks
|
privkey = await this.cacheCallbacks.getCrossSigningKeyCache(type, expectedPubkey);
|
||||||
.getCrossSigningKeyCache(type, expectedPubkey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const cacheresult = validateKey(privkey);
|
const cacheresult = validateKey(privkey);
|
||||||
@@ -124,11 +131,11 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
return cacheresult;
|
return cacheresult;
|
||||||
}
|
}
|
||||||
|
|
||||||
privkey = await this._callbacks.getCrossSigningKey(type, expectedPubkey);
|
privkey = await this.callbacks.getCrossSigningKey(type, expectedPubkey);
|
||||||
const result = validateKey(privkey);
|
const result = validateKey(privkey);
|
||||||
if (result) {
|
if (result) {
|
||||||
if (this._cacheCallbacks.storeCrossSigningKeyCache && shouldCache) {
|
if (this.cacheCallbacks.storeCrossSigningKeyCache && shouldCache) {
|
||||||
await this._cacheCallbacks.storeCrossSigningKeyCache(type, privkey);
|
await this.cacheCallbacks.storeCrossSigningKeyCache(type, privkey);
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@@ -156,10 +163,9 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
* with, or null if it is not present or not encrypted with a trusted
|
* with, or null if it is not present or not encrypted with a trusted
|
||||||
* key
|
* key
|
||||||
*/
|
*/
|
||||||
async isStoredInSecretStorage(secretStorage) {
|
public async isStoredInSecretStorage(secretStorage: SecretStorage): Promise<Record<string, object>> {
|
||||||
// check what SSSS keys have encrypted the master key (if any)
|
// check what SSSS keys have encrypted the master key (if any)
|
||||||
const stored =
|
const stored = await secretStorage.isStored("m.cross_signing.master", false) || {};
|
||||||
await secretStorage.isStored("m.cross_signing.master", false) || {};
|
|
||||||
// then check which of those SSSS keys have also encrypted the SSK and USK
|
// then check which of those SSSS keys have also encrypted the SSK and USK
|
||||||
function intersect(s) {
|
function intersect(s) {
|
||||||
for (const k of Object.keys(stored)) {
|
for (const k of Object.keys(stored)) {
|
||||||
@@ -169,9 +175,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const type of ["self_signing", "user_signing"]) {
|
for (const type of ["self_signing", "user_signing"]) {
|
||||||
intersect(
|
intersect(await secretStorage.isStored(`m.cross_signing.${type}`, false) || {});
|
||||||
await secretStorage.isStored(`m.cross_signing.${type}`, false) || {},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
return Object.keys(stored).length ? stored : null;
|
return Object.keys(stored).length ? stored : null;
|
||||||
}
|
}
|
||||||
@@ -184,7 +188,10 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
* @param {Map} keys The keys to store
|
* @param {Map} keys The keys to store
|
||||||
* @param {SecretStorage} secretStorage The secret store using account data
|
* @param {SecretStorage} secretStorage The secret store using account data
|
||||||
*/
|
*/
|
||||||
static async storeInSecretStorage(keys, secretStorage) {
|
public static async storeInSecretStorage(
|
||||||
|
keys: Map<string, Uint8Array>,
|
||||||
|
secretStorage: SecretStorage,
|
||||||
|
): Promise<void> {
|
||||||
for (const [type, privateKey] of keys) {
|
for (const [type, privateKey] of keys) {
|
||||||
const encodedKey = encodeBase64(privateKey);
|
const encodedKey = encodeBase64(privateKey);
|
||||||
await secretStorage.store(`m.cross_signing.${type}`, encodedKey);
|
await secretStorage.store(`m.cross_signing.${type}`, encodedKey);
|
||||||
@@ -200,7 +207,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
* @param {SecretStorage} secretStorage The secret store using account data
|
* @param {SecretStorage} secretStorage The secret store using account data
|
||||||
* @return {Uint8Array} The private key
|
* @return {Uint8Array} The private key
|
||||||
*/
|
*/
|
||||||
static async getFromSecretStorage(type, secretStorage) {
|
public static async getFromSecretStorage(type: string, secretStorage: SecretStorage): Promise<Uint8Array> {
|
||||||
const encodedKey = await secretStorage.get(`m.cross_signing.${type}`);
|
const encodedKey = await secretStorage.get(`m.cross_signing.${type}`);
|
||||||
if (!encodedKey) {
|
if (!encodedKey) {
|
||||||
return null;
|
return null;
|
||||||
@@ -215,8 +222,8 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
* "self_signing", or "user_signing". Optional, will check all by default.
|
* "self_signing", or "user_signing". Optional, will check all by default.
|
||||||
* @returns {boolean} True if all keys are stored in the local cache.
|
* @returns {boolean} True if all keys are stored in the local cache.
|
||||||
*/
|
*/
|
||||||
async isStoredInKeyCache(type) {
|
public async isStoredInKeyCache(type?: string): Promise<boolean> {
|
||||||
const cacheCallbacks = this._cacheCallbacks;
|
const cacheCallbacks = this.cacheCallbacks;
|
||||||
if (!cacheCallbacks) return false;
|
if (!cacheCallbacks) return false;
|
||||||
const types = type ? [type] : ["master", "self_signing", "user_signing"];
|
const types = type ? [type] : ["master", "self_signing", "user_signing"];
|
||||||
for (const t of types) {
|
for (const t of types) {
|
||||||
@@ -232,9 +239,9 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @returns {Map} A map from key type (string) to private key (Uint8Array)
|
* @returns {Map} A map from key type (string) to private key (Uint8Array)
|
||||||
*/
|
*/
|
||||||
async getCrossSigningKeysFromCache() {
|
public async getCrossSigningKeysFromCache(): Promise<Map<string, Uint8Array>> {
|
||||||
const keys = new Map();
|
const keys = new Map();
|
||||||
const cacheCallbacks = this._cacheCallbacks;
|
const cacheCallbacks = this.cacheCallbacks;
|
||||||
if (!cacheCallbacks) return keys;
|
if (!cacheCallbacks) return keys;
|
||||||
for (const type of ["master", "self_signing", "user_signing"]) {
|
for (const type of ["master", "self_signing", "user_signing"]) {
|
||||||
const privKey = await cacheCallbacks.getCrossSigningKeyCache(type);
|
const privKey = await cacheCallbacks.getCrossSigningKeyCache(type);
|
||||||
@@ -255,8 +262,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @return {string} the ID
|
* @return {string} the ID
|
||||||
*/
|
*/
|
||||||
getId(type) {
|
public getId(type = "master"): string {
|
||||||
type = type || "master";
|
|
||||||
if (!this.keys[type]) return null;
|
if (!this.keys[type]) return null;
|
||||||
const keyInfo = this.keys[type];
|
const keyInfo = this.keys[type];
|
||||||
return publicKeyFromKeyInfo(keyInfo);
|
return publicKeyFromKeyInfo(keyInfo);
|
||||||
@@ -269,8 +275,8 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @param {CrossSigningLevel} level The key types to reset
|
* @param {CrossSigningLevel} level The key types to reset
|
||||||
*/
|
*/
|
||||||
async resetKeys(level) {
|
public async resetKeys(level?: CrossSigningLevel): Promise<void> {
|
||||||
if (!this._callbacks.saveCrossSigningKeys) {
|
if (!this.callbacks.saveCrossSigningKeys) {
|
||||||
throw new Error("No saveCrossSigningKeys callback supplied");
|
throw new Error("No saveCrossSigningKeys callback supplied");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -289,8 +295,8 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const privateKeys = {};
|
const privateKeys: Record<string, Uint8Array> = {};
|
||||||
const keys = {};
|
const keys: Record<string, object> = {};
|
||||||
let masterSigning;
|
let masterSigning;
|
||||||
let masterPub;
|
let masterPub;
|
||||||
|
|
||||||
@@ -347,7 +353,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Object.assign(this.keys, keys);
|
Object.assign(this.keys, keys);
|
||||||
this._callbacks.saveCrossSigningKeys(privateKeys);
|
this.callbacks.saveCrossSigningKeys(privateKeys);
|
||||||
} finally {
|
} finally {
|
||||||
if (masterSigning) {
|
if (masterSigning) {
|
||||||
masterSigning.free();
|
masterSigning.free();
|
||||||
@@ -358,12 +364,12 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
/**
|
/**
|
||||||
* unsets the keys, used when another session has reset the keys, to disable cross-signing
|
* unsets the keys, used when another session has reset the keys, to disable cross-signing
|
||||||
*/
|
*/
|
||||||
clearKeys() {
|
public clearKeys(): void {
|
||||||
this.keys = {};
|
this.keys = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
setKeys(keys) {
|
public setKeys(keys: Record<string, any>): void {
|
||||||
const signingKeys = {};
|
const signingKeys: Record<string, object> = {};
|
||||||
if (keys.master) {
|
if (keys.master) {
|
||||||
if (keys.master.user_id !== this.userId) {
|
if (keys.master.user_id !== this.userId) {
|
||||||
const error = "Mismatched user ID " + keys.master.user_id +
|
const error = "Mismatched user ID " + keys.master.user_id +
|
||||||
@@ -434,7 +440,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
updateCrossSigningVerifiedBefore(isCrossSigningVerified) {
|
public updateCrossSigningVerifiedBefore(isCrossSigningVerified: boolean): void {
|
||||||
// It is critical that this value latches forward from false to true but
|
// It is critical that this value latches forward from false to true but
|
||||||
// never back to false to avoid a downgrade attack.
|
// never back to false to avoid a downgrade attack.
|
||||||
if (!this.crossSigningVerifiedBefore && isCrossSigningVerified) {
|
if (!this.crossSigningVerifiedBefore && isCrossSigningVerified) {
|
||||||
@@ -442,7 +448,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async signObject(data, type) {
|
public async signObject<T extends object>(data: T, type: string): Promise<T> {
|
||||||
if (!this.keys[type]) {
|
if (!this.keys[type]) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"Attempted to sign with " + type + " key but no such key present",
|
"Attempted to sign with " + type + " key but no such key present",
|
||||||
@@ -457,7 +463,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async signUser(key) {
|
public async signUser(key: CrossSigningInfo): Promise<object> {
|
||||||
if (!this.keys.user_signing) {
|
if (!this.keys.user_signing) {
|
||||||
logger.info("No user signing key: not signing user");
|
logger.info("No user signing key: not signing user");
|
||||||
return;
|
return;
|
||||||
@@ -465,7 +471,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
return this.signObject(key.keys.master, "user_signing");
|
return this.signObject(key.keys.master, "user_signing");
|
||||||
}
|
}
|
||||||
|
|
||||||
async signDevice(userId, device) {
|
public async signDevice(userId: string, device: DeviceInfo): Promise<object> {
|
||||||
if (userId !== this.userId) {
|
if (userId !== this.userId) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Trying to sign ${userId}'s device; can only sign our own device`,
|
`Trying to sign ${userId}'s device; can only sign our own device`,
|
||||||
@@ -492,7 +498,7 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @returns {UserTrustLevel}
|
* @returns {UserTrustLevel}
|
||||||
*/
|
*/
|
||||||
checkUserTrust(userCrossSigning) {
|
public checkUserTrust(userCrossSigning: CrossSigningInfo): UserTrustLevel {
|
||||||
// if we're checking our own key, then it's trusted if the master key
|
// if we're checking our own key, then it's trusted if the master key
|
||||||
// and self-signing key match
|
// and self-signing key match
|
||||||
if (this.userId === userCrossSigning.userId
|
if (this.userId === userCrossSigning.userId
|
||||||
@@ -530,12 +536,17 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @param {CrossSigningInfo} userCrossSigning Cross signing info for user
|
* @param {CrossSigningInfo} userCrossSigning Cross signing info for user
|
||||||
* @param {module:crypto/deviceinfo} device The device to check
|
* @param {module:crypto/deviceinfo} device The device to check
|
||||||
* @param {bool} localTrust Whether the device is trusted locally
|
* @param {boolean} localTrust Whether the device is trusted locally
|
||||||
* @param {bool} trustCrossSignedDevices Whether we trust cross signed devices
|
* @param {boolean} trustCrossSignedDevices Whether we trust cross signed devices
|
||||||
*
|
*
|
||||||
* @returns {DeviceTrustLevel}
|
* @returns {DeviceTrustLevel}
|
||||||
*/
|
*/
|
||||||
checkDeviceTrust(userCrossSigning, device, localTrust, trustCrossSignedDevices) {
|
public checkDeviceTrust(
|
||||||
|
userCrossSigning: CrossSigningInfo,
|
||||||
|
device: DeviceInfo,
|
||||||
|
localTrust: boolean,
|
||||||
|
trustCrossSignedDevices: boolean,
|
||||||
|
): DeviceTrustLevel {
|
||||||
const userTrust = this.checkUserTrust(userCrossSigning);
|
const userTrust = this.checkUserTrust(userCrossSigning);
|
||||||
|
|
||||||
const userSSK = userCrossSigning.keys.self_signing;
|
const userSSK = userCrossSigning.keys.self_signing;
|
||||||
@@ -552,29 +563,23 @@ export class CrossSigningInfo extends EventEmitter {
|
|||||||
// if we can verify the user's SSK from their master key...
|
// if we can verify the user's SSK from their master key...
|
||||||
pkVerify(userSSK, userCrossSigning.getId(), userCrossSigning.userId);
|
pkVerify(userSSK, userCrossSigning.getId(), userCrossSigning.userId);
|
||||||
// ...and this device's key from their SSK...
|
// ...and this device's key from their SSK...
|
||||||
pkVerify(
|
pkVerify(deviceObj, publicKeyFromKeyInfo(userSSK), userCrossSigning.userId);
|
||||||
deviceObj, publicKeyFromKeyInfo(userSSK), userCrossSigning.userId,
|
|
||||||
);
|
|
||||||
// ...then we trust this device as much as far as we trust the user
|
// ...then we trust this device as much as far as we trust the user
|
||||||
return DeviceTrustLevel.fromUserTrustLevel(
|
return DeviceTrustLevel.fromUserTrustLevel(userTrust, localTrust, trustCrossSignedDevices);
|
||||||
userTrust, localTrust, trustCrossSignedDevices,
|
|
||||||
);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return new DeviceTrustLevel(
|
return new DeviceTrustLevel(false, false, localTrust, trustCrossSignedDevices);
|
||||||
false, false, localTrust, trustCrossSignedDevices,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {object} Cache callbacks
|
* @returns {object} Cache callbacks
|
||||||
*/
|
*/
|
||||||
getCacheCallbacks() {
|
public getCacheCallbacks(): ICacheCallbacks {
|
||||||
return this._cacheCallbacks;
|
return this.cacheCallbacks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function deviceToObject(device, userId) {
|
function deviceToObject(device: DeviceInfo, userId: string) {
|
||||||
return {
|
return {
|
||||||
algorithms: device.algorithms,
|
algorithms: device.algorithms,
|
||||||
keys: device.keys,
|
keys: device.keys,
|
||||||
@@ -584,49 +589,49 @@ function deviceToObject(device, userId) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CrossSigningLevel = {
|
export enum CrossSigningLevel {
|
||||||
MASTER: 4,
|
MASTER = 4,
|
||||||
USER_SIGNING: 2,
|
USER_SIGNING = 2,
|
||||||
SELF_SIGNING: 1,
|
SELF_SIGNING = 1,
|
||||||
};
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents the ways in which we trust a user
|
* Represents the ways in which we trust a user
|
||||||
*/
|
*/
|
||||||
export class UserTrustLevel {
|
export class UserTrustLevel {
|
||||||
constructor(crossSigningVerified, crossSigningVerifiedBefore, tofu) {
|
constructor(
|
||||||
this._crossSigningVerified = crossSigningVerified;
|
private readonly crossSigningVerified: boolean,
|
||||||
this._crossSigningVerifiedBefore = crossSigningVerifiedBefore;
|
private readonly crossSigningVerifiedBefore: boolean,
|
||||||
this._tofu = tofu;
|
private readonly tofu: boolean,
|
||||||
}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {bool} true if this user is verified via any means
|
* @returns {boolean} true if this user is verified via any means
|
||||||
*/
|
*/
|
||||||
isVerified() {
|
public isVerified(): boolean {
|
||||||
return this.isCrossSigningVerified();
|
return this.isCrossSigningVerified();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {bool} true if this user is verified via cross signing
|
* @returns {boolean} true if this user is verified via cross signing
|
||||||
*/
|
*/
|
||||||
isCrossSigningVerified() {
|
public isCrossSigningVerified(): boolean {
|
||||||
return this._crossSigningVerified;
|
return this.crossSigningVerified;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {bool} true if we ever verified this user before (at least for
|
* @returns {boolean} true if we ever verified this user before (at least for
|
||||||
* the history of verifications observed by this device).
|
* the history of verifications observed by this device).
|
||||||
*/
|
*/
|
||||||
wasCrossSigningVerified() {
|
public wasCrossSigningVerified(): boolean {
|
||||||
return this._crossSigningVerifiedBefore;
|
return this.crossSigningVerifiedBefore;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {bool} true if this user's key is trusted on first use
|
* @returns {boolean} true if this user's key is trusted on first use
|
||||||
*/
|
*/
|
||||||
isTofu() {
|
public isTofu(): boolean {
|
||||||
return this._tofu;
|
return this.tofu;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -634,58 +639,62 @@ export class UserTrustLevel {
|
|||||||
* Represents the ways in which we trust a device
|
* Represents the ways in which we trust a device
|
||||||
*/
|
*/
|
||||||
export class DeviceTrustLevel {
|
export class DeviceTrustLevel {
|
||||||
constructor(crossSigningVerified, tofu, localVerified, trustCrossSignedDevices) {
|
constructor(
|
||||||
this._crossSigningVerified = crossSigningVerified;
|
public readonly crossSigningVerified: boolean,
|
||||||
this._tofu = tofu;
|
public readonly tofu: boolean,
|
||||||
this._localVerified = localVerified;
|
private readonly localVerified: boolean,
|
||||||
this._trustCrossSignedDevices = trustCrossSignedDevices;
|
private readonly trustCrossSignedDevices: boolean,
|
||||||
}
|
) {}
|
||||||
|
|
||||||
static fromUserTrustLevel(userTrustLevel, localVerified, trustCrossSignedDevices) {
|
public static fromUserTrustLevel(
|
||||||
|
userTrustLevel: UserTrustLevel,
|
||||||
|
localVerified: boolean,
|
||||||
|
trustCrossSignedDevices: boolean,
|
||||||
|
): DeviceTrustLevel {
|
||||||
return new DeviceTrustLevel(
|
return new DeviceTrustLevel(
|
||||||
userTrustLevel._crossSigningVerified,
|
userTrustLevel.isCrossSigningVerified(),
|
||||||
userTrustLevel._tofu,
|
userTrustLevel.isTofu(),
|
||||||
localVerified,
|
localVerified,
|
||||||
trustCrossSignedDevices,
|
trustCrossSignedDevices,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {bool} true if this device is verified via any means
|
* @returns {boolean} true if this device is verified via any means
|
||||||
*/
|
*/
|
||||||
isVerified() {
|
public isVerified(): boolean {
|
||||||
return Boolean(this.isLocallyVerified() || (
|
return Boolean(this.isLocallyVerified() || (
|
||||||
this._trustCrossSignedDevices && this.isCrossSigningVerified()
|
this.trustCrossSignedDevices && this.isCrossSigningVerified()
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {bool} true if this device is verified via cross signing
|
* @returns {boolean} true if this device is verified via cross signing
|
||||||
*/
|
*/
|
||||||
isCrossSigningVerified() {
|
public isCrossSigningVerified(): boolean {
|
||||||
return this._crossSigningVerified;
|
return this.crossSigningVerified;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {bool} true if this device is verified locally
|
* @returns {boolean} true if this device is verified locally
|
||||||
*/
|
*/
|
||||||
isLocallyVerified() {
|
public isLocallyVerified(): boolean {
|
||||||
return this._localVerified;
|
return this.localVerified;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @returns {bool} true if this device is trusted from a user's key
|
* @returns {boolean} true if this device is trusted from a user's key
|
||||||
* that is trusted on first use
|
* that is trusted on first use
|
||||||
*/
|
*/
|
||||||
isTofu() {
|
public isTofu(): boolean {
|
||||||
return this._tofu;
|
return this.tofu;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createCryptoStoreCacheCallbacks(store, olmdevice) {
|
export function createCryptoStoreCacheCallbacks(store: CryptoStore, olmDevice: OlmDevice): ICacheCallbacks {
|
||||||
return {
|
return {
|
||||||
getCrossSigningKeyCache: async function(type, _expectedPublicKey) {
|
getCrossSigningKeyCache: async function(type: string, _expectedPublicKey: string): Promise<Uint8Array> {
|
||||||
const key = await new Promise((resolve) => {
|
const key = await new Promise<any>((resolve) => {
|
||||||
return store.doTxn(
|
return store.doTxn(
|
||||||
'readonly',
|
'readonly',
|
||||||
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||||
@@ -696,20 +705,20 @@ export function createCryptoStoreCacheCallbacks(store, olmdevice) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
if (key && key.ciphertext) {
|
if (key && key.ciphertext) {
|
||||||
const pickleKey = Buffer.from(olmdevice._pickleKey);
|
const pickleKey = Buffer.from(olmDevice._pickleKey);
|
||||||
const decrypted = await decryptAES(key, pickleKey, type);
|
const decrypted = await decryptAES(key, pickleKey, type);
|
||||||
return decodeBase64(decrypted);
|
return decodeBase64(decrypted);
|
||||||
} else {
|
} else {
|
||||||
return key;
|
return key;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
storeCrossSigningKeyCache: async function(type, key) {
|
storeCrossSigningKeyCache: async function(type: string, key: Uint8Array): Promise<void> {
|
||||||
if (!(key instanceof Uint8Array)) {
|
if (!(key instanceof Uint8Array)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`storeCrossSigningKeyCache expects Uint8Array, got ${key}`,
|
`storeCrossSigningKeyCache expects Uint8Array, got ${key}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
const pickleKey = Buffer.from(olmdevice._pickleKey);
|
const pickleKey = Buffer.from(olmDevice._pickleKey);
|
||||||
key = await encryptAES(encodeBase64(key), pickleKey, type);
|
key = await encryptAES(encodeBase64(key), pickleKey, type);
|
||||||
return store.doTxn(
|
return store.doTxn(
|
||||||
'readwrite',
|
'readwrite',
|
||||||
@@ -729,7 +738,7 @@ export function createCryptoStoreCacheCallbacks(store, olmdevice) {
|
|||||||
* @param {string} userId The user ID being verified
|
* @param {string} userId The user ID being verified
|
||||||
* @param {string} deviceId The device ID being verified
|
* @param {string} deviceId The device ID being verified
|
||||||
*/
|
*/
|
||||||
export async function requestKeysDuringVerification(baseApis, userId, deviceId) {
|
export async function requestKeysDuringVerification(baseApis: MatrixClient, userId: string, deviceId: string) {
|
||||||
// If this is a self-verification, ask the other party for keys
|
// If this is a self-verification, ask the other party for keys
|
||||||
if (baseApis.getUserId() !== userId) {
|
if (baseApis.getUserId() !== userId) {
|
||||||
return;
|
return;
|
||||||
@@ -739,7 +748,7 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
|
|||||||
// it. We return here in order to test.
|
// it. We return here in order to test.
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
const client = baseApis;
|
const client = baseApis;
|
||||||
const original = client.crypto._crossSigningInfo;
|
const original = client.crypto.crossSigningInfo;
|
||||||
|
|
||||||
// We already have all of the infrastructure we need to validate and
|
// We already have all of the infrastructure we need to validate and
|
||||||
// cache cross-signing keys, so instead of replicating that, here we set
|
// cache cross-signing keys, so instead of replicating that, here we set
|
||||||
@@ -748,8 +757,7 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
|
|||||||
const crossSigning = new CrossSigningInfo(
|
const crossSigning = new CrossSigningInfo(
|
||||||
original.userId,
|
original.userId,
|
||||||
{ getCrossSigningKey: async (type) => {
|
{ getCrossSigningKey: async (type) => {
|
||||||
logger.debug("Cross-signing: requesting secret",
|
logger.debug("Cross-signing: requesting secret", type, deviceId);
|
||||||
type, deviceId);
|
|
||||||
const { promise } = client.requestSecret(
|
const { promise } = client.requestSecret(
|
||||||
`m.cross_signing.${type}`, [deviceId],
|
`m.cross_signing.${type}`, [deviceId],
|
||||||
);
|
);
|
||||||
@@ -757,7 +765,7 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
|
|||||||
const decoded = decodeBase64(result);
|
const decoded = decodeBase64(result);
|
||||||
return Uint8Array.from(decoded);
|
return Uint8Array.from(decoded);
|
||||||
} },
|
} },
|
||||||
original._cacheCallbacks,
|
original.getCacheCallbacks(),
|
||||||
);
|
);
|
||||||
crossSigning.keys = original.keys;
|
crossSigning.keys = original.keys;
|
||||||
|
|
||||||
@@ -774,7 +782,8 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
|
|||||||
});
|
});
|
||||||
|
|
||||||
// also request and cache the key backup key
|
// also request and cache the key backup key
|
||||||
const backupKeyPromise = new Promise(async resolve => {
|
// eslint-disable-next-line no-async-promise-executor
|
||||||
|
const backupKeyPromise = new Promise<void>(async resolve => {
|
||||||
const cachedKey = await client.crypto.getSessionBackupPrivateKey();
|
const cachedKey = await client.crypto.getSessionBackupPrivateKey();
|
||||||
if (!cachedKey) {
|
if (!cachedKey) {
|
||||||
logger.info("No cached backup key found. Requesting...");
|
logger.info("No cached backup key found. Requesting...");
|
||||||
@@ -791,9 +800,7 @@ export async function requestKeysDuringVerification(baseApis, userId, deviceId)
|
|||||||
logger.info("Backup key stored. Starting backup restore...");
|
logger.info("Backup key stored. Starting backup restore...");
|
||||||
const backupInfo = await client.getKeyBackupVersion();
|
const backupInfo = await client.getKeyBackupVersion();
|
||||||
// no need to await for this - just let it go in the bg
|
// no need to await for this - just let it go in the bg
|
||||||
client.restoreKeyBackupWithCache(
|
client.restoreKeyBackupWithCache(undefined, undefined, backupInfo).then(() => {
|
||||||
undefined, undefined, backupInfo,
|
|
||||||
).then(() => {
|
|
||||||
logger.info("Backup restored.");
|
logger.info("Backup restored.");
|
||||||
});
|
});
|
||||||
}
|
}
|
@@ -1,7 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2017 Vector Creations Ltd
|
Copyright 2017 - 2021 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2018, 2019 New Vector Ltd
|
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -23,12 +21,15 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { EventEmitter } from 'events';
|
import { EventEmitter } from 'events';
|
||||||
|
|
||||||
import { logger } from '../logger';
|
import { logger } from '../logger';
|
||||||
import { DeviceInfo } from './deviceinfo';
|
import { DeviceInfo, IDevice } from './deviceinfo';
|
||||||
import { CrossSigningInfo } from './CrossSigning';
|
import { CrossSigningInfo } from './CrossSigning';
|
||||||
import * as olmlib from './olmlib';
|
import * as olmlib from './olmlib';
|
||||||
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
|
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
|
||||||
import { chunkPromises, defer, sleep } from '../utils';
|
import { chunkPromises, defer, IDeferred, sleep } from '../utils';
|
||||||
|
import { MatrixClient, CryptoStore } from "../client";
|
||||||
|
import { OlmDevice } from "./OlmDevice";
|
||||||
|
|
||||||
/* State transition diagram for DeviceList._deviceTrackingStatus
|
/* State transition diagram for DeviceList._deviceTrackingStatus
|
||||||
*
|
*
|
||||||
@@ -51,91 +52,96 @@ import { chunkPromises, defer, sleep } from '../utils';
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// constants for DeviceList._deviceTrackingStatus
|
// constants for DeviceList._deviceTrackingStatus
|
||||||
const TRACKING_STATUS_NOT_TRACKED = 0;
|
enum TrackingStatus {
|
||||||
const TRACKING_STATUS_PENDING_DOWNLOAD = 1;
|
NotTracked,
|
||||||
const TRACKING_STATUS_DOWNLOAD_IN_PROGRESS = 2;
|
PendingDownload,
|
||||||
const TRACKING_STATUS_UP_TO_DATE = 3;
|
DownloadInProgress,
|
||||||
|
UpToDate,
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeviceInfoMap = Record<string, Record<string, IDevice>>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @alias module:crypto/DeviceList
|
* @alias module:crypto/DeviceList
|
||||||
*/
|
*/
|
||||||
export class DeviceList extends EventEmitter {
|
export class DeviceList extends EventEmitter {
|
||||||
constructor(baseApis, cryptoStore, olmDevice, keyDownloadChunkSize = 250) {
|
|
||||||
super();
|
|
||||||
|
|
||||||
this._cryptoStore = cryptoStore;
|
|
||||||
|
|
||||||
// userId -> {
|
// userId -> {
|
||||||
// deviceId -> {
|
// deviceId -> {
|
||||||
// [device info]
|
// [device info]
|
||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
this._devices = {};
|
private devices: DeviceInfoMap = {};
|
||||||
|
|
||||||
// userId -> {
|
// userId -> {
|
||||||
// [key info]
|
// [key info]
|
||||||
// }
|
// }
|
||||||
this._crossSigningInfo = {};
|
public crossSigningInfo: Record<string, object> = {};
|
||||||
|
|
||||||
// map of identity keys to the user who owns it
|
// map of identity keys to the user who owns it
|
||||||
this._userByIdentityKey = {};
|
private userByIdentityKey: Record<string, string> = {};
|
||||||
|
|
||||||
// which users we are tracking device status for.
|
// which users we are tracking device status for.
|
||||||
// userId -> TRACKING_STATUS_*
|
// userId -> TRACKING_STATUS_*
|
||||||
this._deviceTrackingStatus = {}; // loaded from storage in load()
|
private deviceTrackingStatus: Record<string, TrackingStatus> = {}; // loaded from storage in load()
|
||||||
|
|
||||||
// The 'next_batch' sync token at the point the data was writen,
|
// The 'next_batch' sync token at the point the data was written,
|
||||||
// ie. a token representing the point immediately after the
|
// ie. a token representing the point immediately after the
|
||||||
// moment represented by the snapshot in the db.
|
// moment represented by the snapshot in the db.
|
||||||
this._syncToken = null;
|
private syncToken: string = null;
|
||||||
|
|
||||||
this._serialiser = new DeviceListUpdateSerialiser(
|
|
||||||
baseApis, olmDevice, this,
|
|
||||||
);
|
|
||||||
|
|
||||||
// userId -> promise
|
// userId -> promise
|
||||||
this._keyDownloadsInProgressByUser = {};
|
private keyDownloadsInProgressByUser: Record<string, Promise<void>> = {};
|
||||||
|
|
||||||
// Maximum number of user IDs per request to prevent server overload (#1619)
|
|
||||||
this._keyDownloadChunkSize = keyDownloadChunkSize;
|
|
||||||
|
|
||||||
// Set whenever changes are made other than setting the sync token
|
// Set whenever changes are made other than setting the sync token
|
||||||
this._dirty = false;
|
private dirty = false;
|
||||||
|
|
||||||
// Promise resolved when device data is saved
|
// Promise resolved when device data is saved
|
||||||
this._savePromise = null;
|
private savePromise: Promise<boolean> = null;
|
||||||
// Function that resolves the save promise
|
// Function that resolves the save promise
|
||||||
this._resolveSavePromise = null;
|
private resolveSavePromise: (saved: boolean) => void = null;
|
||||||
// The time the save is scheduled for
|
// The time the save is scheduled for
|
||||||
this._savePromiseTime = null;
|
private savePromiseTime: number = null;
|
||||||
// The timer used to delay the save
|
// The timer used to delay the save
|
||||||
this._saveTimer = null;
|
private saveTimer: NodeJS.Timeout = null;
|
||||||
// True if we have fetched data from the server or loaded a non-empty
|
// True if we have fetched data from the server or loaded a non-empty
|
||||||
// set of device data from the store
|
// set of device data from the store
|
||||||
this._hasFetched = null;
|
private hasFetched: boolean = null;
|
||||||
|
|
||||||
|
private readonly serialiser: DeviceListUpdateSerialiser;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
baseApis: MatrixClient,
|
||||||
|
private readonly cryptoStore: CryptoStore,
|
||||||
|
olmDevice: OlmDevice,
|
||||||
|
// Maximum number of user IDs per request to prevent server overload (#1619)
|
||||||
|
public readonly keyDownloadChunkSize = 250,
|
||||||
|
) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
this.serialiser = new DeviceListUpdateSerialiser(baseApis, olmDevice, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Load the device tracking state from storage
|
* Load the device tracking state from storage
|
||||||
*/
|
*/
|
||||||
async load() {
|
public async load() {
|
||||||
await this._cryptoStore.doTxn(
|
await this.cryptoStore.doTxn(
|
||||||
'readonly', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
|
'readonly', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
|
||||||
this._cryptoStore.getEndToEndDeviceData(txn, (deviceData) => {
|
this.cryptoStore.getEndToEndDeviceData(txn, (deviceData) => {
|
||||||
this._hasFetched = Boolean(deviceData && deviceData.devices);
|
this.hasFetched = Boolean(deviceData && deviceData.devices);
|
||||||
this._devices = deviceData ? deviceData.devices : {},
|
this.devices = deviceData ? deviceData.devices : {},
|
||||||
this._crossSigningInfo = deviceData ?
|
this.crossSigningInfo = deviceData ?
|
||||||
deviceData.crossSigningInfo || {} : {};
|
deviceData.crossSigningInfo || {} : {};
|
||||||
this._deviceTrackingStatus = deviceData ?
|
this.deviceTrackingStatus = deviceData ?
|
||||||
deviceData.trackingStatus : {};
|
deviceData.trackingStatus : {};
|
||||||
this._syncToken = deviceData ? deviceData.syncToken : null;
|
this.syncToken = deviceData ? deviceData.syncToken : null;
|
||||||
this._userByIdentityKey = {};
|
this.userByIdentityKey = {};
|
||||||
for (const user of Object.keys(this._devices)) {
|
for (const user of Object.keys(this.devices)) {
|
||||||
const userDevices = this._devices[user];
|
const userDevices = this.devices[user];
|
||||||
for (const device of Object.keys(userDevices)) {
|
for (const device of Object.keys(userDevices)) {
|
||||||
const idKey = userDevices[device].keys['curve25519:'+device];
|
const idKey = userDevices[device].keys['curve25519:'+device];
|
||||||
if (idKey !== undefined) {
|
if (idKey !== undefined) {
|
||||||
this._userByIdentityKey[idKey] = user;
|
this.userByIdentityKey[idKey] = user;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -143,17 +149,17 @@ export class DeviceList extends EventEmitter {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const u of Object.keys(this._deviceTrackingStatus)) {
|
for (const u of Object.keys(this.deviceTrackingStatus)) {
|
||||||
// if a download was in progress when we got shut down, it isn't any more.
|
// if a download was in progress when we got shut down, it isn't any more.
|
||||||
if (this._deviceTrackingStatus[u] == TRACKING_STATUS_DOWNLOAD_IN_PROGRESS) {
|
if (this.deviceTrackingStatus[u] == TrackingStatus.DownloadInProgress) {
|
||||||
this._deviceTrackingStatus[u] = TRACKING_STATUS_PENDING_DOWNLOAD;
|
this.deviceTrackingStatus[u] = TrackingStatus.PendingDownload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
stop() {
|
public stop() {
|
||||||
if (this._saveTimer !== null) {
|
if (this.saveTimer !== null) {
|
||||||
clearTimeout(this._saveTimer);
|
clearTimeout(this.saveTimer);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -164,74 +170,73 @@ export class DeviceList extends EventEmitter {
|
|||||||
* The actual save will be delayed by a short amount of time to
|
* The actual save will be delayed by a short amount of time to
|
||||||
* aggregate multiple writes to the database.
|
* aggregate multiple writes to the database.
|
||||||
*
|
*
|
||||||
* @param {integer} delay Time in ms before which the save actually happens.
|
* @param {number} delay Time in ms before which the save actually happens.
|
||||||
* By default, the save is delayed for a short period in order to batch
|
* By default, the save is delayed for a short period in order to batch
|
||||||
* multiple writes, but this behaviour can be disabled by passing 0.
|
* multiple writes, but this behaviour can be disabled by passing 0.
|
||||||
*
|
*
|
||||||
* @return {Promise<bool>} true if the data was saved, false if
|
* @return {Promise<boolean>} true if the data was saved, false if
|
||||||
* it was not (eg. because no changes were pending). The promise
|
* it was not (eg. because no changes were pending). The promise
|
||||||
* will only resolve once the data is saved, so may take some time
|
* will only resolve once the data is saved, so may take some time
|
||||||
* to resolve.
|
* to resolve.
|
||||||
*/
|
*/
|
||||||
async saveIfDirty(delay) {
|
public async saveIfDirty(delay = 500): Promise<boolean> {
|
||||||
if (!this._dirty) return Promise.resolve(false);
|
if (!this.dirty) return Promise.resolve(false);
|
||||||
// Delay saves for a bit so we can aggregate multiple saves that happen
|
// Delay saves for a bit so we can aggregate multiple saves that happen
|
||||||
// in quick succession (eg. when a whole room's devices are marked as known)
|
// in quick succession (eg. when a whole room's devices are marked as known)
|
||||||
if (delay === undefined) delay = 500;
|
|
||||||
|
|
||||||
const targetTime = Date.now + delay;
|
const targetTime = Date.now + delay;
|
||||||
if (this._savePromiseTime && targetTime < this._savePromiseTime) {
|
if (this.savePromiseTime && targetTime < this.savePromiseTime) {
|
||||||
// There's a save scheduled but for after we would like: cancel
|
// There's a save scheduled but for after we would like: cancel
|
||||||
// it & schedule one for the time we want
|
// it & schedule one for the time we want
|
||||||
clearTimeout(this._saveTimer);
|
clearTimeout(this.saveTimer);
|
||||||
this._saveTimer = null;
|
this.saveTimer = null;
|
||||||
this._savePromiseTime = null;
|
this.savePromiseTime = null;
|
||||||
// (but keep the save promise since whatever called save before
|
// (but keep the save promise since whatever called save before
|
||||||
// will still want to know when the save is done)
|
// will still want to know when the save is done)
|
||||||
}
|
}
|
||||||
|
|
||||||
let savePromise = this._savePromise;
|
let savePromise = this.savePromise;
|
||||||
if (savePromise === null) {
|
if (savePromise === null) {
|
||||||
savePromise = new Promise((resolve, reject) => {
|
savePromise = new Promise((resolve, reject) => {
|
||||||
this._resolveSavePromise = resolve;
|
this.resolveSavePromise = resolve;
|
||||||
});
|
});
|
||||||
this._savePromise = savePromise;
|
this.savePromise = savePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this._saveTimer === null) {
|
if (this.saveTimer === null) {
|
||||||
const resolveSavePromise = this._resolveSavePromise;
|
const resolveSavePromise = this.resolveSavePromise;
|
||||||
this._savePromiseTime = targetTime;
|
this.savePromiseTime = targetTime;
|
||||||
this._saveTimer = setTimeout(() => {
|
this.saveTimer = setTimeout(() => {
|
||||||
logger.log('Saving device tracking data', this._syncToken);
|
logger.log('Saving device tracking data', this.syncToken);
|
||||||
|
|
||||||
// null out savePromise now (after the delay but before the write),
|
// null out savePromise now (after the delay but before the write),
|
||||||
// otherwise we could return the existing promise when the save has
|
// otherwise we could return the existing promise when the save has
|
||||||
// actually already happened.
|
// actually already happened.
|
||||||
this._savePromiseTime = null;
|
this.savePromiseTime = null;
|
||||||
this._saveTimer = null;
|
this.saveTimer = null;
|
||||||
this._savePromise = null;
|
this.savePromise = null;
|
||||||
this._resolveSavePromise = null;
|
this.resolveSavePromise = null;
|
||||||
|
|
||||||
this._cryptoStore.doTxn(
|
this.cryptoStore.doTxn(
|
||||||
'readwrite', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
|
'readwrite', [IndexedDBCryptoStore.STORE_DEVICE_DATA], (txn) => {
|
||||||
this._cryptoStore.storeEndToEndDeviceData({
|
this.cryptoStore.storeEndToEndDeviceData({
|
||||||
devices: this._devices,
|
devices: this.devices,
|
||||||
crossSigningInfo: this._crossSigningInfo,
|
crossSigningInfo: this.crossSigningInfo,
|
||||||
trackingStatus: this._deviceTrackingStatus,
|
trackingStatus: this.deviceTrackingStatus,
|
||||||
syncToken: this._syncToken,
|
syncToken: this.syncToken,
|
||||||
}, txn);
|
}, txn);
|
||||||
},
|
},
|
||||||
).then(() => {
|
).then(() => {
|
||||||
// The device list is considered dirty until the write
|
// The device list is considered dirty until the write completes.
|
||||||
// completes.
|
this.dirty = false;
|
||||||
this._dirty = false;
|
resolveSavePromise(true);
|
||||||
resolveSavePromise();
|
|
||||||
}, err => {
|
}, err => {
|
||||||
logger.error('Failed to save device tracking data', this._syncToken);
|
logger.error('Failed to save device tracking data', this.syncToken);
|
||||||
logger.error(err);
|
logger.error(err);
|
||||||
});
|
});
|
||||||
}, delay);
|
}, delay);
|
||||||
}
|
}
|
||||||
|
|
||||||
return savePromise;
|
return savePromise;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,8 +245,8 @@ export class DeviceList extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @return {string} The sync token
|
* @return {string} The sync token
|
||||||
*/
|
*/
|
||||||
getSyncToken() {
|
public getSyncToken(): string {
|
||||||
return this._syncToken;
|
return this.syncToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -254,8 +259,8 @@ export class DeviceList extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @param {string} st The sync token
|
* @param {string} st The sync token
|
||||||
*/
|
*/
|
||||||
setSyncToken(st) {
|
public setSyncToken(st: string): void {
|
||||||
this._syncToken = st;
|
this.syncToken = st;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -263,33 +268,33 @@ export class DeviceList extends EventEmitter {
|
|||||||
* downloading and storing them if they're not (or if forceDownload is
|
* downloading and storing them if they're not (or if forceDownload is
|
||||||
* true).
|
* true).
|
||||||
* @param {Array} userIds The users to fetch.
|
* @param {Array} userIds The users to fetch.
|
||||||
* @param {bool} forceDownload Always download the keys even if cached.
|
* @param {boolean} forceDownload Always download the keys even if cached.
|
||||||
*
|
*
|
||||||
* @return {Promise} A promise which resolves to a map userId->deviceId->{@link
|
* @return {Promise} A promise which resolves to a map userId->deviceId->{@link
|
||||||
* module:crypto/deviceinfo|DeviceInfo}.
|
* module:crypto/deviceinfo|DeviceInfo}.
|
||||||
*/
|
*/
|
||||||
downloadKeys(userIds, forceDownload) {
|
public downloadKeys(userIds: string[], forceDownload: boolean): Promise<DeviceInfoMap> {
|
||||||
const usersToDownload = [];
|
const usersToDownload = [];
|
||||||
const promises = [];
|
const promises = [];
|
||||||
|
|
||||||
userIds.forEach((u) => {
|
userIds.forEach((u) => {
|
||||||
const trackingStatus = this._deviceTrackingStatus[u];
|
const trackingStatus = this.deviceTrackingStatus[u];
|
||||||
if (this._keyDownloadsInProgressByUser[u]) {
|
if (this.keyDownloadsInProgressByUser[u]) {
|
||||||
// already a key download in progress/queued for this user; its results
|
// already a key download in progress/queued for this user; its results
|
||||||
// will be good enough for us.
|
// will be good enough for us.
|
||||||
logger.log(
|
logger.log(
|
||||||
`downloadKeys: already have a download in progress for ` +
|
`downloadKeys: already have a download in progress for ` +
|
||||||
`${u}: awaiting its result`,
|
`${u}: awaiting its result`,
|
||||||
);
|
);
|
||||||
promises.push(this._keyDownloadsInProgressByUser[u]);
|
promises.push(this.keyDownloadsInProgressByUser[u]);
|
||||||
} else if (forceDownload || trackingStatus != TRACKING_STATUS_UP_TO_DATE) {
|
} else if (forceDownload || trackingStatus != TrackingStatus.UpToDate) {
|
||||||
usersToDownload.push(u);
|
usersToDownload.push(u);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (usersToDownload.length != 0) {
|
if (usersToDownload.length != 0) {
|
||||||
logger.log("downloadKeys: downloading for", usersToDownload);
|
logger.log("downloadKeys: downloading for", usersToDownload);
|
||||||
const downloadPromise = this._doKeyDownload(usersToDownload);
|
const downloadPromise = this.doKeyDownload(usersToDownload);
|
||||||
promises.push(downloadPromise);
|
promises.push(downloadPromise);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -298,7 +303,7 @@ export class DeviceList extends EventEmitter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all(promises).then(() => {
|
return Promise.all(promises).then(() => {
|
||||||
return this._getDevicesFromStore(userIds);
|
return this.getDevicesFromStore(userIds);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -309,12 +314,11 @@ export class DeviceList extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @return {Object} userId->deviceId->{@link module:crypto/deviceinfo|DeviceInfo}.
|
* @return {Object} userId->deviceId->{@link module:crypto/deviceinfo|DeviceInfo}.
|
||||||
*/
|
*/
|
||||||
_getDevicesFromStore(userIds) {
|
private getDevicesFromStore(userIds: string[]): DeviceInfoMap {
|
||||||
const stored = {};
|
const stored = {};
|
||||||
const self = this;
|
userIds.map((u) => {
|
||||||
userIds.map(function(u) {
|
|
||||||
stored[u] = {};
|
stored[u] = {};
|
||||||
const devices = self.getStoredDevicesForUser(u) || [];
|
const devices = this.getStoredDevicesForUser(u) || [];
|
||||||
devices.map(function(dev) {
|
devices.map(function(dev) {
|
||||||
stored[u][dev.deviceId] = dev;
|
stored[u][dev.deviceId] = dev;
|
||||||
});
|
});
|
||||||
@@ -327,8 +331,8 @@ export class DeviceList extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @return {array} All known user IDs
|
* @return {array} All known user IDs
|
||||||
*/
|
*/
|
||||||
getKnownUserIds() {
|
public getKnownUserIds(): string[] {
|
||||||
return Object.keys(this._devices);
|
return Object.keys(this.devices);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -339,8 +343,8 @@ export class DeviceList extends EventEmitter {
|
|||||||
* @return {module:crypto/deviceinfo[]|null} list of devices, or null if we haven't
|
* @return {module:crypto/deviceinfo[]|null} list of devices, or null if we haven't
|
||||||
* managed to get a list of devices for this user yet.
|
* managed to get a list of devices for this user yet.
|
||||||
*/
|
*/
|
||||||
getStoredDevicesForUser(userId) {
|
public getStoredDevicesForUser(userId: string): DeviceInfo[] | null {
|
||||||
const devs = this._devices[userId];
|
const devs = this.devices[userId];
|
||||||
if (!devs) {
|
if (!devs) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -361,19 +365,19 @@ export class DeviceList extends EventEmitter {
|
|||||||
* @return {Object} deviceId->{object} devices, or undefined if
|
* @return {Object} deviceId->{object} devices, or undefined if
|
||||||
* there is no data for this user.
|
* there is no data for this user.
|
||||||
*/
|
*/
|
||||||
getRawStoredDevicesForUser(userId) {
|
public getRawStoredDevicesForUser(userId: string): Record<string, IDevice> {
|
||||||
return this._devices[userId];
|
return this.devices[userId];
|
||||||
}
|
}
|
||||||
|
|
||||||
getStoredCrossSigningForUser(userId) {
|
public getStoredCrossSigningForUser(userId: string): CrossSigningInfo {
|
||||||
if (!this._crossSigningInfo[userId]) return null;
|
if (!this.crossSigningInfo[userId]) return null;
|
||||||
|
|
||||||
return CrossSigningInfo.fromStorage(this._crossSigningInfo[userId], userId);
|
return CrossSigningInfo.fromStorage(this.crossSigningInfo[userId], userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
storeCrossSigningForUser(userId, info) {
|
public storeCrossSigningForUser(userId: string, info: CrossSigningInfo): void {
|
||||||
this._crossSigningInfo[userId] = info;
|
this.crossSigningInfo[userId] = info;
|
||||||
this._dirty = true;
|
this.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -385,8 +389,8 @@ export class DeviceList extends EventEmitter {
|
|||||||
* @return {module:crypto/deviceinfo?} device, or undefined
|
* @return {module:crypto/deviceinfo?} device, or undefined
|
||||||
* if we don't know about this device
|
* if we don't know about this device
|
||||||
*/
|
*/
|
||||||
getStoredDevice(userId, deviceId) {
|
public getStoredDevice(userId: string, deviceId: string): DeviceInfo {
|
||||||
const devs = this._devices[userId];
|
const devs = this.devices[userId];
|
||||||
if (!devs || !devs[deviceId]) {
|
if (!devs || !devs[deviceId]) {
|
||||||
return undefined;
|
return undefined;
|
||||||
}
|
}
|
||||||
@@ -401,7 +405,7 @@ export class DeviceList extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @return {string} user ID
|
* @return {string} user ID
|
||||||
*/
|
*/
|
||||||
getUserByIdentityKey(algorithm, senderKey) {
|
public getUserByIdentityKey(algorithm: string, senderKey: string): string {
|
||||||
if (
|
if (
|
||||||
algorithm !== olmlib.OLM_ALGORITHM &&
|
algorithm !== olmlib.OLM_ALGORITHM &&
|
||||||
algorithm !== olmlib.MEGOLM_ALGORITHM
|
algorithm !== olmlib.MEGOLM_ALGORITHM
|
||||||
@@ -410,7 +414,7 @@ export class DeviceList extends EventEmitter {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._userByIdentityKey[senderKey];
|
return this.userByIdentityKey[senderKey];
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -421,13 +425,13 @@ export class DeviceList extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @return {module:crypto/deviceinfo?}
|
* @return {module:crypto/deviceinfo?}
|
||||||
*/
|
*/
|
||||||
getDeviceByIdentityKey(algorithm, senderKey) {
|
public getDeviceByIdentityKey(algorithm: string, senderKey: string): DeviceInfo | null {
|
||||||
const userId = this.getUserByIdentityKey(algorithm, senderKey);
|
const userId = this.getUserByIdentityKey(algorithm, senderKey);
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const devices = this._devices[userId];
|
const devices = this.devices[userId];
|
||||||
if (!devices) {
|
if (!devices) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -462,25 +466,25 @@ export class DeviceList extends EventEmitter {
|
|||||||
* @param {string} u The user ID
|
* @param {string} u The user ID
|
||||||
* @param {Object} devs New device info for user
|
* @param {Object} devs New device info for user
|
||||||
*/
|
*/
|
||||||
storeDevicesForUser(u, devs) {
|
public storeDevicesForUser(u: string, devs: Record<string, IDevice>): void {
|
||||||
// remove previous devices from _userByIdentityKey
|
// remove previous devices from userByIdentityKey
|
||||||
if (this._devices[u] !== undefined) {
|
if (this.devices[u] !== undefined) {
|
||||||
for (const [deviceId, dev] of Object.entries(this._devices[u])) {
|
for (const [deviceId, dev] of Object.entries(this.devices[u])) {
|
||||||
const identityKey = dev.keys['curve25519:'+deviceId];
|
const identityKey = dev.keys['curve25519:'+deviceId];
|
||||||
|
|
||||||
delete this._userByIdentityKey[identityKey];
|
delete this.userByIdentityKey[identityKey];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._devices[u] = devs;
|
this.devices[u] = devs;
|
||||||
|
|
||||||
// add new ones
|
// add new ones
|
||||||
for (const [deviceId, dev] of Object.entries(devs)) {
|
for (const [deviceId, dev] of Object.entries(devs)) {
|
||||||
const identityKey = dev.keys['curve25519:'+deviceId];
|
const identityKey = dev.keys['curve25519:'+deviceId];
|
||||||
|
|
||||||
this._userByIdentityKey[identityKey] = u;
|
this.userByIdentityKey[identityKey] = u;
|
||||||
}
|
}
|
||||||
this._dirty = true;
|
this.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -492,7 +496,7 @@ export class DeviceList extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @param {String} userId
|
* @param {String} userId
|
||||||
*/
|
*/
|
||||||
startTrackingDeviceList(userId) {
|
public startTrackingDeviceList(userId: string): void {
|
||||||
// sanity-check the userId. This is mostly paranoia, but if synapse
|
// sanity-check the userId. This is mostly paranoia, but if synapse
|
||||||
// can't parse the userId we give it as an mxid, it 500s the whole
|
// can't parse the userId we give it as an mxid, it 500s the whole
|
||||||
// request and we can never update the device lists again (because
|
// request and we can never update the device lists again (because
|
||||||
@@ -503,12 +507,12 @@ export class DeviceList extends EventEmitter {
|
|||||||
if (typeof userId !== 'string') {
|
if (typeof userId !== 'string') {
|
||||||
throw new Error('userId must be a string; was '+userId);
|
throw new Error('userId must be a string; was '+userId);
|
||||||
}
|
}
|
||||||
if (!this._deviceTrackingStatus[userId]) {
|
if (!this.deviceTrackingStatus[userId]) {
|
||||||
logger.log('Now tracking device list for ' + userId);
|
logger.log('Now tracking device list for ' + userId);
|
||||||
this._deviceTrackingStatus[userId] = TRACKING_STATUS_PENDING_DOWNLOAD;
|
this.deviceTrackingStatus[userId] = TrackingStatus.PendingDownload;
|
||||||
// we don't yet persist the tracking status, since there may be a lot
|
// we don't yet persist the tracking status, since there may be a lot
|
||||||
// of calls; we save all data together once the sync is done
|
// of calls; we save all data together once the sync is done
|
||||||
this._dirty = true;
|
this.dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,14 +525,14 @@ export class DeviceList extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @param {String} userId
|
* @param {String} userId
|
||||||
*/
|
*/
|
||||||
stopTrackingDeviceList(userId) {
|
public stopTrackingDeviceList(userId: string): void {
|
||||||
if (this._deviceTrackingStatus[userId]) {
|
if (this.deviceTrackingStatus[userId]) {
|
||||||
logger.log('No longer tracking device list for ' + userId);
|
logger.log('No longer tracking device list for ' + userId);
|
||||||
this._deviceTrackingStatus[userId] = TRACKING_STATUS_NOT_TRACKED;
|
this.deviceTrackingStatus[userId] = TrackingStatus.NotTracked;
|
||||||
|
|
||||||
// we don't yet persist the tracking status, since there may be a lot
|
// we don't yet persist the tracking status, since there may be a lot
|
||||||
// of calls; we save all data together once the sync is done
|
// of calls; we save all data together once the sync is done
|
||||||
this._dirty = true;
|
this.dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -538,11 +542,11 @@ export class DeviceList extends EventEmitter {
|
|||||||
* This will flag each user whose devices we are tracking as in need of an
|
* This will flag each user whose devices we are tracking as in need of an
|
||||||
* update.
|
* update.
|
||||||
*/
|
*/
|
||||||
stopTrackingAllDeviceLists() {
|
public stopTrackingAllDeviceLists(): void {
|
||||||
for (const userId of Object.keys(this._deviceTrackingStatus)) {
|
for (const userId of Object.keys(this.deviceTrackingStatus)) {
|
||||||
this._deviceTrackingStatus[userId] = TRACKING_STATUS_NOT_TRACKED;
|
this.deviceTrackingStatus[userId] = TrackingStatus.NotTracked;
|
||||||
}
|
}
|
||||||
this._dirty = true;
|
this.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -556,14 +560,14 @@ export class DeviceList extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @param {String} userId
|
* @param {String} userId
|
||||||
*/
|
*/
|
||||||
invalidateUserDeviceList(userId) {
|
public invalidateUserDeviceList(userId: string): void {
|
||||||
if (this._deviceTrackingStatus[userId]) {
|
if (this.deviceTrackingStatus[userId]) {
|
||||||
logger.log("Marking device list outdated for", userId);
|
logger.log("Marking device list outdated for", userId);
|
||||||
this._deviceTrackingStatus[userId] = TRACKING_STATUS_PENDING_DOWNLOAD;
|
this.deviceTrackingStatus[userId] = TrackingStatus.PendingDownload;
|
||||||
|
|
||||||
// we don't yet persist the tracking status, since there may be a lot
|
// we don't yet persist the tracking status, since there may be a lot
|
||||||
// of calls; we save all data together once the sync is done
|
// of calls; we save all data together once the sync is done
|
||||||
this._dirty = true;
|
this.dirty = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -573,18 +577,18 @@ export class DeviceList extends EventEmitter {
|
|||||||
* @returns {Promise} which completes when the download completes; normally there
|
* @returns {Promise} which completes when the download completes; normally there
|
||||||
* is no need to wait for this (it's mostly for the unit tests).
|
* is no need to wait for this (it's mostly for the unit tests).
|
||||||
*/
|
*/
|
||||||
refreshOutdatedDeviceLists() {
|
public refreshOutdatedDeviceLists(): Promise<void> {
|
||||||
this.saveIfDirty();
|
this.saveIfDirty();
|
||||||
|
|
||||||
const usersToDownload = [];
|
const usersToDownload = [];
|
||||||
for (const userId of Object.keys(this._deviceTrackingStatus)) {
|
for (const userId of Object.keys(this.deviceTrackingStatus)) {
|
||||||
const stat = this._deviceTrackingStatus[userId];
|
const stat = this.deviceTrackingStatus[userId];
|
||||||
if (stat == TRACKING_STATUS_PENDING_DOWNLOAD) {
|
if (stat == TrackingStatus.PendingDownload) {
|
||||||
usersToDownload.push(userId);
|
usersToDownload.push(userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return this._doKeyDownload(usersToDownload);
|
return this.doKeyDownload(usersToDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -595,34 +599,34 @@ export class DeviceList extends EventEmitter {
|
|||||||
*
|
*
|
||||||
* @param {Object} devices deviceId->{object} the new devices
|
* @param {Object} devices deviceId->{object} the new devices
|
||||||
*/
|
*/
|
||||||
_setRawStoredDevicesForUser(userId, devices) {
|
public setRawStoredDevicesForUser(userId: string, devices: Record<string, IDevice>): void {
|
||||||
// remove old devices from _userByIdentityKey
|
// remove old devices from userByIdentityKey
|
||||||
if (this._devices[userId] !== undefined) {
|
if (this.devices[userId] !== undefined) {
|
||||||
for (const [deviceId, dev] of Object.entries(this._devices[userId])) {
|
for (const [deviceId, dev] of Object.entries(this.devices[userId])) {
|
||||||
const identityKey = dev.keys['curve25519:'+deviceId];
|
const identityKey = dev.keys['curve25519:'+deviceId];
|
||||||
|
|
||||||
delete this._userByIdentityKey[identityKey];
|
delete this.userByIdentityKey[identityKey];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._devices[userId] = devices;
|
this.devices[userId] = devices;
|
||||||
|
|
||||||
// add new devices into _userByIdentityKey
|
// add new devices into userByIdentityKey
|
||||||
for (const [deviceId, dev] of Object.entries(devices)) {
|
for (const [deviceId, dev] of Object.entries(devices)) {
|
||||||
const identityKey = dev.keys['curve25519:'+deviceId];
|
const identityKey = dev.keys['curve25519:'+deviceId];
|
||||||
|
|
||||||
this._userByIdentityKey[identityKey] = userId;
|
this.userByIdentityKey[identityKey] = userId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
setRawStoredCrossSigningForUser(userId, info) {
|
public setRawStoredCrossSigningForUser(userId: string, info: object): void {
|
||||||
this._crossSigningInfo[userId] = info;
|
this.crossSigningInfo[userId] = info;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fire off download update requests for the given users, and update the
|
* Fire off download update requests for the given users, and update the
|
||||||
* device list tracking status for them, and the
|
* device list tracking status for them, and the
|
||||||
* _keyDownloadsInProgressByUser map for them.
|
* keyDownloadsInProgressByUser map for them.
|
||||||
*
|
*
|
||||||
* @param {String[]} users list of userIds
|
* @param {String[]} users list of userIds
|
||||||
*
|
*
|
||||||
@@ -630,15 +634,13 @@ export class DeviceList extends EventEmitter {
|
|||||||
* been updated. rejects if there was a problem updating any of the
|
* been updated. rejects if there was a problem updating any of the
|
||||||
* users.
|
* users.
|
||||||
*/
|
*/
|
||||||
_doKeyDownload(users) {
|
private doKeyDownload(users: string[]): Promise<void> {
|
||||||
if (users.length === 0) {
|
if (users.length === 0) {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
const prom = this._serialiser.updateDevicesForUsers(
|
const prom = this.serialiser.updateDevicesForUsers(users, this.syncToken).then(() => {
|
||||||
users, this._syncToken,
|
|
||||||
).then(() => {
|
|
||||||
finished(true);
|
finished(true);
|
||||||
}, (e) => {
|
}, (e) => {
|
||||||
logger.error(
|
logger.error(
|
||||||
@@ -649,42 +651,41 @@ export class DeviceList extends EventEmitter {
|
|||||||
});
|
});
|
||||||
|
|
||||||
users.forEach((u) => {
|
users.forEach((u) => {
|
||||||
this._keyDownloadsInProgressByUser[u] = prom;
|
this.keyDownloadsInProgressByUser[u] = prom;
|
||||||
const stat = this._deviceTrackingStatus[u];
|
const stat = this.deviceTrackingStatus[u];
|
||||||
if (stat == TRACKING_STATUS_PENDING_DOWNLOAD) {
|
if (stat == TrackingStatus.PendingDownload) {
|
||||||
this._deviceTrackingStatus[u] = TRACKING_STATUS_DOWNLOAD_IN_PROGRESS;
|
this.deviceTrackingStatus[u] = TrackingStatus.DownloadInProgress;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const finished = (success) => {
|
const finished = (success) => {
|
||||||
this.emit("crypto.willUpdateDevices", users, !this._hasFetched);
|
this.emit("crypto.willUpdateDevices", users, !this.hasFetched);
|
||||||
users.forEach((u) => {
|
users.forEach((u) => {
|
||||||
this._dirty = true;
|
this.dirty = true;
|
||||||
|
|
||||||
// we may have queued up another download request for this user
|
// we may have queued up another download request for this user
|
||||||
// since we started this request. If that happens, we should
|
// since we started this request. If that happens, we should
|
||||||
// ignore the completion of the first one.
|
// ignore the completion of the first one.
|
||||||
if (this._keyDownloadsInProgressByUser[u] !== prom) {
|
if (this.keyDownloadsInProgressByUser[u] !== prom) {
|
||||||
logger.log('Another update in the queue for', u,
|
logger.log('Another update in the queue for', u, '- not marking up-to-date');
|
||||||
'- not marking up-to-date');
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
delete this._keyDownloadsInProgressByUser[u];
|
delete this.keyDownloadsInProgressByUser[u];
|
||||||
const stat = this._deviceTrackingStatus[u];
|
const stat = this.deviceTrackingStatus[u];
|
||||||
if (stat == TRACKING_STATUS_DOWNLOAD_IN_PROGRESS) {
|
if (stat == TrackingStatus.DownloadInProgress) {
|
||||||
if (success) {
|
if (success) {
|
||||||
// we didn't get any new invalidations since this download started:
|
// we didn't get any new invalidations since this download started:
|
||||||
// this user's device list is now up to date.
|
// this user's device list is now up to date.
|
||||||
this._deviceTrackingStatus[u] = TRACKING_STATUS_UP_TO_DATE;
|
this.deviceTrackingStatus[u] = TrackingStatus.UpToDate;
|
||||||
logger.log("Device list for", u, "now up to date");
|
logger.log("Device list for", u, "now up to date");
|
||||||
} else {
|
} else {
|
||||||
this._deviceTrackingStatus[u] = TRACKING_STATUS_PENDING_DOWNLOAD;
|
this.deviceTrackingStatus[u] = TrackingStatus.PendingDownload;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
this.saveIfDirty();
|
this.saveIfDirty();
|
||||||
this.emit("crypto.devicesUpdated", users, !this._hasFetched);
|
this.emit("crypto.devicesUpdated", users, !this.hasFetched);
|
||||||
this._hasFetched = true;
|
this.hasFetched = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
return prom;
|
return prom;
|
||||||
@@ -701,29 +702,28 @@ export class DeviceList extends EventEmitter {
|
|||||||
* time (and queuing other requests up).
|
* time (and queuing other requests up).
|
||||||
*/
|
*/
|
||||||
class DeviceListUpdateSerialiser {
|
class DeviceListUpdateSerialiser {
|
||||||
/*
|
private downloadInProgress = false;
|
||||||
* @param {object} baseApis Base API object
|
|
||||||
* @param {object} olmDevice The Olm Device
|
|
||||||
* @param {object} deviceList The device list object
|
|
||||||
*/
|
|
||||||
constructor(baseApis, olmDevice, deviceList) {
|
|
||||||
this._baseApis = baseApis;
|
|
||||||
this._olmDevice = olmDevice;
|
|
||||||
this._deviceList = deviceList; // the device list to be updated
|
|
||||||
|
|
||||||
this._downloadInProgress = false;
|
|
||||||
|
|
||||||
// users which are queued for download
|
// users which are queued for download
|
||||||
// userId -> true
|
// userId -> true
|
||||||
this._keyDownloadsQueuedByUser = {};
|
private keyDownloadsQueuedByUser: Record<string, boolean> = {};
|
||||||
|
|
||||||
// deferred which is resolved when the queued users are downloaded.
|
// deferred which is resolved when the queued users are downloaded.
|
||||||
//
|
|
||||||
// non-null indicates that we have users queued for download.
|
// non-null indicates that we have users queued for download.
|
||||||
this._queuedQueryDeferred = null;
|
private queuedQueryDeferred: IDeferred<void> = null;
|
||||||
|
|
||||||
this._syncToken = null; // The sync token we send with the requests
|
private syncToken: string = null; // The sync token we send with the requests
|
||||||
}
|
|
||||||
|
/*
|
||||||
|
* @param {object} baseApis Base API object
|
||||||
|
* @param {object} olmDevice The Olm Device
|
||||||
|
* @param {object} deviceList The device list object, the device list to be updated
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
private readonly baseApis: MatrixClient,
|
||||||
|
private readonly olmDevice: OlmDevice,
|
||||||
|
private readonly deviceList: DeviceList,
|
||||||
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Make a key query request for the given users
|
* Make a key query request for the given users
|
||||||
@@ -737,57 +737,57 @@ class DeviceListUpdateSerialiser {
|
|||||||
* been updated. rejects if there was a problem updating any of the
|
* been updated. rejects if there was a problem updating any of the
|
||||||
* users.
|
* users.
|
||||||
*/
|
*/
|
||||||
updateDevicesForUsers(users, syncToken) {
|
public updateDevicesForUsers(users: string[], syncToken: string): Promise<void> {
|
||||||
users.forEach((u) => {
|
users.forEach((u) => {
|
||||||
this._keyDownloadsQueuedByUser[u] = true;
|
this.keyDownloadsQueuedByUser[u] = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!this._queuedQueryDeferred) {
|
if (!this.queuedQueryDeferred) {
|
||||||
this._queuedQueryDeferred = defer();
|
this.queuedQueryDeferred = defer();
|
||||||
}
|
}
|
||||||
|
|
||||||
// We always take the new sync token and just use the latest one we've
|
// We always take the new sync token and just use the latest one we've
|
||||||
// been given, since it just needs to be at least as recent as the
|
// been given, since it just needs to be at least as recent as the
|
||||||
// sync response the device invalidation message arrived in
|
// sync response the device invalidation message arrived in
|
||||||
this._syncToken = syncToken;
|
this.syncToken = syncToken;
|
||||||
|
|
||||||
if (this._downloadInProgress) {
|
if (this.downloadInProgress) {
|
||||||
// just queue up these users
|
// just queue up these users
|
||||||
logger.log('Queued key download for', users);
|
logger.log('Queued key download for', users);
|
||||||
return this._queuedQueryDeferred.promise;
|
return this.queuedQueryDeferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
// start a new download.
|
// start a new download.
|
||||||
return this._doQueuedQueries();
|
return this.doQueuedQueries();
|
||||||
}
|
}
|
||||||
|
|
||||||
_doQueuedQueries() {
|
private doQueuedQueries(): Promise<void> {
|
||||||
if (this._downloadInProgress) {
|
if (this.downloadInProgress) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
"DeviceListUpdateSerialiser._doQueuedQueries called with request active",
|
"DeviceListUpdateSerialiser.doQueuedQueries called with request active",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const downloadUsers = Object.keys(this._keyDownloadsQueuedByUser);
|
const downloadUsers = Object.keys(this.keyDownloadsQueuedByUser);
|
||||||
this._keyDownloadsQueuedByUser = {};
|
this.keyDownloadsQueuedByUser = {};
|
||||||
const deferred = this._queuedQueryDeferred;
|
const deferred = this.queuedQueryDeferred;
|
||||||
this._queuedQueryDeferred = null;
|
this.queuedQueryDeferred = null;
|
||||||
|
|
||||||
logger.log('Starting key download for', downloadUsers);
|
logger.log('Starting key download for', downloadUsers);
|
||||||
this._downloadInProgress = true;
|
this.downloadInProgress = true;
|
||||||
|
|
||||||
const opts = {};
|
const opts: Parameters<MatrixClient["downloadKeysForUsers"]>[1] = {};
|
||||||
if (this._syncToken) {
|
if (this.syncToken) {
|
||||||
opts.token = this._syncToken;
|
opts.token = this.syncToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
const factories = [];
|
const factories = [];
|
||||||
for (let i = 0; i < downloadUsers.length; i += this._deviceList._keyDownloadChunkSize) {
|
for (let i = 0; i < downloadUsers.length; i += this.deviceList.keyDownloadChunkSize) {
|
||||||
const userSlice = downloadUsers.slice(i, i + this._deviceList._keyDownloadChunkSize);
|
const userSlice = downloadUsers.slice(i, i + this.deviceList.keyDownloadChunkSize);
|
||||||
factories.push(() => this._baseApis.downloadKeysForUsers(userSlice, opts));
|
factories.push(() => this.baseApis.downloadKeysForUsers(userSlice, opts));
|
||||||
}
|
}
|
||||||
|
|
||||||
chunkPromises(factories, 3).then(async (responses) => {
|
chunkPromises(factories, 3).then(async (responses: any[]) => {
|
||||||
const dk = Object.assign({}, ...(responses.map(res => res.device_keys || {})));
|
const dk = Object.assign({}, ...(responses.map(res => res.device_keys || {})));
|
||||||
const masterKeys = Object.assign({}, ...(responses.map(res => res.master_keys || {})));
|
const masterKeys = Object.assign({}, ...(responses.map(res => res.master_keys || {})));
|
||||||
const ssks = Object.assign({}, ...(responses.map(res => res.self_signing_keys || {})));
|
const ssks = Object.assign({}, ...(responses.map(res => res.self_signing_keys || {})));
|
||||||
@@ -802,7 +802,7 @@ class DeviceListUpdateSerialiser {
|
|||||||
for (const userId of downloadUsers) {
|
for (const userId of downloadUsers) {
|
||||||
await sleep(5);
|
await sleep(5);
|
||||||
try {
|
try {
|
||||||
await this._processQueryResponseForUser(
|
await this.processQueryResponseForUser(
|
||||||
userId, dk[userId], {
|
userId, dk[userId], {
|
||||||
master: masterKeys[userId],
|
master: masterKeys[userId],
|
||||||
self_signing: ssks[userId],
|
self_signing: ssks[userId],
|
||||||
@@ -818,32 +818,34 @@ class DeviceListUpdateSerialiser {
|
|||||||
}).then(() => {
|
}).then(() => {
|
||||||
logger.log('Completed key download for ' + downloadUsers);
|
logger.log('Completed key download for ' + downloadUsers);
|
||||||
|
|
||||||
this._downloadInProgress = false;
|
this.downloadInProgress = false;
|
||||||
deferred.resolve();
|
deferred.resolve();
|
||||||
|
|
||||||
// if we have queued users, fire off another request.
|
// if we have queued users, fire off another request.
|
||||||
if (this._queuedQueryDeferred) {
|
if (this.queuedQueryDeferred) {
|
||||||
this._doQueuedQueries();
|
this.doQueuedQueries();
|
||||||
}
|
}
|
||||||
}, (e) => {
|
}, (e) => {
|
||||||
logger.warn('Error downloading keys for ' + downloadUsers + ':', e);
|
logger.warn('Error downloading keys for ' + downloadUsers + ':', e);
|
||||||
this._downloadInProgress = false;
|
this.downloadInProgress = false;
|
||||||
deferred.reject(e);
|
deferred.reject(e);
|
||||||
});
|
});
|
||||||
|
|
||||||
return deferred.promise;
|
return deferred.promise;
|
||||||
}
|
}
|
||||||
|
|
||||||
async _processQueryResponseForUser(
|
private async processQueryResponseForUser(
|
||||||
userId, dkResponse, crossSigningResponse,
|
userId: string,
|
||||||
) {
|
dkResponse: object,
|
||||||
|
crossSigningResponse: any, // TODO types
|
||||||
|
): Promise<void> {
|
||||||
logger.log('got device keys for ' + userId + ':', dkResponse);
|
logger.log('got device keys for ' + userId + ':', dkResponse);
|
||||||
logger.log('got cross-signing keys for ' + userId + ':', crossSigningResponse);
|
logger.log('got cross-signing keys for ' + userId + ':', crossSigningResponse);
|
||||||
|
|
||||||
{
|
{
|
||||||
// map from deviceid -> deviceinfo for this user
|
// map from deviceid -> deviceinfo for this user
|
||||||
const userStore = {};
|
const userStore: Record<string, DeviceInfo> = {};
|
||||||
const devs = this._deviceList.getRawStoredDevicesForUser(userId);
|
const devs = this.deviceList.getRawStoredDevicesForUser(userId);
|
||||||
if (devs) {
|
if (devs) {
|
||||||
Object.keys(devs).forEach((deviceId) => {
|
Object.keys(devs).forEach((deviceId) => {
|
||||||
const d = DeviceInfo.fromStorage(devs[deviceId], deviceId);
|
const d = DeviceInfo.fromStorage(devs[deviceId], deviceId);
|
||||||
@@ -851,9 +853,9 @@ class DeviceListUpdateSerialiser {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
await _updateStoredDeviceKeysForUser(
|
await updateStoredDeviceKeysForUser(
|
||||||
this._olmDevice, userId, userStore, dkResponse || {},
|
this.olmDevice, userId, userStore, dkResponse || {},
|
||||||
this._baseApis.getUserId(), this._baseApis.deviceId,
|
this.baseApis.getUserId(), this.baseApis.deviceId,
|
||||||
);
|
);
|
||||||
|
|
||||||
// put the updates into the object that will be returned as our results
|
// put the updates into the object that will be returned as our results
|
||||||
@@ -862,7 +864,7 @@ class DeviceListUpdateSerialiser {
|
|||||||
storage[deviceId] = userStore[deviceId].toStorage();
|
storage[deviceId] = userStore[deviceId].toStorage();
|
||||||
});
|
});
|
||||||
|
|
||||||
this._deviceList._setRawStoredDevicesForUser(userId, storage);
|
this.deviceList.setRawStoredDevicesForUser(userId, storage);
|
||||||
}
|
}
|
||||||
|
|
||||||
// now do the same for the cross-signing keys
|
// now do the same for the cross-signing keys
|
||||||
@@ -873,26 +875,31 @@ class DeviceListUpdateSerialiser {
|
|||||||
&& (crossSigningResponse.master || crossSigningResponse.self_signing
|
&& (crossSigningResponse.master || crossSigningResponse.self_signing
|
||||||
|| crossSigningResponse.user_signing)) {
|
|| crossSigningResponse.user_signing)) {
|
||||||
const crossSigning
|
const crossSigning
|
||||||
= this._deviceList.getStoredCrossSigningForUser(userId)
|
= this.deviceList.getStoredCrossSigningForUser(userId)
|
||||||
|| new CrossSigningInfo(userId);
|
|| new CrossSigningInfo(userId);
|
||||||
|
|
||||||
crossSigning.setKeys(crossSigningResponse);
|
crossSigning.setKeys(crossSigningResponse);
|
||||||
|
|
||||||
this._deviceList.setRawStoredCrossSigningForUser(
|
this.deviceList.setRawStoredCrossSigningForUser(
|
||||||
userId, crossSigning.toStorage(),
|
userId, crossSigning.toStorage(),
|
||||||
);
|
);
|
||||||
|
|
||||||
// NB. Unlike most events in the js-sdk, this one is internal to the
|
// NB. Unlike most events in the js-sdk, this one is internal to the
|
||||||
// js-sdk and is not re-emitted
|
// js-sdk and is not re-emitted
|
||||||
this._deviceList.emit('userCrossSigningUpdated', userId);
|
this.deviceList.emit('userCrossSigningUpdated', userId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function _updateStoredDeviceKeysForUser(
|
async function updateStoredDeviceKeysForUser(
|
||||||
_olmDevice, userId, userStore, userResult, localUserId, localDeviceId,
|
olmDevice: OlmDevice,
|
||||||
) {
|
userId: string,
|
||||||
|
userStore: Record<string, DeviceInfo>,
|
||||||
|
userResult: object,
|
||||||
|
localUserId: string,
|
||||||
|
localDeviceId: string,
|
||||||
|
): Promise<boolean> {
|
||||||
let updated = false;
|
let updated = false;
|
||||||
|
|
||||||
// remove any devices in the store which aren't in the response
|
// remove any devices in the store which aren't in the response
|
||||||
@@ -936,7 +943,7 @@ async function _updateStoredDeviceKeysForUser(
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (await _storeDeviceKeys(_olmDevice, userStore, deviceResult)) {
|
if (await storeDeviceKeys(olmDevice, userStore, deviceResult)) {
|
||||||
updated = true;
|
updated = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -949,7 +956,11 @@ async function _updateStoredDeviceKeysForUser(
|
|||||||
*
|
*
|
||||||
* returns (a promise for) true if a change was made, else false
|
* returns (a promise for) true if a change was made, else false
|
||||||
*/
|
*/
|
||||||
async function _storeDeviceKeys(_olmDevice, userStore, deviceResult) {
|
async function storeDeviceKeys(
|
||||||
|
olmDevice: OlmDevice,
|
||||||
|
userStore: Record<string, DeviceInfo>,
|
||||||
|
deviceResult: any, // TODO types
|
||||||
|
): Promise<boolean> {
|
||||||
if (!deviceResult.keys) {
|
if (!deviceResult.keys) {
|
||||||
// no keys?
|
// no keys?
|
||||||
return false;
|
return false;
|
||||||
@@ -961,8 +972,7 @@ async function _storeDeviceKeys(_olmDevice, userStore, deviceResult) {
|
|||||||
const signKeyId = "ed25519:" + deviceId;
|
const signKeyId = "ed25519:" + deviceId;
|
||||||
const signKey = deviceResult.keys[signKeyId];
|
const signKey = deviceResult.keys[signKeyId];
|
||||||
if (!signKey) {
|
if (!signKey) {
|
||||||
logger.warn("Device " + userId + ":" + deviceId +
|
logger.warn("Device " + userId + ":" + deviceId + " has no ed25519 key");
|
||||||
" has no ed25519 key");
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -970,10 +980,9 @@ async function _storeDeviceKeys(_olmDevice, userStore, deviceResult) {
|
|||||||
const signatures = deviceResult.signatures || {};
|
const signatures = deviceResult.signatures || {};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await olmlib.verifySignature(_olmDevice, deviceResult, userId, deviceId, signKey);
|
await olmlib.verifySignature(olmDevice, deviceResult, userId, deviceId, signKey);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
logger.warn("Unable to verify signature on device " +
|
logger.warn("Unable to verify signature on device " + userId + ":" + deviceId + ":" + e);
|
||||||
userId + ":" + deviceId + ":" + e);
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
@@ -184,7 +184,7 @@ export class EncryptionSetupOperation {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// pass the new keys to the main instance of our own CrossSigningInfo.
|
// pass the new keys to the main instance of our own CrossSigningInfo.
|
||||||
crypto._crossSigningInfo.setKeys(this._crossSigningKeys.keys);
|
crypto.crossSigningInfo.setKeys(this._crossSigningKeys.keys);
|
||||||
}
|
}
|
||||||
// set account data
|
// set account data
|
||||||
if (this._accountData) {
|
if (this._accountData) {
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2018, 2019 New Vector Ltd
|
Copyright 2018 - 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -21,44 +21,51 @@ limitations under the License.
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
|
import { IndexedDBCryptoStore } from './store/indexeddb-crypto-store';
|
||||||
|
import { CryptoStore } from "../client";
|
||||||
|
|
||||||
|
/* eslint-disable camelcase */
|
||||||
|
interface IRoomEncryption {
|
||||||
|
algorithm: string;
|
||||||
|
rotation_period_ms: number;
|
||||||
|
rotation_period_msgs: number;
|
||||||
|
}
|
||||||
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @alias module:crypto/RoomList
|
* @alias module:crypto/RoomList
|
||||||
*/
|
*/
|
||||||
export class RoomList {
|
export class RoomList {
|
||||||
constructor(cryptoStore) {
|
|
||||||
this._cryptoStore = cryptoStore;
|
|
||||||
|
|
||||||
// Object of roomId -> room e2e info object (body of the m.room.encryption event)
|
// Object of roomId -> room e2e info object (body of the m.room.encryption event)
|
||||||
this._roomEncryption = {};
|
private roomEncryption: Record<string, IRoomEncryption> = {};
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
constructor(private readonly cryptoStore: CryptoStore) {}
|
||||||
await this._cryptoStore.doTxn(
|
|
||||||
|
public async init(): Promise<void> {
|
||||||
|
await this.cryptoStore.doTxn(
|
||||||
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
|
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
|
||||||
this._cryptoStore.getEndToEndRooms(txn, (result) => {
|
this.cryptoStore.getEndToEndRooms(txn, (result) => {
|
||||||
this._roomEncryption = result;
|
this.roomEncryption = result;
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
getRoomEncryption(roomId) {
|
public getRoomEncryption(roomId: string): IRoomEncryption {
|
||||||
return this._roomEncryption[roomId] || null;
|
return this.roomEncryption[roomId] || null;
|
||||||
}
|
}
|
||||||
|
|
||||||
isRoomEncrypted(roomId) {
|
public isRoomEncrypted(roomId: string): boolean {
|
||||||
return Boolean(this.getRoomEncryption(roomId));
|
return Boolean(this.getRoomEncryption(roomId));
|
||||||
}
|
}
|
||||||
|
|
||||||
async setRoomEncryption(roomId, roomInfo) {
|
public async setRoomEncryption(roomId: string, roomInfo: IRoomEncryption): Promise<void> {
|
||||||
// important that this happens before calling into the store
|
// important that this happens before calling into the store
|
||||||
// as it prevents the Crypto::setRoomEncryption from calling
|
// as it prevents the Crypto::setRoomEncryption from calling
|
||||||
// this twice for consecutive m.room.encryption events
|
// this twice for consecutive m.room.encryption events
|
||||||
this._roomEncryption[roomId] = roomInfo;
|
this.roomEncryption[roomId] = roomInfo;
|
||||||
await this._cryptoStore.doTxn(
|
await this.cryptoStore.doTxn(
|
||||||
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
|
'readwrite', [IndexedDBCryptoStore.STORE_ROOMS], (txn) => {
|
||||||
this._cryptoStore.storeEndToEndRoom(roomId, roomInfo, txn);
|
this.cryptoStore.storeEndToEndRoom(roomId, roomInfo, txn);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
@@ -480,11 +480,11 @@ export class SecretStorage extends EventEmitter {
|
|||||||
};
|
};
|
||||||
const encryptedContent = {
|
const encryptedContent = {
|
||||||
algorithm: olmlib.OLM_ALGORITHM,
|
algorithm: olmlib.OLM_ALGORITHM,
|
||||||
sender_key: this._baseApis.crypto._olmDevice.deviceCurve25519Key,
|
sender_key: this._baseApis.crypto.olmDevice.deviceCurve25519Key,
|
||||||
ciphertext: {},
|
ciphertext: {},
|
||||||
};
|
};
|
||||||
await olmlib.ensureOlmSessionsForDevices(
|
await olmlib.ensureOlmSessionsForDevices(
|
||||||
this._baseApis.crypto._olmDevice,
|
this._baseApis.crypto.olmDevice,
|
||||||
this._baseApis,
|
this._baseApis,
|
||||||
{
|
{
|
||||||
[sender]: [
|
[sender]: [
|
||||||
@@ -496,7 +496,7 @@ export class SecretStorage extends EventEmitter {
|
|||||||
encryptedContent.ciphertext,
|
encryptedContent.ciphertext,
|
||||||
this._baseApis.getUserId(),
|
this._baseApis.getUserId(),
|
||||||
this._baseApis.deviceId,
|
this._baseApis.deviceId,
|
||||||
this._baseApis.crypto._olmDevice,
|
this._baseApis.crypto.olmDevice,
|
||||||
sender,
|
sender,
|
||||||
this._baseApis.getStoredDevice(sender, deviceId),
|
this._baseApis.getStoredDevice(sender, deviceId),
|
||||||
payload,
|
payload,
|
||||||
@@ -527,7 +527,7 @@ export class SecretStorage extends EventEmitter {
|
|||||||
if (requestControl) {
|
if (requestControl) {
|
||||||
// make sure that the device that sent it is one of the devices that
|
// make sure that the device that sent it is one of the devices that
|
||||||
// we requested from
|
// we requested from
|
||||||
const deviceInfo = this._baseApis.crypto._deviceList.getDeviceByIdentityKey(
|
const deviceInfo = this._baseApis.crypto.deviceList.getDeviceByIdentityKey(
|
||||||
olmlib.OLM_ALGORITHM,
|
olmlib.OLM_ALGORITHM,
|
||||||
event.getSenderKey(),
|
event.getSenderKey(),
|
||||||
);
|
);
|
||||||
|
@@ -64,16 +64,16 @@ export class DehydrationManager {
|
|||||||
this.getDehydrationKeyFromCache();
|
this.getDehydrationKeyFromCache();
|
||||||
}
|
}
|
||||||
async getDehydrationKeyFromCache(): Promise<void> {
|
async getDehydrationKeyFromCache(): Promise<void> {
|
||||||
return await this.crypto._cryptoStore.doTxn(
|
return await this.crypto.cryptoStore.doTxn(
|
||||||
'readonly',
|
'readonly',
|
||||||
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
this.crypto._cryptoStore.getSecretStorePrivateKey(
|
this.crypto.cryptoStore.getSecretStorePrivateKey(
|
||||||
txn,
|
txn,
|
||||||
async (result) => {
|
async (result) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
const { key, keyInfo, deviceDisplayName, time } = result;
|
const { key, keyInfo, deviceDisplayName, time } = result;
|
||||||
const pickleKey = Buffer.from(this.crypto._olmDevice._pickleKey);
|
const pickleKey = Buffer.from(this.crypto.olmDevice._pickleKey);
|
||||||
const decrypted = await decryptAES(key, pickleKey, DEHYDRATION_ALGORITHM);
|
const decrypted = await decryptAES(key, pickleKey, DEHYDRATION_ALGORITHM);
|
||||||
this.key = decodeBase64(decrypted);
|
this.key = decodeBase64(decrypted);
|
||||||
this.keyInfo = keyInfo;
|
this.keyInfo = keyInfo;
|
||||||
@@ -114,11 +114,11 @@ export class DehydrationManager {
|
|||||||
this.timeoutId = undefined;
|
this.timeoutId = undefined;
|
||||||
}
|
}
|
||||||
// clear storage
|
// clear storage
|
||||||
await this.crypto._cryptoStore.doTxn(
|
await this.crypto.cryptoStore.doTxn(
|
||||||
'readwrite',
|
'readwrite',
|
||||||
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
this.crypto._cryptoStore.storeSecretStorePrivateKey(
|
this.crypto.cryptoStore.storeSecretStorePrivateKey(
|
||||||
txn, "dehydration", null,
|
txn, "dehydration", null,
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@@ -158,15 +158,15 @@ export class DehydrationManager {
|
|||||||
this.timeoutId = undefined;
|
this.timeoutId = undefined;
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const pickleKey = Buffer.from(this.crypto._olmDevice._pickleKey);
|
const pickleKey = Buffer.from(this.crypto.olmDevice._pickleKey);
|
||||||
|
|
||||||
// update the crypto store with the timestamp
|
// update the crypto store with the timestamp
|
||||||
const key = await encryptAES(encodeBase64(this.key), pickleKey, DEHYDRATION_ALGORITHM);
|
const key = await encryptAES(encodeBase64(this.key), pickleKey, DEHYDRATION_ALGORITHM);
|
||||||
await this.crypto._cryptoStore.doTxn(
|
await this.crypto.cryptoStore.doTxn(
|
||||||
'readwrite',
|
'readwrite',
|
||||||
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
[IndexedDBCryptoStore.STORE_ACCOUNT],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
this.crypto._cryptoStore.storeSecretStorePrivateKey(
|
this.crypto.cryptoStore.storeSecretStorePrivateKey(
|
||||||
txn, "dehydration",
|
txn, "dehydration",
|
||||||
{
|
{
|
||||||
keyInfo: this.keyInfo,
|
keyInfo: this.keyInfo,
|
||||||
@@ -205,7 +205,7 @@ export class DehydrationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.log("Uploading account to server");
|
logger.log("Uploading account to server");
|
||||||
const dehydrateResult = await this.crypto._baseApis.http.authedRequest(
|
const dehydrateResult = await this.crypto.baseApis.http.authedRequest(
|
||||||
undefined,
|
undefined,
|
||||||
"PUT",
|
"PUT",
|
||||||
"/dehydrated_device",
|
"/dehydrated_device",
|
||||||
@@ -223,9 +223,9 @@ export class DehydrationManager {
|
|||||||
const deviceId = dehydrateResult.device_id;
|
const deviceId = dehydrateResult.device_id;
|
||||||
logger.log("Preparing device keys", deviceId);
|
logger.log("Preparing device keys", deviceId);
|
||||||
const deviceKeys: DeviceKeys = {
|
const deviceKeys: DeviceKeys = {
|
||||||
algorithms: this.crypto._supportedAlgorithms,
|
algorithms: this.crypto.supportedAlgorithms,
|
||||||
device_id: deviceId,
|
device_id: deviceId,
|
||||||
user_id: this.crypto._userId,
|
user_id: this.crypto.userId,
|
||||||
keys: {
|
keys: {
|
||||||
[`ed25519:${deviceId}`]: e2eKeys.ed25519,
|
[`ed25519:${deviceId}`]: e2eKeys.ed25519,
|
||||||
[`curve25519:${deviceId}`]: e2eKeys.curve25519,
|
[`curve25519:${deviceId}`]: e2eKeys.curve25519,
|
||||||
@@ -233,12 +233,12 @@ export class DehydrationManager {
|
|||||||
};
|
};
|
||||||
const deviceSignature = account.sign(anotherjson.stringify(deviceKeys));
|
const deviceSignature = account.sign(anotherjson.stringify(deviceKeys));
|
||||||
deviceKeys.signatures = {
|
deviceKeys.signatures = {
|
||||||
[this.crypto._userId]: {
|
[this.crypto.userId]: {
|
||||||
[`ed25519:${deviceId}`]: deviceSignature,
|
[`ed25519:${deviceId}`]: deviceSignature,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (this.crypto._crossSigningInfo.getId("self_signing")) {
|
if (this.crypto.crossSigningInfo.getId("self_signing")) {
|
||||||
await this.crypto._crossSigningInfo.signObject(deviceKeys, "self_signing");
|
await this.crypto.crossSigningInfo.signObject(deviceKeys, "self_signing");
|
||||||
}
|
}
|
||||||
|
|
||||||
logger.log("Preparing one-time keys");
|
logger.log("Preparing one-time keys");
|
||||||
@@ -247,7 +247,7 @@ export class DehydrationManager {
|
|||||||
const k: OneTimeKey = { key };
|
const k: OneTimeKey = { key };
|
||||||
const signature = account.sign(anotherjson.stringify(k));
|
const signature = account.sign(anotherjson.stringify(k));
|
||||||
k.signatures = {
|
k.signatures = {
|
||||||
[this.crypto._userId]: {
|
[this.crypto.userId]: {
|
||||||
[`ed25519:${deviceId}`]: signature,
|
[`ed25519:${deviceId}`]: signature,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -260,7 +260,7 @@ export class DehydrationManager {
|
|||||||
const k: OneTimeKey = { key, fallback: true };
|
const k: OneTimeKey = { key, fallback: true };
|
||||||
const signature = account.sign(anotherjson.stringify(k));
|
const signature = account.sign(anotherjson.stringify(k));
|
||||||
k.signatures = {
|
k.signatures = {
|
||||||
[this.crypto._userId]: {
|
[this.crypto.userId]: {
|
||||||
[`ed25519:${deviceId}`]: signature,
|
[`ed25519:${deviceId}`]: signature,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
@@ -268,7 +268,7 @@ export class DehydrationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
logger.log("Uploading keys to server");
|
logger.log("Uploading keys to server");
|
||||||
await this.crypto._baseApis.http.authedRequest(
|
await this.crypto.baseApis.http.authedRequest(
|
||||||
undefined,
|
undefined,
|
||||||
"POST",
|
"POST",
|
||||||
"/keys/upload/" + encodeURI(deviceId),
|
"/keys/upload/" + encodeURI(deviceId),
|
||||||
|
@@ -1,168 +0,0 @@
|
|||||||
/*
|
|
||||||
Copyright 2016 OpenMarket Ltd
|
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @module crypto/deviceinfo
|
|
||||||
*/
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Information about a user's device
|
|
||||||
*
|
|
||||||
* @constructor
|
|
||||||
* @alias module:crypto/deviceinfo
|
|
||||||
*
|
|
||||||
* @property {string} deviceId the ID of this device
|
|
||||||
*
|
|
||||||
* @property {string[]} algorithms list of algorithms supported by this device
|
|
||||||
*
|
|
||||||
* @property {Object.<string,string>} keys a map from
|
|
||||||
* <key type>:<id> -> <base64-encoded key>>
|
|
||||||
*
|
|
||||||
* @property {module:crypto/deviceinfo.DeviceVerification} verified
|
|
||||||
* whether the device has been verified/blocked by the user
|
|
||||||
*
|
|
||||||
* @property {boolean} known
|
|
||||||
* whether the user knows of this device's existence (useful when warning
|
|
||||||
* the user that a user has added new devices)
|
|
||||||
*
|
|
||||||
* @property {Object} unsigned additional data from the homeserver
|
|
||||||
*
|
|
||||||
* @param {string} deviceId id of the device
|
|
||||||
*/
|
|
||||||
export function DeviceInfo(deviceId) {
|
|
||||||
// you can't change the deviceId
|
|
||||||
Object.defineProperty(this, 'deviceId', {
|
|
||||||
enumerable: true,
|
|
||||||
value: deviceId,
|
|
||||||
});
|
|
||||||
|
|
||||||
this.algorithms = [];
|
|
||||||
this.keys = {};
|
|
||||||
this.verified = DeviceVerification.UNVERIFIED;
|
|
||||||
this.known = false;
|
|
||||||
this.unsigned = {};
|
|
||||||
this.signatures = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* rehydrate a DeviceInfo from the session store
|
|
||||||
*
|
|
||||||
* @param {object} obj raw object from session store
|
|
||||||
* @param {string} deviceId id of the device
|
|
||||||
*
|
|
||||||
* @return {module:crypto~DeviceInfo} new DeviceInfo
|
|
||||||
*/
|
|
||||||
DeviceInfo.fromStorage = function(obj, deviceId) {
|
|
||||||
const res = new DeviceInfo(deviceId);
|
|
||||||
for (const prop in obj) {
|
|
||||||
if (obj.hasOwnProperty(prop)) {
|
|
||||||
res[prop] = obj[prop];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Prepare a DeviceInfo for JSON serialisation in the session store
|
|
||||||
*
|
|
||||||
* @return {object} deviceinfo with non-serialised members removed
|
|
||||||
*/
|
|
||||||
DeviceInfo.prototype.toStorage = function() {
|
|
||||||
return {
|
|
||||||
algorithms: this.algorithms,
|
|
||||||
keys: this.keys,
|
|
||||||
verified: this.verified,
|
|
||||||
known: this.known,
|
|
||||||
unsigned: this.unsigned,
|
|
||||||
signatures: this.signatures,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the fingerprint for this device (ie, the Ed25519 key)
|
|
||||||
*
|
|
||||||
* @return {string} base64-encoded fingerprint of this device
|
|
||||||
*/
|
|
||||||
DeviceInfo.prototype.getFingerprint = function() {
|
|
||||||
return this.keys["ed25519:" + this.deviceId];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the identity key for this device (ie, the Curve25519 key)
|
|
||||||
*
|
|
||||||
* @return {string} base64-encoded identity key of this device
|
|
||||||
*/
|
|
||||||
DeviceInfo.prototype.getIdentityKey = function() {
|
|
||||||
return this.keys["curve25519:" + this.deviceId];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the configured display name for this device, if any
|
|
||||||
*
|
|
||||||
* @return {string?} displayname
|
|
||||||
*/
|
|
||||||
DeviceInfo.prototype.getDisplayName = function() {
|
|
||||||
return this.unsigned.device_display_name || null;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if this device is blocked
|
|
||||||
*
|
|
||||||
* @return {Boolean} true if blocked
|
|
||||||
*/
|
|
||||||
DeviceInfo.prototype.isBlocked = function() {
|
|
||||||
return this.verified == DeviceVerification.BLOCKED;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if this device is verified
|
|
||||||
*
|
|
||||||
* @return {Boolean} true if verified
|
|
||||||
*/
|
|
||||||
DeviceInfo.prototype.isVerified = function() {
|
|
||||||
return this.verified == DeviceVerification.VERIFIED;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if this device is unverified
|
|
||||||
*
|
|
||||||
* @return {Boolean} true if unverified
|
|
||||||
*/
|
|
||||||
DeviceInfo.prototype.isUnverified = function() {
|
|
||||||
return this.verified == DeviceVerification.UNVERIFIED;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the user knows about this device's existence
|
|
||||||
*
|
|
||||||
* @return {Boolean} true if known
|
|
||||||
*/
|
|
||||||
DeviceInfo.prototype.isKnown = function() {
|
|
||||||
return this.known == true;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @enum
|
|
||||||
*/
|
|
||||||
DeviceInfo.DeviceVerification = {
|
|
||||||
VERIFIED: 1,
|
|
||||||
UNVERIFIED: 0,
|
|
||||||
BLOCKED: -1,
|
|
||||||
};
|
|
||||||
|
|
||||||
const DeviceVerification = DeviceInfo.DeviceVerification;
|
|
||||||
|
|
175
src/crypto/deviceinfo.ts
Normal file
175
src/crypto/deviceinfo.ts
Normal file
@@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 - 2021 The Matrix.org Foundation C.I.C.
|
||||||
|
|
||||||
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
you may not use this file except in compliance with the License.
|
||||||
|
You may obtain a copy of the License at
|
||||||
|
|
||||||
|
http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
|
||||||
|
Unless required by applicable law or agreed to in writing, software
|
||||||
|
distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
See the License for the specific language governing permissions and
|
||||||
|
limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @module crypto/deviceinfo
|
||||||
|
*/
|
||||||
|
|
||||||
|
export interface IDevice {
|
||||||
|
keys: Record<string, string>;
|
||||||
|
algorithms: string[];
|
||||||
|
verified: DeviceVerification;
|
||||||
|
known: boolean;
|
||||||
|
unsigned?: Record<string, any>;
|
||||||
|
signatures?: Record<string, string>;
|
||||||
|
}
|
||||||
|
|
||||||
|
enum DeviceVerification {
|
||||||
|
Blocked = -1,
|
||||||
|
Unverified = 0,
|
||||||
|
Verified = 1,
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Information about a user's device
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @alias module:crypto/deviceinfo
|
||||||
|
*
|
||||||
|
* @property {string} deviceId the ID of this device
|
||||||
|
*
|
||||||
|
* @property {string[]} algorithms list of algorithms supported by this device
|
||||||
|
*
|
||||||
|
* @property {Object.<string,string>} keys a map from
|
||||||
|
* <key type>:<id> -> <base64-encoded key>>
|
||||||
|
*
|
||||||
|
* @property {module:crypto/deviceinfo.DeviceVerification} verified
|
||||||
|
* whether the device has been verified/blocked by the user
|
||||||
|
*
|
||||||
|
* @property {boolean} known
|
||||||
|
* whether the user knows of this device's existence (useful when warning
|
||||||
|
* the user that a user has added new devices)
|
||||||
|
*
|
||||||
|
* @property {Object} unsigned additional data from the homeserver
|
||||||
|
*
|
||||||
|
* @param {string} deviceId id of the device
|
||||||
|
*/
|
||||||
|
export class DeviceInfo {
|
||||||
|
/**
|
||||||
|
* rehydrate a DeviceInfo from the session store
|
||||||
|
*
|
||||||
|
* @param {object} obj raw object from session store
|
||||||
|
* @param {string} deviceId id of the device
|
||||||
|
*
|
||||||
|
* @return {module:crypto~DeviceInfo} new DeviceInfo
|
||||||
|
*/
|
||||||
|
public static fromStorage(obj: IDevice, deviceId: string): DeviceInfo {
|
||||||
|
const res = new DeviceInfo(deviceId);
|
||||||
|
for (const prop in obj) {
|
||||||
|
if (obj.hasOwnProperty(prop)) {
|
||||||
|
res[prop] = obj[prop];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @enum
|
||||||
|
*/
|
||||||
|
public static DeviceVerification = {
|
||||||
|
VERIFIED: DeviceVerification.Verified,
|
||||||
|
UNVERIFIED: DeviceVerification.Unverified,
|
||||||
|
BLOCKED: DeviceVerification.Blocked,
|
||||||
|
};
|
||||||
|
|
||||||
|
public algorithms: string[];
|
||||||
|
public keys: Record<string, string> = {};
|
||||||
|
public verified = DeviceVerification.Unverified;
|
||||||
|
public known = false;
|
||||||
|
public unsigned: Record<string, any> = {};
|
||||||
|
public signatures: Record<string, string> = {};
|
||||||
|
|
||||||
|
constructor(public readonly deviceId: string) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prepare a DeviceInfo for JSON serialisation in the session store
|
||||||
|
*
|
||||||
|
* @return {object} deviceinfo with non-serialised members removed
|
||||||
|
*/
|
||||||
|
public toStorage(): IDevice {
|
||||||
|
return {
|
||||||
|
algorithms: this.algorithms,
|
||||||
|
keys: this.keys,
|
||||||
|
verified: this.verified,
|
||||||
|
known: this.known,
|
||||||
|
unsigned: this.unsigned,
|
||||||
|
signatures: this.signatures,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the fingerprint for this device (ie, the Ed25519 key)
|
||||||
|
*
|
||||||
|
* @return {string} base64-encoded fingerprint of this device
|
||||||
|
*/
|
||||||
|
public getFingerprint(): string {
|
||||||
|
return this.keys["ed25519:" + this.deviceId];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the identity key for this device (ie, the Curve25519 key)
|
||||||
|
*
|
||||||
|
* @return {string} base64-encoded identity key of this device
|
||||||
|
*/
|
||||||
|
public getIdentityKey(): string {
|
||||||
|
return this.keys["curve25519:" + this.deviceId];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the configured display name for this device, if any
|
||||||
|
*
|
||||||
|
* @return {string?} displayname
|
||||||
|
*/
|
||||||
|
public getDisplayName(): string | null {
|
||||||
|
return this.unsigned.device_display_name || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this device is blocked
|
||||||
|
*
|
||||||
|
* @return {Boolean} true if blocked
|
||||||
|
*/
|
||||||
|
public isBlocked(): boolean {
|
||||||
|
return this.verified == DeviceVerification.Blocked;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this device is verified
|
||||||
|
*
|
||||||
|
* @return {Boolean} true if verified
|
||||||
|
*/
|
||||||
|
public isVerified(): boolean {
|
||||||
|
return this.verified == DeviceVerification.Verified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if this device is unverified
|
||||||
|
*
|
||||||
|
* @return {Boolean} true if unverified
|
||||||
|
*/
|
||||||
|
public isUnverified(): boolean {
|
||||||
|
return this.verified == DeviceVerification.Unverified;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns true if the user knows about this device's existence
|
||||||
|
*
|
||||||
|
* @return {Boolean} true if known
|
||||||
|
*/
|
||||||
|
public isKnown(): boolean {
|
||||||
|
return this.known === true;
|
||||||
|
}
|
||||||
|
}
|
@@ -29,7 +29,7 @@ import { logger } from '../logger';
|
|||||||
import { OlmDevice } from "./OlmDevice";
|
import { OlmDevice } from "./OlmDevice";
|
||||||
import * as olmlib from "./olmlib";
|
import * as olmlib from "./olmlib";
|
||||||
import { DeviceList } from "./DeviceList";
|
import { DeviceList } from "./DeviceList";
|
||||||
import { DeviceInfo } from "./deviceinfo";
|
import { DeviceInfo, IDevice } from "./deviceinfo";
|
||||||
import * as algorithms from "./algorithms";
|
import * as algorithms from "./algorithms";
|
||||||
import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from './CrossSigning';
|
import { createCryptoStoreCacheCallbacks, CrossSigningInfo, DeviceTrustLevel, UserTrustLevel } from './CrossSigning';
|
||||||
import { EncryptionSetupBuilder } from "./EncryptionSetup";
|
import { EncryptionSetupBuilder } from "./EncryptionSetup";
|
||||||
@@ -420,9 +420,7 @@ export class Crypto extends EventEmitter {
|
|||||||
};
|
};
|
||||||
|
|
||||||
myDevices[this.deviceId] = deviceInfo;
|
myDevices[this.deviceId] = deviceInfo;
|
||||||
this.deviceList.storeDevicesForUser(
|
this.deviceList.storeDevicesForUser(this.userId, myDevices);
|
||||||
this.userId, myDevices,
|
|
||||||
);
|
|
||||||
this.deviceList.saveIfDirty();
|
this.deviceList.saveIfDirty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -922,10 +920,7 @@ export class Crypto extends EventEmitter {
|
|||||||
await this.crossSigningInfo.getCrossSigningKeysFromCache();
|
await this.crossSigningInfo.getCrossSigningKeysFromCache();
|
||||||
// This is writing to in-memory account data in
|
// This is writing to in-memory account data in
|
||||||
// builder.accountDataClientAdapter so won't fail
|
// builder.accountDataClientAdapter so won't fail
|
||||||
await CrossSigningInfo.storeInSecretStorage(
|
await CrossSigningInfo.storeInSecretStorage(crossSigningPrivateKeys, secretStorage);
|
||||||
crossSigningPrivateKeys,
|
|
||||||
secretStorage,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (setupNewKeyBackup && !keyBackupInfo) {
|
if (setupNewKeyBackup && !keyBackupInfo) {
|
||||||
@@ -1172,7 +1167,7 @@ export class Crypto extends EventEmitter {
|
|||||||
// FIXME: do this in batches
|
// FIXME: do this in batches
|
||||||
const users = {};
|
const users = {};
|
||||||
for (const [userId, crossSigningInfo]
|
for (const [userId, crossSigningInfo]
|
||||||
of Object.entries(this.deviceList._crossSigningInfo)) {
|
of Object.entries(this.deviceList.crossSigningInfo)) {
|
||||||
const upgradeInfo = await this.checkForDeviceVerificationUpgrade(
|
const upgradeInfo = await this.checkForDeviceVerificationUpgrade(
|
||||||
userId, CrossSigningInfo.fromStorage(crossSigningInfo, userId),
|
userId, CrossSigningInfo.fromStorage(crossSigningInfo, userId),
|
||||||
);
|
);
|
||||||
@@ -1248,7 +1243,7 @@ export class Crypto extends EventEmitter {
|
|||||||
private async checkForValidDeviceSignature(
|
private async checkForValidDeviceSignature(
|
||||||
userId: string,
|
userId: string,
|
||||||
key: any, // TODO types
|
key: any, // TODO types
|
||||||
devices: Record<string, DeviceInfo>,
|
devices: Record<string, IDevice>,
|
||||||
): Promise<string[]> {
|
): Promise<string[]> {
|
||||||
const deviceIds: string[] = [];
|
const deviceIds: string[] = [];
|
||||||
if (devices && key.signatures && key.signatures[userId]) {
|
if (devices && key.signatures && key.signatures[userId]) {
|
||||||
@@ -1934,7 +1929,7 @@ export class Crypto extends EventEmitter {
|
|||||||
public downloadKeys(
|
public downloadKeys(
|
||||||
userIds: string[],
|
userIds: string[],
|
||||||
forceDownload?: boolean,
|
forceDownload?: boolean,
|
||||||
): Promise<Record<string, Record<string, DeviceInfo>>> {
|
): Promise<Record<string, Record<string, IDevice>>> {
|
||||||
return this.deviceList.downloadKeys(userIds, forceDownload);
|
return this.deviceList.downloadKeys(userIds, forceDownload);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2003,7 +1998,7 @@ export class Crypto extends EventEmitter {
|
|||||||
verified?: boolean,
|
verified?: boolean,
|
||||||
blocked?: boolean,
|
blocked?: boolean,
|
||||||
known?: boolean,
|
known?: boolean,
|
||||||
): Promise<DeviceInfo> {
|
): Promise<DeviceInfo | CrossSigningInfo> {
|
||||||
// get rid of any `undefined`s here so we can just check
|
// get rid of any `undefined`s here so we can just check
|
||||||
// for null rather than null or undefined
|
// for null rather than null or undefined
|
||||||
if (verified === undefined) verified = null;
|
if (verified === undefined) verified = null;
|
||||||
@@ -2068,7 +2063,7 @@ export class Crypto extends EventEmitter {
|
|||||||
// This will emit events when it comes back down the sync
|
// This will emit events when it comes back down the sync
|
||||||
// (we could do local echo to speed things up)
|
// (we could do local echo to speed things up)
|
||||||
}
|
}
|
||||||
return device;
|
return device as any; // TODO types
|
||||||
} else {
|
} else {
|
||||||
return xsk;
|
return xsk;
|
||||||
}
|
}
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
Copyright 2018 New Vector Ltd
|
Copyright 2018 - 2021 The Matrix.org Foundation C.I.C.
|
||||||
Copyright 2019 The Matrix.org Foundation C.I.C.
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
you may not use this file except in compliance with the License.
|
you may not use this file except in compliance with the License.
|
||||||
@@ -21,7 +20,21 @@ const DEFAULT_ITERATIONS = 500000;
|
|||||||
|
|
||||||
const DEFAULT_BITSIZE = 256;
|
const DEFAULT_BITSIZE = 256;
|
||||||
|
|
||||||
export async function keyFromAuthData(authData, password) {
|
/* eslint-disable camelcase */
|
||||||
|
interface IAuthData {
|
||||||
|
private_key_salt: string;
|
||||||
|
private_key_iterations: number;
|
||||||
|
private_key_bits?: number;
|
||||||
|
}
|
||||||
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
|
interface IKey {
|
||||||
|
key: Uint8Array;
|
||||||
|
salt: string;
|
||||||
|
iterations: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function keyFromAuthData(authData: IAuthData, password: string): Promise<Uint8Array> {
|
||||||
if (!global.Olm) {
|
if (!global.Olm) {
|
||||||
throw new Error("Olm is not available");
|
throw new Error("Olm is not available");
|
||||||
}
|
}
|
||||||
@@ -40,7 +53,7 @@ export async function keyFromAuthData(authData, password) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function keyFromPassphrase(password) {
|
export async function keyFromPassphrase(password: string): Promise<IKey> {
|
||||||
if (!global.Olm) {
|
if (!global.Olm) {
|
||||||
throw new Error("Olm is not available");
|
throw new Error("Olm is not available");
|
||||||
}
|
}
|
||||||
@@ -52,7 +65,12 @@ export async function keyFromPassphrase(password) {
|
|||||||
return { key, salt, iterations: DEFAULT_ITERATIONS };
|
return { key, salt, iterations: DEFAULT_ITERATIONS };
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function deriveKey(password, salt, iterations, numBits = DEFAULT_BITSIZE) {
|
export async function deriveKey(
|
||||||
|
password: string,
|
||||||
|
salt: string,
|
||||||
|
iterations: number,
|
||||||
|
numBits = DEFAULT_BITSIZE,
|
||||||
|
): Promise<Uint8Array> {
|
||||||
const subtleCrypto = global.crypto.subtle;
|
const subtleCrypto = global.crypto.subtle;
|
||||||
const TextEncoder = global.TextEncoder;
|
const TextEncoder = global.TextEncoder;
|
||||||
if (!subtleCrypto || !TextEncoder) {
|
if (!subtleCrypto || !TextEncoder) {
|
@@ -31,17 +31,22 @@ export interface IKeyBackupRoomSessions {
|
|||||||
[sessionId: string]: IKeyBackupSession;
|
[sessionId: string]: IKeyBackupSession;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* eslint-disable camelcase */
|
||||||
export interface IKeyBackupVersion {
|
export interface IKeyBackupVersion {
|
||||||
algorithm: string;
|
algorithm: string;
|
||||||
auth_data: { // eslint-disable-line camelcase
|
auth_data: {
|
||||||
public_key: string; // eslint-disable-line camelcase
|
public_key: string;
|
||||||
signatures: ISignatures;
|
signatures: ISignatures;
|
||||||
|
private_key_salt: string;
|
||||||
|
private_key_iterations: number;
|
||||||
|
private_key_bits?: number;
|
||||||
};
|
};
|
||||||
count: number;
|
count: number;
|
||||||
etag: string;
|
etag: string;
|
||||||
version: string; // number contained within
|
version: string; // number contained within
|
||||||
recovery_key: string; // eslint-disable-line camelcase
|
recovery_key: string;
|
||||||
}
|
}
|
||||||
|
/* eslint-enable camelcase */
|
||||||
|
|
||||||
export interface IKeyBackupPrepareOpts {
|
export interface IKeyBackupPrepareOpts {
|
||||||
secureSecretStorage: boolean;
|
secureSecretStorage: boolean;
|
||||||
|
@@ -292,7 +292,7 @@ export class VerificationBase extends EventEmitter {
|
|||||||
await verifier(keyId, device, keyInfo);
|
await verifier(keyId, device, keyInfo);
|
||||||
verifiedDevices.push(deviceId);
|
verifiedDevices.push(deviceId);
|
||||||
} else {
|
} else {
|
||||||
const crossSigningInfo = this._baseApis.crypto._deviceList
|
const crossSigningInfo = this._baseApis.crypto.deviceList
|
||||||
.getStoredCrossSigningForUser(userId);
|
.getStoredCrossSigningForUser(userId);
|
||||||
if (crossSigningInfo && crossSigningInfo.getId() === deviceId) {
|
if (crossSigningInfo && crossSigningInfo.getId() === deviceId) {
|
||||||
await verifier(keyId, DeviceInfo.fromStorage({
|
await verifier(keyId, DeviceInfo.fromStorage({
|
||||||
|
@@ -19,6 +19,7 @@ import { MemoryStore } from "./store/memory";
|
|||||||
import { MatrixScheduler } from "./scheduler";
|
import { MatrixScheduler } from "./scheduler";
|
||||||
import { MatrixClient } from "./client";
|
import { MatrixClient } from "./client";
|
||||||
import { ICreateClientOpts } from "./client";
|
import { ICreateClientOpts } from "./client";
|
||||||
|
import { DeviceTrustLevel } from "./crypto/CrossSigning";
|
||||||
|
|
||||||
export * from "./client";
|
export * from "./client";
|
||||||
export * from "./http-api";
|
export * from "./http-api";
|
||||||
@@ -99,7 +100,7 @@ export function setCryptoStoreFactory(fac) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface ICryptoCallbacks {
|
export interface ICryptoCallbacks {
|
||||||
getCrossSigningKey?: (keyType: string, pubKey: Uint8Array) => Promise<Uint8Array>;
|
getCrossSigningKey?: (keyType: string, pubKey: string) => Promise<Uint8Array>;
|
||||||
saveCrossSigningKeys?: (keys: Record<string, Uint8Array>) => void;
|
saveCrossSigningKeys?: (keys: Record<string, Uint8Array>) => void;
|
||||||
shouldUpgradeDeviceVerifications?: (
|
shouldUpgradeDeviceVerifications?: (
|
||||||
users: Record<string, any>
|
users: Record<string, any>
|
||||||
@@ -112,7 +113,7 @@ export interface ICryptoCallbacks {
|
|||||||
) => void;
|
) => void;
|
||||||
onSecretRequested?: (
|
onSecretRequested?: (
|
||||||
userId: string, deviceId: string,
|
userId: string, deviceId: string,
|
||||||
requestId: string, secretName: string, deviceTrust: IDeviceTrustLevel
|
requestId: string, secretName: string, deviceTrust: DeviceTrustLevel
|
||||||
) => Promise<string>;
|
) => Promise<string>;
|
||||||
getDehydrationKey?: (
|
getDehydrationKey?: (
|
||||||
keyInfo: ISecretStorageKeyInfo,
|
keyInfo: ISecretStorageKeyInfo,
|
||||||
@@ -132,14 +133,6 @@ export interface ISecretStorageKeyInfo {
|
|||||||
mac?: string;
|
mac?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move this to `CrossSigning` once converted
|
|
||||||
export interface IDeviceTrustLevel {
|
|
||||||
isVerified(): boolean;
|
|
||||||
isCrossSigningVerified(): boolean;
|
|
||||||
isLocallyVerified(): boolean;
|
|
||||||
isTofu(): boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Construct a Matrix Client. Similar to {@link module:client.MatrixClient}
|
* Construct a Matrix Client. Similar to {@link module:client.MatrixClient}
|
||||||
* except that the 'request', 'store' and 'scheduler' dependencies are satisfied.
|
* except that the 'request', 'store' and 'scheduler' dependencies are satisfied.
|
||||||
|
10
src/utils.ts
10
src/utils.ts
@@ -435,12 +435,18 @@ export function isNullOrUndefined(val: any): boolean {
|
|||||||
return val === null || val === undefined;
|
return val === null || val === undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IDeferred<T> {
|
||||||
|
resolve: (value: T) => void;
|
||||||
|
reject: (any) => void;
|
||||||
|
promise: Promise<T>;
|
||||||
|
}
|
||||||
|
|
||||||
// Returns a Deferred
|
// Returns a Deferred
|
||||||
export function defer() {
|
export function defer<T>(): IDeferred<T> {
|
||||||
let resolve;
|
let resolve;
|
||||||
let reject;
|
let reject;
|
||||||
|
|
||||||
const promise = new Promise((_resolve, _reject) => {
|
const promise = new Promise<T>((_resolve, _reject) => {
|
||||||
resolve = _resolve;
|
resolve = _resolve;
|
||||||
reject = _reject;
|
reject = _reject;
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user