1
0
mirror of https://github.com/matrix-org/matrix-js-sdk.git synced 2025-12-17 21:42:17 +03:00

Merge pull request #276 from matrix-org/rav/ignore_key_reshares

Ignore reshares of known megolm sessions
This commit is contained in:
Richard van der Hoff
2016-11-10 19:56:01 +00:00
committed by GitHub
4 changed files with 431 additions and 22 deletions

View File

@@ -627,6 +627,24 @@ OlmDevice.prototype.addInboundGroupSession = function(
roomId, senderKey, sessionId, sessionKey, keysClaimed
) {
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();
try {
session.create(sessionKey);

View File

@@ -5,21 +5,6 @@ var HttpBackend = require("../mock-request");
var utils = require("../../lib/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 bobHttpBackend;
var aliClient;
@@ -36,7 +21,6 @@ var aliDeviceKeys;
var bobDeviceKeys;
var bobDeviceCurve25519Key;
var bobDeviceEd25519Key;
var aliLocalStore;
var aliStorage;
var bobStorage;
var aliMessages;
@@ -461,11 +445,9 @@ describe("MatrixClient crypto", function() {
}
beforeEach(function() {
aliLocalStore = new MockStorageApi();
aliStorage = new sdk.WebStorageSessionStore(aliLocalStore);
bobStorage = new sdk.WebStorageSessionStore(new MockStorageApi());
test_utils.beforeEach(this);
aliStorage = new sdk.WebStorageSessionStore(new test_utils.MockStorageApi());
aliHttpBackend = new HttpBackend();
aliClient = sdk.createClient({
baseUrl: "http://alis.server",
@@ -476,6 +458,7 @@ describe("MatrixClient crypto", function() {
request: aliHttpBackend.requestFn,
});
bobStorage = new sdk.WebStorageSessionStore(new test_utils.MockStorageApi());
bobHttpBackend = new HttpBackend();
bobClient = sdk.createClient({
baseUrl: "http://bobs.server",

366
spec/integ/megolm.spec.js Normal file
View 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');
});
}));
});

View File

@@ -159,7 +159,7 @@ module.exports.mkMessage = function(opts) {
* <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.
*
* @param {Error} error exception to be reported
* @param {Error} err exception to be reported
*
* @example
* it("should not throw", function(done) {
@@ -168,6 +168,48 @@ module.exports.mkMessage = function(opts) {
* }).catch(utils.failTest).done(done);
* });
*/
module.exports.failTest = function(error) {
expect(error.stack).toBe(null);
module.exports.failTest = function(err) {
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];
}
};