You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-11-26 17:03:12 +03:00
Merge remote-tracking branch 'origin/develop' into dbkr/e2e_backups
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- node # Latest stable version of nodejs.
|
- "10.11.0"
|
||||||
script:
|
script:
|
||||||
- ./travis.sh
|
- ./travis.sh
|
||||||
|
|||||||
32
CHANGELOG.md
32
CHANGELOG.md
@@ -1,3 +1,35 @@
|
|||||||
|
Changes in [0.13.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.13.1) (2018-11-14)
|
||||||
|
==================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.13.0...v0.13.1)
|
||||||
|
|
||||||
|
* Add function to get currently joined rooms.
|
||||||
|
[\#779](https://github.com/matrix-org/matrix-js-sdk/pull/779)
|
||||||
|
|
||||||
|
Changes in [0.13.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.13.0) (2018-11-15)
|
||||||
|
==================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.12.1...v0.13.0)
|
||||||
|
|
||||||
|
BREAKING CHANGE
|
||||||
|
----------------
|
||||||
|
* `MatrixClient::login` now sets client `access_token` and `user_id` following successful login with username and password.
|
||||||
|
|
||||||
|
Changes in [0.12.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.12.1) (2018-10-29)
|
||||||
|
==================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.12.1-rc.1...v0.12.1)
|
||||||
|
|
||||||
|
* No changes since rc.1
|
||||||
|
|
||||||
|
Changes in [0.12.1-rc.1](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.12.1-rc.1) (2018-10-24)
|
||||||
|
============================================================================================================
|
||||||
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.12.0...v0.12.1-rc.1)
|
||||||
|
|
||||||
|
* Add repository type to package.json to make it valid
|
||||||
|
[\#762](https://github.com/matrix-org/matrix-js-sdk/pull/762)
|
||||||
|
* Add getMediaConfig()
|
||||||
|
[\#761](https://github.com/matrix-org/matrix-js-sdk/pull/761)
|
||||||
|
* add new examples, to be expanded into a post
|
||||||
|
[\#739](https://github.com/matrix-org/matrix-js-sdk/pull/739)
|
||||||
|
|
||||||
Changes in [0.12.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.12.0) (2018-10-16)
|
Changes in [0.12.0](https://github.com/matrix-org/matrix-js-sdk/releases/tag/v0.12.0) (2018-10-16)
|
||||||
==================================================================================================
|
==================================================================================================
|
||||||
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.12.0-rc.1...v0.12.0)
|
[Full Changelog](https://github.com/matrix-org/matrix-js-sdk/compare/v0.12.0-rc.1...v0.12.0)
|
||||||
|
|||||||
@@ -1,5 +1,17 @@
|
|||||||
var matrixcs = require("./lib/matrix");
|
var matrixcs = require("./lib/matrix");
|
||||||
matrixcs.request(require("browser-request"));
|
const request = require('browser-request');
|
||||||
|
const queryString = require('qs');
|
||||||
|
|
||||||
|
matrixcs.request(function(opts, fn) {
|
||||||
|
// We manually fix the query string for browser-request because
|
||||||
|
// it doesn't correctly handle cases like ?via=one&via=two. Instead
|
||||||
|
// we mimic `request`'s query string interface to make it all work
|
||||||
|
// as expected.
|
||||||
|
// browser-request will happily take the constructed string as the
|
||||||
|
// query string without trying to modify it further.
|
||||||
|
opts.qs = queryString.stringify(opts.qs || {}, opts.qsStringifyOptions);
|
||||||
|
return request(opts, fn);
|
||||||
|
});
|
||||||
|
|
||||||
// just *accessing* indexedDB throws an exception in firefox with
|
// just *accessing* indexedDB throws an exception in firefox with
|
||||||
// indexeddb disabled.
|
// indexeddb disabled.
|
||||||
|
|||||||
11
package.json
11
package.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "matrix-js-sdk",
|
"name": "matrix-js-sdk",
|
||||||
"version": "0.12.0",
|
"version": "0.13.1",
|
||||||
"description": "Matrix Client-Server SDK for Javascript",
|
"description": "Matrix Client-Server SDK for Javascript",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -59,15 +59,16 @@
|
|||||||
"bs58": "^4.0.1",
|
"bs58": "^4.0.1",
|
||||||
"content-type": "^1.0.2",
|
"content-type": "^1.0.2",
|
||||||
"loglevel": "1.6.1",
|
"loglevel": "1.6.1",
|
||||||
"request": "^2.53.0"
|
"qs": "^6.5.2",
|
||||||
|
"request": "^2.88.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"babel-cli": "^6.18.0",
|
"babel-cli": "^6.18.0",
|
||||||
"babel-eslint": "^7.1.1",
|
"babel-eslint": "^8.1.1",
|
||||||
"babel-plugin-transform-async-to-bluebird": "^1.1.1",
|
"babel-plugin-transform-async-to-bluebird": "^1.1.1",
|
||||||
"babel-plugin-transform-runtime": "^6.23.0",
|
"babel-plugin-transform-runtime": "^6.23.0",
|
||||||
"babel-preset-es2015": "^6.18.0",
|
"babel-preset-es2015": "^6.18.0",
|
||||||
"browserify": "^14.0.0",
|
"browserify": "^16.2.3",
|
||||||
"browserify-shim": "^3.8.13",
|
"browserify-shim": "^3.8.13",
|
||||||
"eslint": "^3.13.1",
|
"eslint": "^3.13.1",
|
||||||
"eslint-config-google": "^0.7.1",
|
"eslint-config-google": "^0.7.1",
|
||||||
@@ -83,7 +84,7 @@
|
|||||||
"source-map-support": "^0.4.11",
|
"source-map-support": "^0.4.11",
|
||||||
"sourceify": "^0.1.0",
|
"sourceify": "^0.1.0",
|
||||||
"uglify-js": "^2.8.26",
|
"uglify-js": "^2.8.26",
|
||||||
"watchify": "^3.2.1"
|
"watchify": "^3.11.0"
|
||||||
},
|
},
|
||||||
"browserify": {
|
"browserify": {
|
||||||
"transform": [
|
"transform": [
|
||||||
|
|||||||
@@ -817,8 +817,14 @@ describe("megolm", function() {
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Grab the event that we'll need to resend
|
||||||
|
const room = aliceTestClient.client.getRoom(ROOM_ID);
|
||||||
|
const pendingEvents = room.getPendingEvents();
|
||||||
|
expect(pendingEvents.length).toEqual(1);
|
||||||
|
const unsentEvent = pendingEvents[0];
|
||||||
|
|
||||||
return Promise.all([
|
return Promise.all([
|
||||||
aliceTestClient.client.sendTextMessage(ROOM_ID, 'test'),
|
aliceTestClient.client.resendEvent(unsentEvent, room),
|
||||||
|
|
||||||
// the crypto stuff can take a while, so give the requests a whole second.
|
// the crypto stuff can take a while, so give the requests a whole second.
|
||||||
aliceTestClient.httpBackend.flushAllExpected({
|
aliceTestClient.httpBackend.flushAllExpected({
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
|
|
||||||
"use strict";
|
|
||||||
import 'source-map-support/register';
|
import 'source-map-support/register';
|
||||||
import Crypto from '../../lib/crypto';
|
import Crypto from '../../lib/crypto';
|
||||||
import expect from 'expect';
|
import expect from 'expect';
|
||||||
|
|
||||||
|
import WebStorageSessionStore from '../../lib/store/session/webstorage';
|
||||||
|
import MemoryCryptoStore from '../../lib/crypto/store/memory-crypto-store.js';
|
||||||
|
import MockStorageApi from '../MockStorageApi';
|
||||||
|
|
||||||
|
const EventEmitter = require("events").EventEmitter;
|
||||||
|
|
||||||
const sdk = require("../..");
|
const sdk = require("../..");
|
||||||
|
|
||||||
const Olm = global.Olm;
|
const Olm = global.Olm;
|
||||||
@@ -20,4 +24,96 @@ describe("Crypto", function() {
|
|||||||
it("Crypto exposes the correct olm library version", function() {
|
it("Crypto exposes the correct olm library version", function() {
|
||||||
expect(Crypto.getOlmVersion()[0]).toEqual(3);
|
expect(Crypto.getOlmVersion()[0]).toEqual(3);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
describe('Session management', function() {
|
||||||
|
const otkResponse = {
|
||||||
|
one_time_keys: {
|
||||||
|
'@alice:home.server': {
|
||||||
|
aliceDevice: {
|
||||||
|
'signed_curve25519:FLIBBLE': {
|
||||||
|
key: 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI',
|
||||||
|
signatures: {
|
||||||
|
'@alice:home.server': {
|
||||||
|
'ed25519:aliceDevice': 'totally a valid signature',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
let crypto;
|
||||||
|
let mockBaseApis;
|
||||||
|
let mockRoomList;
|
||||||
|
|
||||||
|
let fakeEmitter;
|
||||||
|
|
||||||
|
beforeEach(async function() {
|
||||||
|
const mockStorage = new MockStorageApi();
|
||||||
|
const sessionStore = new WebStorageSessionStore(mockStorage);
|
||||||
|
const cryptoStore = new MemoryCryptoStore(mockStorage);
|
||||||
|
|
||||||
|
cryptoStore.storeEndToEndDeviceData({
|
||||||
|
devices: {
|
||||||
|
'@bob:home.server': {
|
||||||
|
'BOBDEVICE': {
|
||||||
|
keys: {
|
||||||
|
'curve25519:BOBDEVICE': 'this is a key',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
trackingStatus: {},
|
||||||
|
});
|
||||||
|
|
||||||
|
mockBaseApis = {
|
||||||
|
sendToDevice: expect.createSpy(),
|
||||||
|
};
|
||||||
|
mockRoomList = {};
|
||||||
|
|
||||||
|
fakeEmitter = new EventEmitter();
|
||||||
|
|
||||||
|
crypto = new Crypto(
|
||||||
|
mockBaseApis,
|
||||||
|
sessionStore,
|
||||||
|
"@alice:home.server",
|
||||||
|
"FLIBBLE",
|
||||||
|
sessionStore,
|
||||||
|
cryptoStore,
|
||||||
|
mockRoomList,
|
||||||
|
);
|
||||||
|
crypto.registerEventHandlers(fakeEmitter);
|
||||||
|
await crypto.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(async function() {
|
||||||
|
await crypto.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("restarts wedged Olm sessions", async function() {
|
||||||
|
const prom = new Promise((resolve) => {
|
||||||
|
mockBaseApis.claimOneTimeKeys = function() {
|
||||||
|
resolve();
|
||||||
|
return otkResponse;
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
fakeEmitter.emit('toDeviceEvent', {
|
||||||
|
getType: expect.createSpy().andReturn('m.room.message'),
|
||||||
|
getContent: expect.createSpy().andReturn({
|
||||||
|
msgtype: 'm.bad.encrypted',
|
||||||
|
}),
|
||||||
|
getWireContent: expect.createSpy().andReturn({
|
||||||
|
algorithm: 'm.olm.v1.curve25519-aes-sha2',
|
||||||
|
sender_key: 'this is a key',
|
||||||
|
}),
|
||||||
|
getSender: expect.createSpy().andReturn('@bob:home.server'),
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("waiting");
|
||||||
|
await prom;
|
||||||
|
console.log("done");
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import Crypto from '../../../../lib/crypto';
|
|||||||
|
|
||||||
const MatrixEvent = sdk.MatrixEvent;
|
const MatrixEvent = sdk.MatrixEvent;
|
||||||
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
|
const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
|
||||||
|
const MegolmEncryption = algorithms.ENCRYPTION_CLASSES['m.megolm.v1.aes-sha2'];
|
||||||
|
|
||||||
const ROOM_ID = '!ROOM:ID';
|
const ROOM_ID = '!ROOM:ID';
|
||||||
|
|
||||||
@@ -34,9 +35,11 @@ describe("MegolmDecryption", function() {
|
|||||||
let mockCrypto;
|
let mockCrypto;
|
||||||
let mockBaseApis;
|
let mockBaseApis;
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(async function() {
|
||||||
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this
|
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this
|
||||||
|
|
||||||
|
await Olm.init();
|
||||||
|
|
||||||
mockCrypto = testUtils.mock(Crypto, 'Crypto');
|
mockCrypto = testUtils.mock(Crypto, 'Crypto');
|
||||||
mockBaseApis = {};
|
mockBaseApis = {};
|
||||||
|
|
||||||
@@ -66,7 +69,6 @@ describe("MegolmDecryption", function() {
|
|||||||
describe('receives some keys:', function() {
|
describe('receives some keys:', function() {
|
||||||
let groupSession;
|
let groupSession;
|
||||||
beforeEach(async function() {
|
beforeEach(async function() {
|
||||||
await Olm.init();
|
|
||||||
groupSession = new global.Olm.OutboundGroupSession();
|
groupSession = new global.Olm.OutboundGroupSession();
|
||||||
groupSession.create();
|
groupSession.create();
|
||||||
|
|
||||||
@@ -263,5 +265,92 @@ describe("MegolmDecryption", function() {
|
|||||||
// test is successful if no exception is thrown
|
// test is successful if no exception is thrown
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("re-uses sessions for sequential messages", async function() {
|
||||||
|
const mockStorage = new MockStorageApi();
|
||||||
|
const sessionStore = new WebStorageSessionStore(mockStorage);
|
||||||
|
const cryptoStore = new MemoryCryptoStore(mockStorage);
|
||||||
|
|
||||||
|
const olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
||||||
|
olmDevice.verifySignature = expect.createSpy();
|
||||||
|
await olmDevice.init();
|
||||||
|
|
||||||
|
mockBaseApis.claimOneTimeKeys = expect.createSpy().andReturn(Promise.resolve({
|
||||||
|
one_time_keys: {
|
||||||
|
'@alice:home.server': {
|
||||||
|
aliceDevice: {
|
||||||
|
'signed_curve25519:flooble': {
|
||||||
|
key: 'YmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmJiYmI',
|
||||||
|
signatures: {
|
||||||
|
'@alice:home.server': {
|
||||||
|
'ed25519:aliceDevice': 'totally valid',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
mockBaseApis.sendToDevice = expect.createSpy().andReturn(Promise.resolve());
|
||||||
|
|
||||||
|
mockCrypto.downloadKeys.andReturn(Promise.resolve({
|
||||||
|
'@alice:home.server': {
|
||||||
|
aliceDevice: {
|
||||||
|
deviceId: 'aliceDevice',
|
||||||
|
isBlocked: expect.createSpy().andReturn(false),
|
||||||
|
isUnverified: expect.createSpy().andReturn(false),
|
||||||
|
getIdentityKey: expect.createSpy().andReturn(
|
||||||
|
'YWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWFhYWE',
|
||||||
|
),
|
||||||
|
getFingerprint: expect.createSpy().andReturn(''),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
const megolmEncryption = new MegolmEncryption({
|
||||||
|
userId: '@user:id',
|
||||||
|
crypto: mockCrypto,
|
||||||
|
olmDevice: olmDevice,
|
||||||
|
baseApis: mockBaseApis,
|
||||||
|
roomId: ROOM_ID,
|
||||||
|
config: {
|
||||||
|
rotation_period_ms: 9999999999999,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const mockRoom = {
|
||||||
|
getEncryptionTargetMembers: expect.createSpy().andReturn(
|
||||||
|
[{userId: "@alice:home.server"}],
|
||||||
|
),
|
||||||
|
getBlacklistUnverifiedDevices: expect.createSpy().andReturn(false),
|
||||||
|
};
|
||||||
|
const ct1 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
|
||||||
|
body: "Some text",
|
||||||
|
});
|
||||||
|
expect(mockRoom.getEncryptionTargetMembers).toHaveBeenCalled();
|
||||||
|
|
||||||
|
// this should have claimed a key for alice as it's starting a new session
|
||||||
|
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalled(
|
||||||
|
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519',
|
||||||
|
);
|
||||||
|
expect(mockCrypto.downloadKeys).toHaveBeenCalledWith(
|
||||||
|
['@alice:home.server'], false,
|
||||||
|
);
|
||||||
|
expect(mockBaseApis.sendToDevice).toHaveBeenCalled();
|
||||||
|
expect(mockBaseApis.claimOneTimeKeys).toHaveBeenCalled(
|
||||||
|
[['@alice:home.server', 'aliceDevice']], 'signed_curve25519',
|
||||||
|
);
|
||||||
|
|
||||||
|
mockBaseApis.claimOneTimeKeys.reset();
|
||||||
|
|
||||||
|
const ct2 = await megolmEncryption.encryptMessage(mockRoom, "a.fake.type", {
|
||||||
|
body: "Some more text",
|
||||||
|
});
|
||||||
|
|
||||||
|
// this should *not* have claimed a key as it should be using the same session
|
||||||
|
expect(mockBaseApis.claimOneTimeKeys).toNotHaveBeenCalled();
|
||||||
|
|
||||||
|
// likewise they should show the same session ID
|
||||||
|
expect(ct2.session_id).toEqual(ct1.session_id);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
90
spec/unit/crypto/algorithms/olm.spec.js
Normal file
90
spec/unit/crypto/algorithms/olm.spec.js
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2018 New Vector Ltd
|
||||||
|
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
try {
|
||||||
|
global.Olm = require('olm');
|
||||||
|
} catch (e) {
|
||||||
|
console.warn("unable to run megolm tests: libolm not available");
|
||||||
|
}
|
||||||
|
|
||||||
|
import expect from 'expect';
|
||||||
|
import WebStorageSessionStore from '../../../../lib/store/session/webstorage';
|
||||||
|
import MemoryCryptoStore from '../../../../lib/crypto/store/memory-crypto-store.js';
|
||||||
|
import MockStorageApi from '../../../MockStorageApi';
|
||||||
|
import testUtils from '../../../test-utils';
|
||||||
|
|
||||||
|
import OlmDevice from '../../../../lib/crypto/OlmDevice';
|
||||||
|
|
||||||
|
function makeOlmDevice() {
|
||||||
|
const mockStorage = new MockStorageApi();
|
||||||
|
const sessionStore = new WebStorageSessionStore(mockStorage);
|
||||||
|
const cryptoStore = new MemoryCryptoStore(mockStorage);
|
||||||
|
const olmDevice = new OlmDevice(sessionStore, cryptoStore);
|
||||||
|
return olmDevice;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function setupSession(initiator, opponent) {
|
||||||
|
await opponent.generateOneTimeKeys(1);
|
||||||
|
const keys = await opponent.getOneTimeKeys();
|
||||||
|
const firstKey = Object.values(keys['curve25519'])[0];
|
||||||
|
|
||||||
|
const sid = await initiator.createOutboundSession(
|
||||||
|
opponent.deviceCurve25519Key, firstKey,
|
||||||
|
);
|
||||||
|
return sid;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("OlmDecryption", function() {
|
||||||
|
if (!global.Olm) {
|
||||||
|
console.warn('Not running megolm unit tests: libolm not present');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let aliceOlmDevice;
|
||||||
|
let bobOlmDevice;
|
||||||
|
|
||||||
|
beforeEach(async function() {
|
||||||
|
testUtils.beforeEach(this); // eslint-disable-line no-invalid-this
|
||||||
|
|
||||||
|
await global.Olm.init();
|
||||||
|
|
||||||
|
aliceOlmDevice = makeOlmDevice();
|
||||||
|
bobOlmDevice = makeOlmDevice();
|
||||||
|
await aliceOlmDevice.init();
|
||||||
|
await bobOlmDevice.init();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('olm', function() {
|
||||||
|
it("can decrypt messages", async function() {
|
||||||
|
const sid = await setupSession(aliceOlmDevice, bobOlmDevice);
|
||||||
|
|
||||||
|
const ciphertext = await aliceOlmDevice.encryptMessage(
|
||||||
|
bobOlmDevice.deviceCurve25519Key,
|
||||||
|
sid,
|
||||||
|
"The olm or proteus is an aquatic salamander in the family Proteidae",
|
||||||
|
);
|
||||||
|
|
||||||
|
const result = await bobOlmDevice.createInboundSession(
|
||||||
|
aliceOlmDevice.deviceCurve25519Key,
|
||||||
|
ciphertext.type,
|
||||||
|
ciphertext.body,
|
||||||
|
);
|
||||||
|
expect(result.payload).toEqual(
|
||||||
|
"The olm or proteus is an aquatic salamander in the family Proteidae",
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -262,7 +262,19 @@ MatrixBaseApis.prototype.login = function(loginType, data, callback) {
|
|||||||
utils.extend(login_data, data);
|
utils.extend(login_data, data);
|
||||||
|
|
||||||
return this._http.authedRequest(
|
return this._http.authedRequest(
|
||||||
callback, "POST", "/login", undefined, login_data,
|
(error, response) => {
|
||||||
|
if (loginType === "m.login.password" && response &&
|
||||||
|
response.access_token && response.user_id) {
|
||||||
|
this._http.opts.accessToken = response.access_token;
|
||||||
|
this.credentials = {
|
||||||
|
userId: response.user_id,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (callback) {
|
||||||
|
callback(error, response);
|
||||||
|
}
|
||||||
|
}, "POST", "/login", undefined, login_data,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -927,6 +939,28 @@ MatrixBaseApis.prototype.setRoomReadMarkersHttpRequest =
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {module:client.Promise} Resolves: A list of the user's current rooms
|
||||||
|
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||||
|
*/
|
||||||
|
MatrixBaseApis.prototype.getJoinedRooms = function() {
|
||||||
|
const path = utils.encodeUri("/joined_rooms");
|
||||||
|
return this._http.authedRequest(undefined, "GET", path);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve membership info. for a room.
|
||||||
|
* @param {string} roomId ID of the room to get membership for
|
||||||
|
* @return {module:client.Promise} Resolves: A list of currently joined users
|
||||||
|
* and their profile data.
|
||||||
|
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||||
|
*/
|
||||||
|
MatrixBaseApis.prototype.getJoinedRoomMembers = function(roomId) {
|
||||||
|
const path = utils.encodeUri("/rooms/$roomId/joined_members", {
|
||||||
|
$roomId: roomId,
|
||||||
|
});
|
||||||
|
return this._http.authedRequest(undefined, "GET", path);
|
||||||
|
};
|
||||||
|
|
||||||
// Room Directory operations
|
// Room Directory operations
|
||||||
// =========================
|
// =========================
|
||||||
|
|||||||
@@ -1246,6 +1246,8 @@ MatrixClient.prototype.isUserIgnored = function(userId) {
|
|||||||
* </strong> Default: true.
|
* </strong> Default: true.
|
||||||
* @param {boolean} opts.inviteSignUrl If the caller has a keypair 3pid invite,
|
* @param {boolean} opts.inviteSignUrl If the caller has a keypair 3pid invite,
|
||||||
* the signing URL is passed in this parameter.
|
* the signing URL is passed in this parameter.
|
||||||
|
* @param {string[]} opts.viaServers The server names to try and join through in
|
||||||
|
* addition to those that are automatically chosen.
|
||||||
* @param {module:client.callback} callback Optional.
|
* @param {module:client.callback} callback Optional.
|
||||||
* @return {module:client.Promise} Resolves: Room object.
|
* @return {module:client.Promise} Resolves: Room object.
|
||||||
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
* @return {module:http-api.MatrixError} Rejects: with an error response.
|
||||||
@@ -1274,6 +1276,13 @@ MatrixClient.prototype.joinRoom = function(roomIdOrAlias, opts, callback) {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const queryString = {};
|
||||||
|
if (opts.viaServers) {
|
||||||
|
queryString["server_name"] = opts.viaServers;
|
||||||
|
}
|
||||||
|
|
||||||
|
const reqOpts = {qsStringifyOptions: {arrayFormat: 'repeat'}};
|
||||||
|
|
||||||
const defer = Promise.defer();
|
const defer = Promise.defer();
|
||||||
|
|
||||||
const self = this;
|
const self = this;
|
||||||
@@ -1284,7 +1293,8 @@ MatrixClient.prototype.joinRoom = function(roomIdOrAlias, opts, callback) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const path = utils.encodeUri("/join/$roomid", { $roomid: roomIdOrAlias});
|
const path = utils.encodeUri("/join/$roomid", { $roomid: roomIdOrAlias});
|
||||||
return self._http.authedRequest(undefined, "POST", path, undefined, data);
|
return self._http.authedRequest(
|
||||||
|
undefined, "POST", path, queryString, data, reqOpts);
|
||||||
}).then(function(res) {
|
}).then(function(res) {
|
||||||
const roomId = res.room_id;
|
const roomId = res.room_id;
|
||||||
const syncApi = new SyncApi(self, self._clientOpts);
|
const syncApi = new SyncApi(self, self._clientOpts);
|
||||||
@@ -1504,6 +1514,13 @@ MatrixClient.prototype.sendEvent = function(roomId, eventType, content, txnId,
|
|||||||
room.addPendingEvent(localEvent, txnId);
|
room.addPendingEvent(localEvent, txnId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// addPendingEvent can change the state to NOT_SENT if it believes
|
||||||
|
// that there's other events that have failed. We won't bother to
|
||||||
|
// try sending the event if the state has changed as such.
|
||||||
|
if (localEvent.status === EventStatus.NOT_SENT) {
|
||||||
|
return Promise.reject(new Error("Event blocked by other events not yet sent"));
|
||||||
|
}
|
||||||
|
|
||||||
return _sendEvent(this, room, localEvent, callback);
|
return _sendEvent(this, room, localEvent, callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -295,8 +295,8 @@ OlmDevice.prototype._storeAccount = function(txn, account) {
|
|||||||
*/
|
*/
|
||||||
OlmDevice.prototype._getSession = function(deviceKey, sessionId, txn, func) {
|
OlmDevice.prototype._getSession = function(deviceKey, sessionId, txn, func) {
|
||||||
this._cryptoStore.getEndToEndSession(
|
this._cryptoStore.getEndToEndSession(
|
||||||
deviceKey, sessionId, txn, (pickledSession) => {
|
deviceKey, sessionId, txn, (sessionInfo) => {
|
||||||
this._unpickleSession(pickledSession, func);
|
this._unpickleSession(sessionInfo, func);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -306,15 +306,17 @@ OlmDevice.prototype._getSession = function(deviceKey, sessionId, txn, func) {
|
|||||||
* function with it. The session object is destroyed once the function
|
* function with it. The session object is destroyed once the function
|
||||||
* returns.
|
* returns.
|
||||||
*
|
*
|
||||||
* @param {string} pickledSession
|
* @param {object} sessionInfo
|
||||||
* @param {function} func
|
* @param {function} func
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
OlmDevice.prototype._unpickleSession = function(pickledSession, func) {
|
OlmDevice.prototype._unpickleSession = function(sessionInfo, func) {
|
||||||
const session = new global.Olm.Session();
|
const session = new global.Olm.Session();
|
||||||
try {
|
try {
|
||||||
session.unpickle(this._pickleKey, pickledSession);
|
session.unpickle(this._pickleKey, sessionInfo.session);
|
||||||
func(session);
|
const unpickledSessInfo = Object.assign({}, sessionInfo, {session});
|
||||||
|
|
||||||
|
func(unpickledSessInfo);
|
||||||
} finally {
|
} finally {
|
||||||
session.free();
|
session.free();
|
||||||
}
|
}
|
||||||
@@ -324,14 +326,17 @@ OlmDevice.prototype._unpickleSession = function(pickledSession, func) {
|
|||||||
* store our OlmSession in the session store
|
* store our OlmSession in the session store
|
||||||
*
|
*
|
||||||
* @param {string} deviceKey
|
* @param {string} deviceKey
|
||||||
* @param {OlmSession} session
|
* @param {object} sessionInfo {session: OlmSession, lastReceivedMessageTs: int}
|
||||||
* @param {*} txn Opaque transaction object from cryptoStore.doTxn()
|
* @param {*} txn Opaque transaction object from cryptoStore.doTxn()
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
OlmDevice.prototype._saveSession = function(deviceKey, session, txn) {
|
OlmDevice.prototype._saveSession = function(deviceKey, sessionInfo, txn) {
|
||||||
const pickledSession = session.pickle(this._pickleKey);
|
const sessionId = sessionInfo.session.session_id();
|
||||||
|
const pickledSessionInfo = Object.assign(sessionInfo, {
|
||||||
|
session: sessionInfo.session.pickle(this._pickleKey),
|
||||||
|
});
|
||||||
this._cryptoStore.storeEndToEndSession(
|
this._cryptoStore.storeEndToEndSession(
|
||||||
deviceKey, session.session_id(), pickledSession, txn,
|
deviceKey, sessionId, pickledSessionInfo, txn,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -461,7 +466,14 @@ OlmDevice.prototype.createOutboundSession = async function(
|
|||||||
session.create_outbound(account, theirIdentityKey, theirOneTimeKey);
|
session.create_outbound(account, theirIdentityKey, theirOneTimeKey);
|
||||||
newSessionId = session.session_id();
|
newSessionId = session.session_id();
|
||||||
this._storeAccount(txn, account);
|
this._storeAccount(txn, account);
|
||||||
this._saveSession(theirIdentityKey, session, txn);
|
const sessionInfo = {
|
||||||
|
session,
|
||||||
|
// Pretend we've received a message at this point, otherwise
|
||||||
|
// if we try to send a message to the device, it won't use
|
||||||
|
// this session
|
||||||
|
lastReceivedMessageTs: Date.now(),
|
||||||
|
};
|
||||||
|
this._saveSession(theirIdentityKey, sessionInfo, txn);
|
||||||
} finally {
|
} finally {
|
||||||
session.free();
|
session.free();
|
||||||
}
|
}
|
||||||
@@ -510,7 +522,13 @@ OlmDevice.prototype.createInboundSession = async function(
|
|||||||
|
|
||||||
const payloadString = session.decrypt(messageType, ciphertext);
|
const payloadString = session.decrypt(messageType, ciphertext);
|
||||||
|
|
||||||
this._saveSession(theirDeviceIdentityKey, session, txn);
|
const sessionInfo = {
|
||||||
|
session,
|
||||||
|
// this counts as a received message: set last received message time
|
||||||
|
// to now
|
||||||
|
lastReceivedMessageTs: Date.now(),
|
||||||
|
};
|
||||||
|
this._saveSession(theirDeviceIdentityKey, sessionInfo, txn);
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
payload: payloadString,
|
payload: payloadString,
|
||||||
@@ -558,13 +576,30 @@ OlmDevice.prototype.getSessionIdsForDevice = async function(theirDeviceIdentityK
|
|||||||
* @return {Promise<?string>} session id, or null if no established session
|
* @return {Promise<?string>} session id, or null if no established session
|
||||||
*/
|
*/
|
||||||
OlmDevice.prototype.getSessionIdForDevice = async function(theirDeviceIdentityKey) {
|
OlmDevice.prototype.getSessionIdForDevice = async function(theirDeviceIdentityKey) {
|
||||||
const sessionIds = await this.getSessionIdsForDevice(theirDeviceIdentityKey);
|
const sessionInfos = await this.getSessionInfoForDevice(theirDeviceIdentityKey);
|
||||||
if (sessionIds.length === 0) {
|
if (sessionInfos.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
// Use the session with the lowest ID.
|
// Use the session that has most recently received a message
|
||||||
sessionIds.sort();
|
let idxOfBest = 0;
|
||||||
return sessionIds[0];
|
for (let i = 1; i < sessionInfos.length; i++) {
|
||||||
|
const thisSessInfo = sessionInfos[i];
|
||||||
|
const thisLastReceived = thisSessInfo.lastReceivedMessageTs === undefined ?
|
||||||
|
0 : thisSessInfo.lastReceivedMessageTs;
|
||||||
|
|
||||||
|
const bestSessInfo = sessionInfos[idxOfBest];
|
||||||
|
const bestLastReceived = bestSessInfo.lastReceivedMessageTs === undefined ?
|
||||||
|
0 : bestSessInfo.lastReceivedMessageTs;
|
||||||
|
if (
|
||||||
|
thisLastReceived > bestLastReceived || (
|
||||||
|
thisLastReceived === bestLastReceived &&
|
||||||
|
thisSessInfo.sessionId < bestSessInfo.sessionId
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
idxOfBest = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return sessionInfos[idxOfBest].sessionId;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -587,9 +622,10 @@ OlmDevice.prototype.getSessionInfoForDevice = async function(deviceIdentityKey)
|
|||||||
this._cryptoStore.getEndToEndSessions(deviceIdentityKey, txn, (sessions) => {
|
this._cryptoStore.getEndToEndSessions(deviceIdentityKey, txn, (sessions) => {
|
||||||
const sessionIds = Object.keys(sessions).sort();
|
const sessionIds = Object.keys(sessions).sort();
|
||||||
for (const sessionId of sessionIds) {
|
for (const sessionId of sessionIds) {
|
||||||
this._unpickleSession(sessions[sessionId], (session) => {
|
this._unpickleSession(sessions[sessionId], (sessInfo) => {
|
||||||
info.push({
|
info.push({
|
||||||
hasReceivedMessage: session.has_received_message(),
|
lastReceivedMessageTs: sessInfo.lastReceivedMessageTs,
|
||||||
|
hasReceivedMessage: sessInfo.session.has_received_message(),
|
||||||
sessionId: sessionId,
|
sessionId: sessionId,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -620,9 +656,9 @@ OlmDevice.prototype.encryptMessage = async function(
|
|||||||
await this._cryptoStore.doTxn(
|
await this._cryptoStore.doTxn(
|
||||||
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
|
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
this._getSession(theirDeviceIdentityKey, sessionId, txn, (session) => {
|
this._getSession(theirDeviceIdentityKey, sessionId, txn, (sessionInfo) => {
|
||||||
res = session.encrypt(payloadString);
|
res = sessionInfo.session.encrypt(payloadString);
|
||||||
this._saveSession(theirDeviceIdentityKey, session, txn);
|
this._saveSession(theirDeviceIdentityKey, sessionInfo, txn);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -647,9 +683,10 @@ OlmDevice.prototype.decryptMessage = async function(
|
|||||||
await this._cryptoStore.doTxn(
|
await this._cryptoStore.doTxn(
|
||||||
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
|
'readwrite', [IndexedDBCryptoStore.STORE_SESSIONS],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
this._getSession(theirDeviceIdentityKey, sessionId, txn, (session) => {
|
this._getSession(theirDeviceIdentityKey, sessionId, txn, (sessionInfo) => {
|
||||||
payloadString = session.decrypt(messageType, ciphertext);
|
payloadString = sessionInfo.session.decrypt(messageType, ciphertext);
|
||||||
this._saveSession(theirDeviceIdentityKey, session, txn);
|
sessionInfo.lastReceivedMessageTs = Date.now();
|
||||||
|
this._saveSession(theirDeviceIdentityKey, sessionInfo, txn);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -679,8 +716,8 @@ OlmDevice.prototype.matchesSession = async function(
|
|||||||
await this._cryptoStore.doTxn(
|
await this._cryptoStore.doTxn(
|
||||||
'readonly', [IndexedDBCryptoStore.STORE_SESSIONS],
|
'readonly', [IndexedDBCryptoStore.STORE_SESSIONS],
|
||||||
(txn) => {
|
(txn) => {
|
||||||
this._getSession(theirDeviceIdentityKey, sessionId, txn, (session) => {
|
this._getSession(theirDeviceIdentityKey, sessionId, txn, (sessionInfo) => {
|
||||||
matches = session.matches_inbound(ciphertext);
|
matches = sessionInfo.session.matches_inbound(ciphertext);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@@ -714,7 +751,7 @@ OlmDevice.prototype._saveOutboundGroupSession = function(session) {
|
|||||||
*/
|
*/
|
||||||
OlmDevice.prototype._getOutboundGroupSession = function(sessionId, func) {
|
OlmDevice.prototype._getOutboundGroupSession = function(sessionId, func) {
|
||||||
const pickled = this._outboundGroupSessionStore[sessionId];
|
const pickled = this._outboundGroupSessionStore[sessionId];
|
||||||
if (pickled === null) {
|
if (pickled === undefined) {
|
||||||
throw new Error("Unknown outbound group session " + sessionId);
|
throw new Error("Unknown outbound group session " + sessionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1048,6 +1085,8 @@ OlmDevice.prototype.hasInboundSessionKeys = async function(roomId, senderKey, se
|
|||||||
* @param {string} roomId room in which the message was received
|
* @param {string} roomId room in which the message was received
|
||||||
* @param {string} senderKey base64-encoded curve25519 key of the sender
|
* @param {string} senderKey base64-encoded curve25519 key of the sender
|
||||||
* @param {string} sessionId session identifier
|
* @param {string} sessionId session identifier
|
||||||
|
* @param {integer} chainIndex The chain index at which to export the session.
|
||||||
|
* If omitted, export at the first index we know about.
|
||||||
*
|
*
|
||||||
* @returns {Promise<{chain_index: number, key: string,
|
* @returns {Promise<{chain_index: number, key: string,
|
||||||
* forwarding_curve25519_key_chain: Array<string>,
|
* forwarding_curve25519_key_chain: Array<string>,
|
||||||
@@ -1055,9 +1094,12 @@ OlmDevice.prototype.hasInboundSessionKeys = async function(roomId, senderKey, se
|
|||||||
* }>}
|
* }>}
|
||||||
* details of the session key. The key is a base64-encoded megolm key in
|
* details of the session key. The key is a base64-encoded megolm key in
|
||||||
* export format.
|
* export format.
|
||||||
|
*
|
||||||
|
* @throws Error If the given chain index could not be obtained from the known
|
||||||
|
* index (ie. the given chain index is before the first we have).
|
||||||
*/
|
*/
|
||||||
OlmDevice.prototype.getInboundGroupSessionKey = async function(
|
OlmDevice.prototype.getInboundGroupSessionKey = async function(
|
||||||
roomId, senderKey, sessionId,
|
roomId, senderKey, sessionId, chainIndex,
|
||||||
) {
|
) {
|
||||||
let result;
|
let result;
|
||||||
await this._cryptoStore.doTxn(
|
await this._cryptoStore.doTxn(
|
||||||
@@ -1068,14 +1110,19 @@ OlmDevice.prototype.getInboundGroupSessionKey = async function(
|
|||||||
result = null;
|
result = null;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const messageIndex = session.first_known_index();
|
|
||||||
|
if (chainIndex === undefined) {
|
||||||
|
chainIndex = session.first_known_index();
|
||||||
|
}
|
||||||
|
|
||||||
|
const exportedSession = session.export_session(chainIndex);
|
||||||
|
|
||||||
const claimedKeys = sessionData.keysClaimed || {};
|
const claimedKeys = sessionData.keysClaimed || {};
|
||||||
const senderEd25519Key = claimedKeys.ed25519 || null;
|
const senderEd25519Key = claimedKeys.ed25519 || null;
|
||||||
|
|
||||||
result = {
|
result = {
|
||||||
"chain_index": messageIndex,
|
"chain_index": chainIndex,
|
||||||
"key": session.export_session(messageIndex),
|
"key": exportedSession,
|
||||||
"forwarding_curve25519_key_chain":
|
"forwarding_curve25519_key_chain":
|
||||||
sessionData.forwardingCurve25519KeyChain || [],
|
sessionData.forwardingCurve25519KeyChain || [],
|
||||||
"sender_claimed_ed25519_key": senderEd25519Key,
|
"sender_claimed_ed25519_key": senderEd25519Key,
|
||||||
|
|||||||
@@ -244,6 +244,21 @@ export default class OutgoingRoomKeyRequestManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look for room key requests by target device and state
|
||||||
|
*
|
||||||
|
* @param {string} userId Target user ID
|
||||||
|
* @param {string} deviceId Target device ID
|
||||||
|
*
|
||||||
|
* @return {Promise} resolves to a list of all the
|
||||||
|
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
|
||||||
|
*/
|
||||||
|
getOutgoingSentRoomKeyRequest(userId, deviceId) {
|
||||||
|
return this._cryptoStore.getOutgoingRoomKeyRequestsByTarget(
|
||||||
|
userId, deviceId, [ROOM_KEY_REQUEST_STATES.SENT],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// start the background timer to send queued requests, if the timer isn't
|
// start the background timer to send queued requests, if the timer isn't
|
||||||
// already running
|
// already running
|
||||||
_startTimer() {
|
_startTimer() {
|
||||||
|
|||||||
@@ -144,6 +144,11 @@ function MegolmEncryption(params) {
|
|||||||
// room).
|
// room).
|
||||||
this._setupPromise = Promise.resolve();
|
this._setupPromise = Promise.resolve();
|
||||||
|
|
||||||
|
// Map of outbound sessions by sessions ID. Used if we need a particular
|
||||||
|
// session (the session we're currently using to send is always obtained
|
||||||
|
// using _setupPromise).
|
||||||
|
this._outboundSessions = {};
|
||||||
|
|
||||||
// default rotation periods
|
// default rotation periods
|
||||||
this._sessionRotationPeriodMsgs = 100;
|
this._sessionRotationPeriodMsgs = 100;
|
||||||
this._sessionRotationPeriodMs = 7 * 24 * 3600 * 1000;
|
this._sessionRotationPeriodMs = 7 * 24 * 3600 * 1000;
|
||||||
@@ -195,6 +200,7 @@ MegolmEncryption.prototype._ensureOutboundSession = function(devicesInRoom) {
|
|||||||
if (!session) {
|
if (!session) {
|
||||||
logger.log(`Starting new megolm session for room ${self._roomId}`);
|
logger.log(`Starting new megolm session for room ${self._roomId}`);
|
||||||
session = await self._prepareNewSession();
|
session = await self._prepareNewSession();
|
||||||
|
self._outboundSessions[session.sessionId] = session;
|
||||||
}
|
}
|
||||||
|
|
||||||
// now check if we need to share with any devices
|
// now check if we need to share with any devices
|
||||||
@@ -421,8 +427,98 @@ MegolmEncryption.prototype._encryptAndSendKeysToDevices = function(
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* Re-shares a megolm session key with devices if the key has already been
|
||||||
|
* sent to them.
|
||||||
*
|
*
|
||||||
|
* @param {string} senderKey The key of the originating device for the session
|
||||||
|
* @param {string} sessionId ID of the outbound session to share
|
||||||
|
* @param {string} userId ID of the user who owns the target device
|
||||||
|
* @param {module:crypto/deviceinfo} device The target device
|
||||||
|
*/
|
||||||
|
MegolmEncryption.prototype.reshareKeyWithDevice = async function(
|
||||||
|
senderKey, sessionId, userId, device,
|
||||||
|
) {
|
||||||
|
const obSessionInfo = this._outboundSessions[sessionId];
|
||||||
|
if (!obSessionInfo) {
|
||||||
|
logger.debug("Session ID " + sessionId + " not found: not re-sharing keys");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The chain index of the key we previously sent this device
|
||||||
|
if (obSessionInfo.sharedWithDevices[userId] === undefined) {
|
||||||
|
logger.debug("Session ID " + sessionId + " never shared with user " + userId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const sentChainIndex = obSessionInfo.sharedWithDevices[userId][device.deviceId];
|
||||||
|
if (sentChainIndex === undefined) {
|
||||||
|
logger.debug(
|
||||||
|
"Session ID " + sessionId + " never shared with device " +
|
||||||
|
userId + ":" + device.deviceId,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// get the key from the inbound session: the outbound one will already
|
||||||
|
// have been ratcheted to the next chain index.
|
||||||
|
const key = await this._olmDevice.getInboundGroupSessionKey(
|
||||||
|
this._roomId, senderKey, sessionId, sentChainIndex,
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!key) {
|
||||||
|
logger.warn(
|
||||||
|
"No outbound session key found for " + sessionId + ": not re-sharing keys",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await olmlib.ensureOlmSessionsForDevices(
|
||||||
|
this._olmDevice, this._baseApis, {
|
||||||
|
[userId]: {
|
||||||
|
[device.deviceId]: device,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
type: "m.forwarded_room_key",
|
||||||
|
content: {
|
||||||
|
algorithm: olmlib.MEGOLM_ALGORITHM,
|
||||||
|
room_id: this._roomId,
|
||||||
|
session_id: sessionId,
|
||||||
|
session_key: key.key,
|
||||||
|
chain_index: key.chain_index,
|
||||||
|
sender_key: senderKey,
|
||||||
|
sender_claimed_ed25519_key: key.sender_claimed_ed25519_key,
|
||||||
|
forwarding_curve25519_key_chain: key.forwarding_curve25519_key_chain,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const encryptedContent = {
|
||||||
|
algorithm: olmlib.OLM_ALGORITHM,
|
||||||
|
sender_key: this._olmDevice.deviceCurve25519Key,
|
||||||
|
ciphertext: {},
|
||||||
|
};
|
||||||
|
await olmlib.encryptMessageForDevice(
|
||||||
|
encryptedContent.ciphertext,
|
||||||
|
this._userId,
|
||||||
|
this._deviceId,
|
||||||
|
this._olmDevice,
|
||||||
|
userId,
|
||||||
|
device,
|
||||||
|
payload,
|
||||||
|
),
|
||||||
|
|
||||||
|
await this._baseApis.sendToDevice("m.room.encrypted", {
|
||||||
|
[userId]: {
|
||||||
|
[device.deviceId]: encryptedContent,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
logger.debug(
|
||||||
|
`Re-shared key for session ${sessionId} with ${userId}:${device.deviceId}`,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
* @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session
|
* @param {module:crypto/algorithms/megolm.OutboundSessionInfo} session
|
||||||
*
|
*
|
||||||
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
|
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
|
||||||
|
|||||||
@@ -41,6 +41,8 @@ export function isCryptoAvailable() {
|
|||||||
return Boolean(global.Olm);
|
return Boolean(global.Olm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MIN_FORCE_SESSION_INTERVAL_MS = 60 * 60 * 1000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cryptography bits
|
* Cryptography bits
|
||||||
*
|
*
|
||||||
@@ -128,6 +130,15 @@ export default function Crypto(baseApis, sessionStore, userId, deviceId,
|
|||||||
// has happened for a given room. This is delayed
|
// has happened for a given room. This is delayed
|
||||||
// to avoid loading room members as long as possible.
|
// to avoid loading room members as long as possible.
|
||||||
this._roomDeviceTrackingState = {};
|
this._roomDeviceTrackingState = {};
|
||||||
|
|
||||||
|
// The timestamp of the last time we forced establishment
|
||||||
|
// of a new session for each device, in milliseconds.
|
||||||
|
// {
|
||||||
|
// userId: {
|
||||||
|
// deviceId: 1234567890000,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
this._lastNewSessionForced = {};
|
||||||
}
|
}
|
||||||
utils.inherits(Crypto, EventEmitter);
|
utils.inherits(Crypto, EventEmitter);
|
||||||
|
|
||||||
@@ -1378,6 +1389,8 @@ Crypto.prototype._onToDeviceEvent = function(event) {
|
|||||||
this._onRoomKeyEvent(event);
|
this._onRoomKeyEvent(event);
|
||||||
} else if (event.getType() == "m.room_key_request") {
|
} else if (event.getType() == "m.room_key_request") {
|
||||||
this._onRoomKeyRequestEvent(event);
|
this._onRoomKeyRequestEvent(event);
|
||||||
|
} else if (event.getContent().msgtype === "m.bad.encrypted") {
|
||||||
|
this._onToDeviceBadEncrypted(event);
|
||||||
} else if (event.isBeingDecrypted()) {
|
} else if (event.isBeingDecrypted()) {
|
||||||
// once the event has been decrypted, try again
|
// once the event has been decrypted, try again
|
||||||
event.once('Event.decrypted', (ev) => {
|
event.once('Event.decrypted', (ev) => {
|
||||||
@@ -1413,6 +1426,87 @@ Crypto.prototype._onRoomKeyEvent = function(event) {
|
|||||||
alg.onRoomKeyEvent(event);
|
alg.onRoomKeyEvent(event);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handle a toDevice event that couldn't be decrypted
|
||||||
|
*
|
||||||
|
* @private
|
||||||
|
* @param {module:models/event.MatrixEvent} event undecryptable event
|
||||||
|
*/
|
||||||
|
Crypto.prototype._onToDeviceBadEncrypted = async function(event) {
|
||||||
|
const content = event.getWireContent();
|
||||||
|
const sender = event.getSender();
|
||||||
|
const algorithm = content.algorithm;
|
||||||
|
const deviceKey = content.sender_key;
|
||||||
|
|
||||||
|
if (sender === undefined || deviceKey === undefined || deviceKey === undefined) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// check when we last forced a new session with this device: if we've already done so
|
||||||
|
// recently, don't do it again.
|
||||||
|
this._lastNewSessionForced[sender] = this._lastNewSessionForced[sender] || {};
|
||||||
|
const lastNewSessionForced = this._lastNewSessionForced[sender][deviceKey] || 0;
|
||||||
|
if (lastNewSessionForced + MIN_FORCE_SESSION_INTERVAL_MS > Date.now()) {
|
||||||
|
logger.debug(
|
||||||
|
"New session already forced with device " + sender + ":" + deviceKey +
|
||||||
|
" at " + lastNewSessionForced + ": not forcing another",
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this._lastNewSessionForced[sender][deviceKey] = Date.now();
|
||||||
|
|
||||||
|
// establish a new olm session with this device since we're failing to decrypt messages
|
||||||
|
// on a current session.
|
||||||
|
// Note that an undecryptable message from another device could easily be spoofed -
|
||||||
|
// is there anything we can do to mitigate this?
|
||||||
|
const device = this._deviceList.getDeviceByIdentityKey(sender, algorithm, deviceKey);
|
||||||
|
const devicesByUser = {};
|
||||||
|
devicesByUser[sender] = [device];
|
||||||
|
await olmlib.ensureOlmSessionsForDevices(
|
||||||
|
this._olmDevice, this._baseApis, devicesByUser, true,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Now send a blank message on that session so the other side knows about it.
|
||||||
|
// (The keyshare request is sent in the clear so that won't do)
|
||||||
|
// We send this first such that, as long as the toDevice messages arrive in the
|
||||||
|
// same order we sent them, the other end will get this first, set up the new session,
|
||||||
|
// then get the keyshare request and send the key over this new session (because it
|
||||||
|
// is the session it has most recently received a message on).
|
||||||
|
const encryptedContent = {
|
||||||
|
algorithm: olmlib.OLM_ALGORITHM,
|
||||||
|
sender_key: this._olmDevice.deviceCurve25519Key,
|
||||||
|
ciphertext: {},
|
||||||
|
};
|
||||||
|
await olmlib.encryptMessageForDevice(
|
||||||
|
encryptedContent.ciphertext,
|
||||||
|
this._userId,
|
||||||
|
this._deviceId,
|
||||||
|
this._olmDevice,
|
||||||
|
sender,
|
||||||
|
device,
|
||||||
|
{type: "m.dummy"},
|
||||||
|
);
|
||||||
|
|
||||||
|
await this._baseApis.sendToDevice("m.room.encrypted", {
|
||||||
|
[sender]: {
|
||||||
|
[device.deviceId]: encryptedContent,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
// Most of the time this probably won't be necessary since we'll have queued up a key request when
|
||||||
|
// we failed to decrypt the message and will be waiting a bit for the key to arrive before sending
|
||||||
|
// it. This won't always be the case though so we need to re-send any that have already been sent
|
||||||
|
// to avoid races.
|
||||||
|
const requestsToResend =
|
||||||
|
await this._outgoingRoomKeyRequestManager.getOutgoingSentRoomKeyRequest(
|
||||||
|
sender, device.deviceId,
|
||||||
|
);
|
||||||
|
for (const keyReq of requestsToResend) {
|
||||||
|
this.cancelRoomKeyRequest(keyReq.requestBody, true);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle a change in the membership state of a member of a room
|
* Handle a change in the membership state of a member of a room
|
||||||
*
|
*
|
||||||
@@ -1538,9 +1632,27 @@ Crypto.prototype._processReceivedRoomKeyRequest = async function(req) {
|
|||||||
` for ${roomId} / ${body.session_id} (id ${req.requestId})`);
|
` for ${roomId} / ${body.session_id} (id ${req.requestId})`);
|
||||||
|
|
||||||
if (userId !== this._userId) {
|
if (userId !== this._userId) {
|
||||||
// TODO: determine if we sent this device the keys already: in
|
if (!this._roomEncryptors[roomId]) {
|
||||||
// which case we can do so again.
|
logger.debug(`room key request for unencrypted room ${roomId}`);
|
||||||
logger.log("Ignoring room key request from other user for now");
|
return;
|
||||||
|
}
|
||||||
|
const encryptor = this._roomEncryptors[roomId];
|
||||||
|
const device = this._deviceList.getStoredDevice(userId, deviceId);
|
||||||
|
if (!device) {
|
||||||
|
logger.debug(`Ignoring keyshare for unknown device ${userId}:${deviceId}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
await encryptor.reshareKeyWithDevice(
|
||||||
|
body.sender_key, body.session_id, userId, device,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
logger.warn(
|
||||||
|
"Failed to re-share keys for session " + body.session_id +
|
||||||
|
" with device " + userId + ":" + device.deviceId, e,
|
||||||
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -121,14 +121,17 @@ module.exports.encryptMessageForDevice = async function(
|
|||||||
* @param {module:base-apis~MatrixBaseApis} baseApis
|
* @param {module:base-apis~MatrixBaseApis} baseApis
|
||||||
*
|
*
|
||||||
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
|
* @param {object<string, module:crypto/deviceinfo[]>} devicesByUser
|
||||||
* map from userid to list of devices
|
* map from userid to list of devices to ensure sessions for
|
||||||
|
*
|
||||||
|
* @param {bolean} force If true, establish a new session even if one already exists.
|
||||||
|
* Optional.
|
||||||
*
|
*
|
||||||
* @return {module:client.Promise} resolves once the sessions are complete, to
|
* @return {module:client.Promise} resolves once the sessions are complete, to
|
||||||
* an Object mapping from userId to deviceId to
|
* an Object mapping from userId to deviceId to
|
||||||
* {@link module:crypto~OlmSessionResult}
|
* {@link module:crypto~OlmSessionResult}
|
||||||
*/
|
*/
|
||||||
module.exports.ensureOlmSessionsForDevices = async function(
|
module.exports.ensureOlmSessionsForDevices = async function(
|
||||||
olmDevice, baseApis, devicesByUser,
|
olmDevice, baseApis, devicesByUser, force,
|
||||||
) {
|
) {
|
||||||
const devicesWithoutSession = [
|
const devicesWithoutSession = [
|
||||||
// [userId, deviceId], ...
|
// [userId, deviceId], ...
|
||||||
@@ -146,7 +149,7 @@ module.exports.ensureOlmSessionsForDevices = async function(
|
|||||||
const deviceId = deviceInfo.deviceId;
|
const deviceId = deviceInfo.deviceId;
|
||||||
const key = deviceInfo.getIdentityKey();
|
const key = deviceInfo.getIdentityKey();
|
||||||
const sessionId = await olmDevice.getSessionIdForDevice(key);
|
const sessionId = await olmDevice.getSessionIdForDevice(key);
|
||||||
if (sessionId === null) {
|
if (sessionId === null || force) {
|
||||||
devicesWithoutSession.push([userId, deviceId]);
|
devicesWithoutSession.push([userId, deviceId]);
|
||||||
}
|
}
|
||||||
result[userId][deviceId] = {
|
result[userId][deviceId] = {
|
||||||
@@ -182,7 +185,7 @@ module.exports.ensureOlmSessionsForDevices = async function(
|
|||||||
for (let j = 0; j < devices.length; j++) {
|
for (let j = 0; j < devices.length; j++) {
|
||||||
const deviceInfo = devices[j];
|
const deviceInfo = devices[j];
|
||||||
const deviceId = deviceInfo.deviceId;
|
const deviceId = deviceInfo.deviceId;
|
||||||
if (result[userId][deviceId].sessionId) {
|
if (result[userId][deviceId].sessionId && !force) {
|
||||||
// we already have a result for this device
|
// we already have a result for this device
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -206,6 +206,42 @@ export class Backend {
|
|||||||
return promiseifyTxn(txn).then(() => result);
|
return promiseifyTxn(txn).then(() => result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
|
||||||
|
let stateIndex = 0;
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
function onsuccess(ev) {
|
||||||
|
const cursor = ev.target.result;
|
||||||
|
if (cursor) {
|
||||||
|
const keyReq = cursor.value;
|
||||||
|
if (keyReq.recipients.includes({userId, deviceId})) {
|
||||||
|
results.push(keyReq);
|
||||||
|
}
|
||||||
|
cursor.continue();
|
||||||
|
} else {
|
||||||
|
// try the next state in the list
|
||||||
|
stateIndex++;
|
||||||
|
if (stateIndex >= wantedStates.length) {
|
||||||
|
// no matches
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wantedState = wantedStates[stateIndex];
|
||||||
|
const cursorReq = ev.target.source.openCursor(wantedState);
|
||||||
|
cursorReq.onsuccess = onsuccess;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const txn = this._db.transaction("outgoingRoomKeyRequests", "readonly");
|
||||||
|
const store = txn.objectStore("outgoingRoomKeyRequests");
|
||||||
|
|
||||||
|
const wantedState = wantedStates[stateIndex];
|
||||||
|
const cursorReq = store.index("state").openCursor(wantedState);
|
||||||
|
cursorReq.onsuccess = onsuccess;
|
||||||
|
|
||||||
|
return promiseifyTxn(txn).then(() => results);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look for an existing room key request by id and state, and update it if
|
* Look for an existing room key request by id and state, and update it if
|
||||||
* found
|
* found
|
||||||
@@ -314,7 +350,10 @@ export class Backend {
|
|||||||
getReq.onsuccess = function() {
|
getReq.onsuccess = function() {
|
||||||
const cursor = getReq.result;
|
const cursor = getReq.result;
|
||||||
if (cursor) {
|
if (cursor) {
|
||||||
results[cursor.value.sessionId] = cursor.value.session;
|
results[cursor.value.sessionId] = {
|
||||||
|
session: cursor.value.session,
|
||||||
|
lastReceivedMessageTs: cursor.value.lastReceivedMessageTs,
|
||||||
|
};
|
||||||
cursor.continue();
|
cursor.continue();
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
@@ -332,7 +371,10 @@ export class Backend {
|
|||||||
getReq.onsuccess = function() {
|
getReq.onsuccess = function() {
|
||||||
try {
|
try {
|
||||||
if (getReq.result) {
|
if (getReq.result) {
|
||||||
func(getReq.result.session);
|
func({
|
||||||
|
session: getReq.result.session,
|
||||||
|
lastReceivedMessageTs: getReq.result.lastReceivedMessageTs,
|
||||||
|
});
|
||||||
} else {
|
} else {
|
||||||
func(null);
|
func(null);
|
||||||
}
|
}
|
||||||
@@ -342,9 +384,14 @@ export class Backend {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
storeEndToEndSession(deviceKey, sessionId, session, txn) {
|
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
|
||||||
const objectStore = txn.objectStore("sessions");
|
const objectStore = txn.objectStore("sessions");
|
||||||
objectStore.put({deviceKey, sessionId, session});
|
objectStore.put({
|
||||||
|
deviceKey,
|
||||||
|
sessionId,
|
||||||
|
session: sessionInfo.session,
|
||||||
|
lastReceivedMessageTs: sessionInfo.lastReceivedMessageTs,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inbound group sessions
|
// Inbound group sessions
|
||||||
|
|||||||
@@ -207,6 +207,24 @@ export default class IndexedDBCryptoStore {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look for room key requests by target device and state
|
||||||
|
*
|
||||||
|
* @param {string} userId Target user ID
|
||||||
|
* @param {string} deviceId Target device ID
|
||||||
|
* @param {Array<Number>} wantedStates list of acceptable states
|
||||||
|
*
|
||||||
|
* @return {Promise} resolves to a list of all the
|
||||||
|
* {@link module:crypto/store/base~OutgoingRoomKeyRequest}
|
||||||
|
*/
|
||||||
|
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
|
||||||
|
return this._connect().then((backend) => {
|
||||||
|
return backend.getOutgoingRoomKeyRequestsByTarget(
|
||||||
|
userId, deviceId, wantedStates,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look for an existing room key request by id and state, and update it if
|
* Look for an existing room key request by id and state, and update it if
|
||||||
* found
|
* found
|
||||||
@@ -284,7 +302,10 @@ export default class IndexedDBCryptoStore {
|
|||||||
* @param {string} sessionId The ID of the session to retrieve
|
* @param {string} sessionId The ID of the session to retrieve
|
||||||
* @param {*} txn An active transaction. See doTxn().
|
* @param {*} txn An active transaction. See doTxn().
|
||||||
* @param {function(object)} func Called with A map from sessionId
|
* @param {function(object)} func Called with A map from sessionId
|
||||||
* to Base64 end-to-end session.
|
* to session information object with 'session' key being the
|
||||||
|
* Base64 end-to-end session and lastReceivedMessageTs being the
|
||||||
|
* timestamp in milliseconds at which the session last received
|
||||||
|
* a message.
|
||||||
*/
|
*/
|
||||||
getEndToEndSession(deviceKey, sessionId, txn, func) {
|
getEndToEndSession(deviceKey, sessionId, txn, func) {
|
||||||
this._backendPromise.value().getEndToEndSession(deviceKey, sessionId, txn, func);
|
this._backendPromise.value().getEndToEndSession(deviceKey, sessionId, txn, func);
|
||||||
@@ -296,7 +317,10 @@ export default class IndexedDBCryptoStore {
|
|||||||
* @param {string} deviceKey The public key of the other device.
|
* @param {string} deviceKey The public key of the other device.
|
||||||
* @param {*} txn An active transaction. See doTxn().
|
* @param {*} txn An active transaction. See doTxn().
|
||||||
* @param {function(object)} func Called with A map from sessionId
|
* @param {function(object)} func Called with A map from sessionId
|
||||||
* to Base64 end-to-end session.
|
* to session information object with 'session' key being the
|
||||||
|
* Base64 end-to-end session and lastReceivedMessageTs being the
|
||||||
|
* timestamp in milliseconds at which the session last received
|
||||||
|
* a message.
|
||||||
*/
|
*/
|
||||||
getEndToEndSessions(deviceKey, txn, func) {
|
getEndToEndSessions(deviceKey, txn, func) {
|
||||||
this._backendPromise.value().getEndToEndSessions(deviceKey, txn, func);
|
this._backendPromise.value().getEndToEndSessions(deviceKey, txn, func);
|
||||||
@@ -306,12 +330,12 @@ export default class IndexedDBCryptoStore {
|
|||||||
* Store a session between the logged-in user and another device
|
* Store a session between the logged-in user and another device
|
||||||
* @param {string} deviceKey The public key of the other device.
|
* @param {string} deviceKey The public key of the other device.
|
||||||
* @param {string} sessionId The ID for this end-to-end session.
|
* @param {string} sessionId The ID for this end-to-end session.
|
||||||
* @param {string} session Base64 encoded end-to-end session.
|
* @param {string} sessionInfo Session information object
|
||||||
* @param {*} txn An active transaction. See doTxn().
|
* @param {*} txn An active transaction. See doTxn().
|
||||||
*/
|
*/
|
||||||
storeEndToEndSession(deviceKey, sessionId, session, txn) {
|
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
|
||||||
this._backendPromise.value().storeEndToEndSession(
|
this._backendPromise.value().storeEndToEndSession(
|
||||||
deviceKey, sessionId, session, txn,
|
deviceKey, sessionId, sessionInfo, txn,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -68,7 +68,21 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore {
|
|||||||
}
|
}
|
||||||
|
|
||||||
_getEndToEndSessions(deviceKey, txn, func) {
|
_getEndToEndSessions(deviceKey, txn, func) {
|
||||||
return getJsonItem(this.store, keyEndToEndSessions(deviceKey));
|
const sessions = getJsonItem(this.store, keyEndToEndSessions(deviceKey));
|
||||||
|
const fixedSessions = {};
|
||||||
|
|
||||||
|
// fix up any old sessions to be objects rather than just the base64 pickle
|
||||||
|
for (const [sid, val] of Object.entries(sessions || {})) {
|
||||||
|
if (typeof val === 'string') {
|
||||||
|
fixedSessions[sid] = {
|
||||||
|
session: val,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
fixedSessions[sid] = val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return fixedSessions;
|
||||||
}
|
}
|
||||||
|
|
||||||
getEndToEndSession(deviceKey, sessionId, txn, func) {
|
getEndToEndSession(deviceKey, sessionId, txn, func) {
|
||||||
@@ -80,9 +94,9 @@ export default class LocalStorageCryptoStore extends MemoryCryptoStore {
|
|||||||
func(this._getEndToEndSessions(deviceKey) || {});
|
func(this._getEndToEndSessions(deviceKey) || {});
|
||||||
}
|
}
|
||||||
|
|
||||||
storeEndToEndSession(deviceKey, sessionId, session, txn) {
|
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
|
||||||
const sessions = this._getEndToEndSessions(deviceKey) || {};
|
const sessions = this._getEndToEndSessions(deviceKey) || {};
|
||||||
sessions[sessionId] = session;
|
sessions[sessionId] = sessionInfo;
|
||||||
setJsonItem(
|
setJsonItem(
|
||||||
this.store, keyEndToEndSessions(deviceKey), sessions,
|
this.store, keyEndToEndSessions(deviceKey), sessions,
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -147,6 +147,19 @@ export default class MemoryCryptoStore {
|
|||||||
return Promise.resolve(null);
|
return Promise.resolve(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getOutgoingRoomKeyRequestsByTarget(userId, deviceId, wantedStates) {
|
||||||
|
const results = [];
|
||||||
|
|
||||||
|
for (const req of this._outgoingRoomKeyRequests) {
|
||||||
|
for (const state of wantedStates) {
|
||||||
|
if (req.state === state && req.recipients.includes({userId, deviceId})) {
|
||||||
|
results.push(req);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Promise.resolve(results);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Look for an existing room key request by id and state, and update it if
|
* Look for an existing room key request by id and state, and update it if
|
||||||
* found
|
* found
|
||||||
@@ -236,13 +249,13 @@ export default class MemoryCryptoStore {
|
|||||||
func(this._sessions[deviceKey] || {});
|
func(this._sessions[deviceKey] || {});
|
||||||
}
|
}
|
||||||
|
|
||||||
storeEndToEndSession(deviceKey, sessionId, session, txn) {
|
storeEndToEndSession(deviceKey, sessionId, sessionInfo, txn) {
|
||||||
let deviceSessions = this._sessions[deviceKey];
|
let deviceSessions = this._sessions[deviceKey];
|
||||||
if (deviceSessions === undefined) {
|
if (deviceSessions === undefined) {
|
||||||
deviceSessions = {};
|
deviceSessions = {};
|
||||||
this._sessions[deviceKey] = deviceSessions;
|
this._sessions[deviceKey] = deviceSessions;
|
||||||
}
|
}
|
||||||
deviceSessions[sessionId] = session;
|
deviceSessions[sessionId] = sessionInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Inbound Group Sessions
|
// Inbound Group Sessions
|
||||||
|
|||||||
@@ -752,6 +752,8 @@ module.exports.MatrixHttpApi.prototype = {
|
|||||||
method: method,
|
method: method,
|
||||||
withCredentials: false,
|
withCredentials: false,
|
||||||
qs: queryParams,
|
qs: queryParams,
|
||||||
|
qsStringifyOptions: opts.qsStringifyOptions,
|
||||||
|
useQuerystring: true,
|
||||||
body: data,
|
body: data,
|
||||||
json: false,
|
json: false,
|
||||||
timeout: localTimeoutMs,
|
timeout: localTimeoutMs,
|
||||||
|
|||||||
@@ -999,6 +999,10 @@ Room.prototype.addPendingEvent = function(event, txnId) {
|
|||||||
this._txnToEvent[txnId] = event;
|
this._txnToEvent[txnId] = event;
|
||||||
|
|
||||||
if (this._opts.pendingEventOrdering == "detached") {
|
if (this._opts.pendingEventOrdering == "detached") {
|
||||||
|
if (this._pendingEventList.some((e) => e.status === EventStatus.NOT_SENT)) {
|
||||||
|
console.warn("Setting event as NOT_SENT due to messages in the same state");
|
||||||
|
event.status = EventStatus.NOT_SENT;
|
||||||
|
}
|
||||||
this._pendingEventList.push(event);
|
this._pendingEventList.push(event);
|
||||||
} else {
|
} else {
|
||||||
for (let i = 0; i < this._timelineSets.length; i++) {
|
for (let i = 0; i < this._timelineSets.length; i++) {
|
||||||
|
|||||||
@@ -516,7 +516,7 @@ SyncApi.prototype.sync = function() {
|
|||||||
console.warn("InvalidStoreError: store is not usable: stopping sync.");
|
console.warn("InvalidStoreError: store is not usable: stopping sync.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (this.opts.lazyLoadMembers && this._crypto) {
|
if (this.opts.lazyLoadMembers && this.opts.crypto) {
|
||||||
this.opts.crypto.enableLazyLoading();
|
this.opts.crypto.enableLazyLoading();
|
||||||
}
|
}
|
||||||
await this.client._storeClientOptions();
|
await this.client._storeClientOptions();
|
||||||
|
|||||||
Reference in New Issue
Block a user