You've already forked matrix-js-sdk
mirror of
https://github.com/matrix-org/matrix-js-sdk.git
synced 2025-12-19 10:22:30 +03:00
Merge pull request #276 from matrix-org/rav/ignore_key_reshares
Ignore reshares of known megolm sessions
This commit is contained in:
@@ -627,6 +627,24 @@ OlmDevice.prototype.addInboundGroupSession = function(
|
|||||||
roomId, senderKey, sessionId, sessionKey, keysClaimed
|
roomId, senderKey, sessionId, sessionKey, keysClaimed
|
||||||
) {
|
) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
|
||||||
|
/* if we already have this session, consider updating it */
|
||||||
|
function updateSession(session) {
|
||||||
|
console.log("Update for megolm session " + senderKey + "/" + sessionId);
|
||||||
|
// for now we just ignore updates. TODO: implement something here
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
var r = this._getInboundGroupSession(
|
||||||
|
roomId, senderKey, sessionId, updateSession
|
||||||
|
);
|
||||||
|
|
||||||
|
if (r !== null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// new session.
|
||||||
var session = new Olm.InboundGroupSession();
|
var session = new Olm.InboundGroupSession();
|
||||||
try {
|
try {
|
||||||
session.create(sessionKey);
|
session.create(sessionKey);
|
||||||
|
|||||||
@@ -5,21 +5,6 @@ var HttpBackend = require("../mock-request");
|
|||||||
var utils = require("../../lib/utils");
|
var utils = require("../../lib/utils");
|
||||||
var test_utils = require("../test-utils");
|
var test_utils = require("../test-utils");
|
||||||
|
|
||||||
function MockStorageApi() {
|
|
||||||
this.data = {};
|
|
||||||
}
|
|
||||||
MockStorageApi.prototype = {
|
|
||||||
setItem: function(k, v) {
|
|
||||||
this.data[k] = v;
|
|
||||||
},
|
|
||||||
getItem: function(k) {
|
|
||||||
return this.data[k] || null;
|
|
||||||
},
|
|
||||||
removeItem: function(k) {
|
|
||||||
delete this.data[k];
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
var aliHttpBackend;
|
var aliHttpBackend;
|
||||||
var bobHttpBackend;
|
var bobHttpBackend;
|
||||||
var aliClient;
|
var aliClient;
|
||||||
@@ -36,7 +21,6 @@ var aliDeviceKeys;
|
|||||||
var bobDeviceKeys;
|
var bobDeviceKeys;
|
||||||
var bobDeviceCurve25519Key;
|
var bobDeviceCurve25519Key;
|
||||||
var bobDeviceEd25519Key;
|
var bobDeviceEd25519Key;
|
||||||
var aliLocalStore;
|
|
||||||
var aliStorage;
|
var aliStorage;
|
||||||
var bobStorage;
|
var bobStorage;
|
||||||
var aliMessages;
|
var aliMessages;
|
||||||
@@ -461,11 +445,9 @@ describe("MatrixClient crypto", function() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
aliLocalStore = new MockStorageApi();
|
|
||||||
aliStorage = new sdk.WebStorageSessionStore(aliLocalStore);
|
|
||||||
bobStorage = new sdk.WebStorageSessionStore(new MockStorageApi());
|
|
||||||
test_utils.beforeEach(this);
|
test_utils.beforeEach(this);
|
||||||
|
|
||||||
|
aliStorage = new sdk.WebStorageSessionStore(new test_utils.MockStorageApi());
|
||||||
aliHttpBackend = new HttpBackend();
|
aliHttpBackend = new HttpBackend();
|
||||||
aliClient = sdk.createClient({
|
aliClient = sdk.createClient({
|
||||||
baseUrl: "http://alis.server",
|
baseUrl: "http://alis.server",
|
||||||
@@ -476,6 +458,7 @@ describe("MatrixClient crypto", function() {
|
|||||||
request: aliHttpBackend.requestFn,
|
request: aliHttpBackend.requestFn,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
bobStorage = new sdk.WebStorageSessionStore(new test_utils.MockStorageApi());
|
||||||
bobHttpBackend = new HttpBackend();
|
bobHttpBackend = new HttpBackend();
|
||||||
bobClient = sdk.createClient({
|
bobClient = sdk.createClient({
|
||||||
baseUrl: "http://bobs.server",
|
baseUrl: "http://bobs.server",
|
||||||
|
|||||||
366
spec/integ/megolm.spec.js
Normal file
366
spec/integ/megolm.spec.js
Normal file
@@ -0,0 +1,366 @@
|
|||||||
|
/*
|
||||||
|
Copyright 2016 OpenMarket 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.
|
||||||
|
*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
|
||||||
|
|
||||||
|
try {
|
||||||
|
var Olm = require('olm');
|
||||||
|
} catch (e) {}
|
||||||
|
|
||||||
|
var sdk = require('../..');
|
||||||
|
var utils = require('../../lib/utils');
|
||||||
|
var test_utils = require('../test-utils');
|
||||||
|
var MockHttpBackend = require('../mock-request');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrapper for a MockStorageApi, MockHttpBackend and MatrixClient
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
* @param {string} userId
|
||||||
|
* @param {string} deviceId
|
||||||
|
* @param {string} accessToken
|
||||||
|
*/
|
||||||
|
function TestClient(userId, deviceId, accessToken) {
|
||||||
|
this.userId = userId;
|
||||||
|
this.deviceId = deviceId;
|
||||||
|
|
||||||
|
this.storage = new sdk.WebStorageSessionStore(new test_utils.MockStorageApi());
|
||||||
|
this.httpBackend = new MockHttpBackend();
|
||||||
|
this.client = sdk.createClient({
|
||||||
|
baseUrl: "http://test.server",
|
||||||
|
userId: userId,
|
||||||
|
accessToken: accessToken,
|
||||||
|
deviceId: deviceId,
|
||||||
|
sessionStore: this.storage,
|
||||||
|
request: this.httpBackend.requestFn,
|
||||||
|
});
|
||||||
|
|
||||||
|
this.deviceKeys = null;
|
||||||
|
this.oneTimeKeys = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* start the client, and wait for it to initialise.
|
||||||
|
*
|
||||||
|
* @return {Promise}
|
||||||
|
*/
|
||||||
|
TestClient.prototype.start = function() {
|
||||||
|
var self = this;
|
||||||
|
this.httpBackend.when("GET", "/pushrules").respond(200, {});
|
||||||
|
this.httpBackend.when("POST", "/filter").respond(200, { filter_id: "fid" });
|
||||||
|
this.httpBackend.when("POST", "/keys/upload").respond(200, function(path, content) {
|
||||||
|
expect(content.one_time_keys).not.toBeDefined();
|
||||||
|
expect(content.device_keys).toBeDefined();
|
||||||
|
self.deviceKeys = content.device_keys;
|
||||||
|
return {one_time_key_counts: {signed_curve25519: 0}};
|
||||||
|
});
|
||||||
|
this.httpBackend.when("POST", "/keys/upload").respond(200, function(path, content) {
|
||||||
|
expect(content.device_keys).not.toBeDefined();
|
||||||
|
expect(content.one_time_keys).toBeDefined();
|
||||||
|
expect(content.one_time_keys).not.toEqual({});
|
||||||
|
self.oneTimeKeys = content.one_time_keys;
|
||||||
|
return {one_time_key_counts: {
|
||||||
|
signed_curve25519: utils.keys(self.oneTimeKeys).length
|
||||||
|
}};
|
||||||
|
});
|
||||||
|
|
||||||
|
this.client.startClient();
|
||||||
|
|
||||||
|
return this.httpBackend.flush();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* stop the client
|
||||||
|
*/
|
||||||
|
TestClient.prototype.stop = function() {
|
||||||
|
this.client.stopClient();
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* get the uploaded curve25519 device key
|
||||||
|
*
|
||||||
|
* @return {string} base64 device key
|
||||||
|
*/
|
||||||
|
TestClient.prototype.getDeviceKey = function() {
|
||||||
|
var key_id = 'curve25519:' + this.deviceId;
|
||||||
|
return this.deviceKeys.keys[key_id];
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* start an Olm session with a given recipient
|
||||||
|
*
|
||||||
|
* @param {Olm.Account} olmAccount
|
||||||
|
* @param {TestClient} recipientTestClient
|
||||||
|
* @return {Olm.Session}
|
||||||
|
*/
|
||||||
|
function createOlmSession(olmAccount, recipientTestClient) {
|
||||||
|
var otk_id = utils.keys(recipientTestClient.oneTimeKeys)[0];
|
||||||
|
var otk = recipientTestClient.oneTimeKeys[otk_id];
|
||||||
|
|
||||||
|
var session = new Olm.Session();
|
||||||
|
session.create_outbound(
|
||||||
|
olmAccount, recipientTestClient.getDeviceKey(), otk.key
|
||||||
|
);
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* encrypt an event with olm
|
||||||
|
*
|
||||||
|
* @param {object} opts
|
||||||
|
* @param {string=} opts.sender
|
||||||
|
* @param {string} opts.senderKey
|
||||||
|
* @param {string} opts.recipientKey
|
||||||
|
* @param {Olm.Session} opts.p2pSession
|
||||||
|
* @param {object} opts.plaintext
|
||||||
|
*
|
||||||
|
* @return {object} event
|
||||||
|
*/
|
||||||
|
function encryptOlmEvent(opts) {
|
||||||
|
expect(opts.senderKey).toBeDefined();
|
||||||
|
expect(opts.p2pSession).toBeDefined();
|
||||||
|
expect(opts.plaintext).toBeDefined();
|
||||||
|
expect(opts.recipientKey).toBeDefined();
|
||||||
|
|
||||||
|
var event = {
|
||||||
|
content: {
|
||||||
|
algorithm: "m.olm.v1.curve25519-aes-sha2",
|
||||||
|
ciphertext: {},
|
||||||
|
sender_key: opts.senderKey,
|
||||||
|
},
|
||||||
|
sender: opts.sender || "@bob:xyz",
|
||||||
|
type: "m.room.encrypted",
|
||||||
|
};
|
||||||
|
event.content.ciphertext[opts.recipientKey] =
|
||||||
|
opts.p2pSession.encrypt(JSON.stringify(opts.plaintext));
|
||||||
|
return event;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* encrypt an event with megolm
|
||||||
|
*
|
||||||
|
* @param {object} opts
|
||||||
|
* @param {string} opts.senderKey
|
||||||
|
* @param {Olm.OutboundGroupSession} opts.groupSession
|
||||||
|
* @param {object=} opts.plaintext
|
||||||
|
* @param {string=} opts.room_id
|
||||||
|
*
|
||||||
|
* @return {object} event
|
||||||
|
*/
|
||||||
|
function encryptMegolmEvent(opts) {
|
||||||
|
expect(opts.senderKey).toBeDefined();
|
||||||
|
expect(opts.groupSession).toBeDefined();
|
||||||
|
|
||||||
|
var plaintext = opts.plaintext || {};
|
||||||
|
if (!plaintext.content) {
|
||||||
|
plaintext.content = {
|
||||||
|
body: '42',
|
||||||
|
msgtype: "m.text",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!plaintext.type) {
|
||||||
|
plaintext.type = "m.room.message";
|
||||||
|
}
|
||||||
|
if (!plaintext.room_id) {
|
||||||
|
expect(opts.room_id).toBeDefined();
|
||||||
|
plaintext.room_id = opts.room_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
content: {
|
||||||
|
algorithm: "m.megolm.v1.aes-sha2",
|
||||||
|
ciphertext: opts.groupSession.encrypt(JSON.stringify(plaintext)),
|
||||||
|
device_id: "testDevice",
|
||||||
|
sender_key: opts.senderKey,
|
||||||
|
session_id: opts.groupSession.session_id(),
|
||||||
|
},
|
||||||
|
type: "m.room.encrypted",
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* build an encrypted room_key event to share a group session
|
||||||
|
*
|
||||||
|
* @param {object} opts
|
||||||
|
* @param {string} opts.senderKey
|
||||||
|
* @param {string} opts.recipientKey
|
||||||
|
* @param {Olm.Session} opts.p2pSession
|
||||||
|
* @param {Olm.OutboundGroupSession} opts.groupSession
|
||||||
|
* @param {string=} opts.room_id
|
||||||
|
*
|
||||||
|
* @return {object} event
|
||||||
|
*/
|
||||||
|
function encryptGroupSessionKey(opts) {
|
||||||
|
return encryptOlmEvent({
|
||||||
|
senderKey: opts.senderKey,
|
||||||
|
recipientKey: opts.recipientKey,
|
||||||
|
p2pSession: opts.p2pSession,
|
||||||
|
plaintext: {
|
||||||
|
content: {
|
||||||
|
algorithm: "m.megolm.v1.aes-sha2",
|
||||||
|
room_id: opts.room_id,
|
||||||
|
session_id: opts.groupSession.session_id(),
|
||||||
|
session_key: opts.groupSession.session_key(),
|
||||||
|
},
|
||||||
|
type: "m.room_key",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("megolm", function() {
|
||||||
|
if (!sdk.CRYPTO_ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var ROOM_ID = "!room:id";
|
||||||
|
|
||||||
|
var testOlmAccount;
|
||||||
|
var testSenderKey;
|
||||||
|
var aliceTestClient;
|
||||||
|
|
||||||
|
beforeEach(test_utils.asyncTest(function() {
|
||||||
|
test_utils.beforeEach(this);
|
||||||
|
|
||||||
|
aliceTestClient = new TestClient(
|
||||||
|
"@alice:localhost", "xzcvb", "akjgkrgjs"
|
||||||
|
);
|
||||||
|
|
||||||
|
testOlmAccount = new Olm.Account();
|
||||||
|
testOlmAccount.create();
|
||||||
|
var testE2eKeys = JSON.parse(testOlmAccount.identity_keys());
|
||||||
|
testSenderKey = testE2eKeys.curve25519;
|
||||||
|
|
||||||
|
return aliceTestClient.start();
|
||||||
|
}));
|
||||||
|
|
||||||
|
afterEach(function() {
|
||||||
|
aliceTestClient.stop();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("Alice receives a megolm message", test_utils.asyncTest(function() {
|
||||||
|
var p2pSession = createOlmSession(testOlmAccount, aliceTestClient);
|
||||||
|
|
||||||
|
var groupSession = new Olm.OutboundGroupSession();
|
||||||
|
groupSession.create();
|
||||||
|
|
||||||
|
// make the room_key event
|
||||||
|
var roomKeyEncrypted = encryptGroupSessionKey({
|
||||||
|
senderKey: testSenderKey,
|
||||||
|
recipientKey: aliceTestClient.getDeviceKey(),
|
||||||
|
p2pSession: p2pSession,
|
||||||
|
groupSession: groupSession,
|
||||||
|
room_id: ROOM_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
// encrypt a message with the group session
|
||||||
|
var messageEncrypted = encryptMegolmEvent({
|
||||||
|
senderKey: testSenderKey,
|
||||||
|
groupSession: groupSession,
|
||||||
|
room_id: ROOM_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Alice gets both the events in a single sync
|
||||||
|
var syncResponse = {
|
||||||
|
next_batch: 1,
|
||||||
|
to_device: {
|
||||||
|
events: [roomKeyEncrypted],
|
||||||
|
},
|
||||||
|
rooms: {
|
||||||
|
join: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
syncResponse.rooms.join[ROOM_ID] = {
|
||||||
|
timeline: {
|
||||||
|
events: [messageEncrypted],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse);
|
||||||
|
return aliceTestClient.httpBackend.flush("/sync", 1).then(function() {
|
||||||
|
var room = aliceTestClient.client.getRoom(ROOM_ID);
|
||||||
|
var event = room.getLiveTimeline().getEvents()[0];
|
||||||
|
expect(event.getContent().body).toEqual('42');
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
|
||||||
|
it("Alice gets a second room_key message", test_utils.asyncTest(function() {
|
||||||
|
var p2pSession = createOlmSession(testOlmAccount, aliceTestClient);
|
||||||
|
|
||||||
|
var groupSession = new Olm.OutboundGroupSession();
|
||||||
|
groupSession.create();
|
||||||
|
|
||||||
|
// make the room_key event
|
||||||
|
var roomKeyEncrypted1 = encryptGroupSessionKey({
|
||||||
|
senderKey: testSenderKey,
|
||||||
|
recipientKey: aliceTestClient.getDeviceKey(),
|
||||||
|
p2pSession: p2pSession,
|
||||||
|
groupSession: groupSession,
|
||||||
|
room_id: ROOM_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
// encrypt a message with the group session
|
||||||
|
var messageEncrypted = encryptMegolmEvent({
|
||||||
|
senderKey: testSenderKey,
|
||||||
|
groupSession: groupSession,
|
||||||
|
room_id: ROOM_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
// make a second room_key event now that we have advanced the group
|
||||||
|
// session.
|
||||||
|
var roomKeyEncrypted2 = encryptGroupSessionKey({
|
||||||
|
senderKey: testSenderKey,
|
||||||
|
recipientKey: aliceTestClient.getDeviceKey(),
|
||||||
|
p2pSession: p2pSession,
|
||||||
|
groupSession: groupSession,
|
||||||
|
room_id: ROOM_ID,
|
||||||
|
});
|
||||||
|
|
||||||
|
// on the first sync, send the best room key
|
||||||
|
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, {
|
||||||
|
next_batch: 1,
|
||||||
|
to_device: {
|
||||||
|
events: [roomKeyEncrypted1],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// on the second sync, send the advanced room key, along with the
|
||||||
|
// message. This simulates the situation where Alice has been sent a
|
||||||
|
// later copy of the room key and is reloading the client.
|
||||||
|
var syncResponse2 = {
|
||||||
|
next_batch: 2,
|
||||||
|
to_device: {
|
||||||
|
events: [roomKeyEncrypted2],
|
||||||
|
},
|
||||||
|
rooms: {
|
||||||
|
join: {},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
syncResponse2.rooms.join[ROOM_ID] = {
|
||||||
|
timeline: {
|
||||||
|
events: [messageEncrypted],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
aliceTestClient.httpBackend.when("GET", "/sync").respond(200, syncResponse2);
|
||||||
|
|
||||||
|
return aliceTestClient.httpBackend.flush("/sync", 2).then(function() {
|
||||||
|
var room = aliceTestClient.client.getRoom(ROOM_ID);
|
||||||
|
var event = room.getLiveTimeline().getEvents()[0];
|
||||||
|
expect(event.getContent().body).toEqual('42');
|
||||||
|
});
|
||||||
|
|
||||||
|
}));
|
||||||
|
});
|
||||||
@@ -159,7 +159,7 @@ module.exports.mkMessage = function(opts) {
|
|||||||
* <p>This is useful for use with integration tests which use asyncronous
|
* <p>This is useful for use with integration tests which use asyncronous
|
||||||
* methods: it can be added as a 'catch' handler in a promise chain.
|
* methods: it can be added as a 'catch' handler in a promise chain.
|
||||||
*
|
*
|
||||||
* @param {Error} error exception to be reported
|
* @param {Error} err exception to be reported
|
||||||
*
|
*
|
||||||
* @example
|
* @example
|
||||||
* it("should not throw", function(done) {
|
* it("should not throw", function(done) {
|
||||||
@@ -168,6 +168,48 @@ module.exports.mkMessage = function(opts) {
|
|||||||
* }).catch(utils.failTest).done(done);
|
* }).catch(utils.failTest).done(done);
|
||||||
* });
|
* });
|
||||||
*/
|
*/
|
||||||
module.exports.failTest = function(error) {
|
module.exports.failTest = function(err) {
|
||||||
expect(error.stack).toBe(null);
|
expect(true).toBe(false, "Testfunc threw: " + err.stack);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wrap a test function which returns a promise into a format which
|
||||||
|
* jasmine will understand.
|
||||||
|
*
|
||||||
|
* @param {Function} testfunc test function, which should return a promise
|
||||||
|
*
|
||||||
|
* @return {Function}
|
||||||
|
*
|
||||||
|
* @example
|
||||||
|
* it("should not throw", asyncTest(function() {
|
||||||
|
* return asynchronousMethod().then(function() {
|
||||||
|
* // some tests
|
||||||
|
* });
|
||||||
|
* }));
|
||||||
|
*/
|
||||||
|
module.exports.asyncTest = function(testfunc) {
|
||||||
|
return function(done) {
|
||||||
|
testfunc.call(this).catch(module.exports.failTest).done(done);
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mock implementation of webstorage
|
||||||
|
*
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
module.exports.MockStorageApi = function() {
|
||||||
|
this.data = {};
|
||||||
|
};
|
||||||
|
module.exports.MockStorageApi.prototype = {
|
||||||
|
setItem: function(k, v) {
|
||||||
|
this.data[k] = v;
|
||||||
|
},
|
||||||
|
getItem: function(k) {
|
||||||
|
return this.data[k] || null;
|
||||||
|
},
|
||||||
|
removeItem: function(k) {
|
||||||
|
delete this.data[k];
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user