diff --git a/package.json b/package.json index 5e004136c0f..c5b2d954c0a 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "build": "babel -s -d lib src && rimraf dist && mkdir dist && browserify -d browser-index.js | exorcist dist/browser-matrix.js.map > dist/browser-matrix.js && uglifyjs -c -m -o dist/browser-matrix.min.js --source-map dist/browser-matrix.min.js.map --in-source-map dist/browser-matrix.js.map dist/browser-matrix.js", "dist": "npm run build", "watch": "watchify -d browser-index.js -o 'exorcist dist/browser-matrix.js.map > dist/browser-matrix.js' -v", - "lint": "eslint --max-warnings 113 src spec", + "lint": "eslint --max-warnings 112 src spec", "prepublish": "npm run build && git rev-parse HEAD > git-revision.txt" }, "repository": { diff --git a/spec/integ/megolm.spec.js b/spec/integ/megolm-integ.spec.js similarity index 100% rename from spec/integ/megolm.spec.js rename to spec/integ/megolm-integ.spec.js diff --git a/spec/unit/crypto/algorithms/megolm.spec.js b/spec/unit/crypto/algorithms/megolm.spec.js new file mode 100644 index 00000000000..1383cecbd90 --- /dev/null +++ b/spec/unit/crypto/algorithms/megolm.spec.js @@ -0,0 +1,160 @@ +try { + global.Olm = require('olm'); +} catch (e) { + console.warn("unable to run megolm tests: libolm not available"); +} + +import expect from 'expect'; +import q from 'q'; + +import sdk from '../../../..'; +import algorithms from '../../../../lib/crypto/algorithms'; +import WebStorageSessionStore from '../../../../lib/store/session/webstorage'; +import MockStorageApi from '../../../MockStorageApi'; +import testUtils from '../../../test-utils'; + +// Crypto and OlmDevice won't import unless we have global.Olm +let OlmDevice; +let Crypto; +if (global.Olm) { + OlmDevice = require('../../../../lib/crypto/OlmDevice'); + Crypto = require('../../../../lib/crypto'); +} + +const MatrixEvent = sdk.MatrixEvent; +const MegolmDecryption = algorithms.DECRYPTION_CLASSES['m.megolm.v1.aes-sha2']; + +const ROOM_ID = '!ROOM:ID'; + +describe("MegolmDecryption", function() { + if (!global.Olm) { + console.warn('Not running megolm unit tests: libolm not present'); + return; + } + + let megolmDecryption; + let mockOlmLib; + let mockCrypto; + let mockBaseApis; + + beforeEach(function() { + testUtils.beforeEach(this); // eslint-disable-line no-invalid-this + + mockCrypto = testUtils.mock(Crypto, 'Crypto'); + mockBaseApis = {}; + + const mockStorage = new MockStorageApi(); + const sessionStore = new WebStorageSessionStore(mockStorage); + + const olmDevice = new OlmDevice(sessionStore); + + megolmDecryption = new MegolmDecryption({ + userId: '@user:id', + crypto: mockCrypto, + olmDevice: olmDevice, + baseApis: mockBaseApis, + roomId: ROOM_ID, + }); + + + // we stub out the olm encryption bits + mockOlmLib = {}; + mockOlmLib.ensureOlmSessionsForDevices = expect.createSpy(); + mockOlmLib.encryptMessageForDevice = expect.createSpy(); + megolmDecryption.olmlib = mockOlmLib; + }); + + describe('receives some keys:', function() { + let groupSession; + beforeEach(function() { + groupSession = new global.Olm.OutboundGroupSession(); + groupSession.create(); + + const event = new MatrixEvent({}); + event.setClearData( + { + type: 'm.room_key', + content: { + algorithm: 'm.megolm.v1.aes-sha2', + room_id: ROOM_ID, + session_id: groupSession.session_id(), + session_key: groupSession.session_key(), + }, + }, + "SENDER_CURVE25519", + "SENDER_ED25519", + ); + + megolmDecryption.onRoomKeyEvent(event); + }); + + it('can decrypt an event', function() { + const event = new MatrixEvent({ + type: 'm.room.encrypted', + room_id: ROOM_ID, + content: { + algorithm: 'm.megolm.v1.aes-sha2', + sender_key: "SENDER_CURVE25519", + session_id: groupSession.session_id(), + ciphertext: groupSession.encrypt(JSON.stringify({ + room_id: ROOM_ID, + content: 'testytest', + })), + }, + }); + + megolmDecryption.decryptEvent(event); + expect(event.getContent()).toEqual('testytest'); + }); + + it('can respond to a key request event', function() { + const keyRequest = { + userId: '@alice:foo', + deviceId: 'alidevice', + requestBody: { + room_id: ROOM_ID, + sender_key: "SENDER_CURVE25519", + session_id: groupSession.session_id(), + }, + }; + + expect(megolmDecryption.hasKeysForKeyRequest(keyRequest)) + .toBe(true); + + // set up some pre-conditions for the share call + const deviceInfo = {}; + mockCrypto.getStoredDevice.andReturn(deviceInfo); + mockOlmLib.ensureOlmSessionsForDevices.andReturn( + q({'@alice:foo': {'alidevice': { + sessionId: 'alisession', + }}}), + ); + mockBaseApis.sendToDevice = expect.createSpy(); + + + // do the share + megolmDecryption.shareKeysWithDevice(keyRequest); + + // it's asynchronous, so we have to wait a bit + return q.delay(1).then(() => { + // check that it called encryptMessageForDevice with + // appropriate args. + expect(mockOlmLib.encryptMessageForDevice.calls.length) + .toEqual(1); + + const call = mockOlmLib.encryptMessageForDevice.calls[0]; + const payload = call.arguments[6]; + + expect(payload.type).toEqual("m.forwarded_room_key"); + expect(payload.content).toInclude({ + sender_key: "SENDER_CURVE25519", + sender_claimed_ed25519_key: "SENDER_ED25519", + session_id: groupSession.session_id(), + chain_index: 0, + forwarding_curve25519_key_chain: [], + }); + expect(payload.content.session_key).toExist(); + }); + }); + }); +}); diff --git a/src/crypto/OlmDevice.js b/src/crypto/OlmDevice.js index 40520596782..0f4615958ca 100644 --- a/src/crypto/OlmDevice.js +++ b/src/crypto/OlmDevice.js @@ -55,6 +55,8 @@ function checkPayloadLength(payloadString) { * * @typedef {Object} module:crypto/OlmDevice.MegolmSessionData * @property {String} sender_key Sender's Curve25519 device key + * @property {String[]} forwarding_curve25519_key_chain Devices which forwarded + * this session to us (normally empty). * @property {Object} sender_claimed_keys Other keys the sender claims. * @property {String} room_id Room this session is used in * @property {String} session_id Unique id for the session @@ -580,27 +582,30 @@ OlmDevice.prototype.getOutboundGroupSessionKey = function(sessionId) { // Inbound group session // ===================== +/** + * data stored in the session store about an inbound group session + * + * @typedef {Object} InboundGroupSessionData + * @property {string} room_Id + * @property {string} session pickled Olm.InboundGroupSession + * @property {Object} keysClaimed + * @property {[string]} forwardingCurve25519KeyChain Devices involved in forwarding + * this session to us (normally empty). + */ + /** * store an InboundGroupSession in the session store * - * @param {string} roomId * @param {string} senderCurve25519Key * @param {string} sessionId - * @param {Olm.InboundGroupSession} session - * @param {object} keysClaimed Other keys the sender claims. + * @param {InboundGroupSessionData} sessionData * @private */ OlmDevice.prototype._saveInboundGroupSession = function( - roomId, senderCurve25519Key, sessionId, session, keysClaimed, + senderCurve25519Key, sessionId, sessionData, ) { - const r = { - room_id: roomId, - session: session.pickle(this._pickleKey), - keysClaimed: keysClaimed, - }; - this._sessionStore.storeEndToEndInboundGroupSession( - senderCurve25519Key, sessionId, JSON.stringify(r), + senderCurve25519Key, sessionId, JSON.stringify(sessionData), ); }; @@ -610,8 +615,8 @@ OlmDevice.prototype._saveInboundGroupSession = function( * @param {string} roomId * @param {string} senderKey * @param {string} sessionId - * @param {function(Olm.InboundGroupSession, Object): T} func - * function to call. Second argument is the map of keys claimed by the session. + * @param {function(Olm.InboundGroupSession, InboundGroupSessionData): T} func + * function to call. * * @return {null} the sessionId is unknown * @@ -645,7 +650,7 @@ OlmDevice.prototype._getInboundGroupSession = function( const session = new Olm.InboundGroupSession(); try { session.unpickle(this._pickleKey, r.session); - return func(session, r.keysClaimed || {}); + return func(session, r); } finally { session.free(); } @@ -656,17 +661,23 @@ OlmDevice.prototype._getInboundGroupSession = function( * * @param {string} roomId room in which this session will be used * @param {string} senderKey base64-encoded curve25519 key of the sender + * @param {string[]} forwardingCurve25519KeyChain Devices involved in forwarding + * this session to us. * @param {string} sessionId session identifier * @param {string} sessionKey base64-encoded secret key * @param {Object} keysClaimed Other keys the sender claims. + * @param {boolean} exportFormat true if the megolm keys are in export format + * (ie, they lack an ed25519 signature) */ OlmDevice.prototype.addInboundGroupSession = function( - roomId, senderKey, sessionId, sessionKey, keysClaimed, + roomId, senderKey, forwardingCurve25519KeyChain, + sessionId, sessionKey, keysClaimed, + exportFormat, ) { const self = this; /* if we already have this session, consider updating it */ - function updateSession(session) { + function updateSession(session, sessionData) { console.log("Update for megolm session " + senderKey + "/" + sessionId); // for now we just ignore updates. TODO: implement something here @@ -684,14 +695,26 @@ OlmDevice.prototype.addInboundGroupSession = function( // new session. const session = new Olm.InboundGroupSession(); try { - session.create(sessionKey); + if (exportFormat) { + session.import_session(sessionKey); + } else { + session.create(sessionKey); + } if (sessionId != session.session_id()) { throw new Error( "Mismatched group session ID from senderKey: " + senderKey, ); } + + const sessionData = { + room_id: roomId, + session: session.pickle(this._pickleKey), + keysClaimed: keysClaimed, + forwardingCurve25519KeyChain: forwardingCurve25519KeyChain, + }; + self._saveInboundGroupSession( - roomId, senderKey, sessionId, session, keysClaimed, + senderKey, sessionId, sessionData, ); } finally { session.free(); @@ -706,7 +729,7 @@ OlmDevice.prototype.addInboundGroupSession = function( */ OlmDevice.prototype.importInboundGroupSession = function(data) { /* if we already have this session, consider updating it */ - function updateSession(session) { + function updateSession(session, sessionData) { console.log("Update for megolm session " + data.sender_key + "|" + data.session_id); // for now we just ignore updates. TODO: implement something here @@ -731,9 +754,16 @@ OlmDevice.prototype.importInboundGroupSession = function(data) { "Mismatched group session ID from senderKey: " + data.sender_key, ); } + + const sessionData = { + room_id: data.room_id, + session: session.pickle(this._pickleKey), + keysClaimed: data.sender_claimed_keys, + forwardingCurve25519KeyChain: data.forwarding_curve25519_key_chain, + }; + this._saveInboundGroupSession( - data.room_id, data.sender_key, data.session_id, session, - data.sender_claimed_keys, + data.sender_key, data.session_id, sessionData, ); } finally { session.free(); @@ -750,15 +780,16 @@ OlmDevice.prototype.importInboundGroupSession = function(data) { * * @return {null} the sessionId is unknown * - * @return {{result: string, keysProved: Object, keysClaimed: - * Object}} result + * @return {{result: string, senderKey: string, + * forwardingCurve25519KeyChain: [string], + * keysClaimed: Object}} */ OlmDevice.prototype.decryptGroupMessage = function( roomId, senderKey, sessionId, body, ) { const self = this; - function decrypt(session, keysClaimed) { + function decrypt(session, sessionData) { const res = session.decrypt(body); let plaintext = res.plaintext; @@ -777,17 +808,15 @@ OlmDevice.prototype.decryptGroupMessage = function( self._inboundGroupSessionMessageIndexes[messageIndexKey] = true; } - // the sender must have had the senderKey to persuade us to save the - // session. - const keysProved = {curve25519: senderKey}; - + sessionData.session = session.pickle(self._pickleKey); self._saveInboundGroupSession( - roomId, senderKey, sessionId, session, keysClaimed, + senderKey, sessionId, sessionData, ); return { result: plaintext, - keysClaimed: keysClaimed, - keysProved: keysProved, + keysClaimed: sessionData.keysClaimed || {}, + senderKey: senderKey, + forwardingCurve25519KeyChain: sessionData.forwardingCurve25519KeyChain || [], }; } @@ -834,16 +863,26 @@ OlmDevice.prototype.hasInboundSessionKeys = function(roomId, senderKey, sessionI * @param {string} senderKey base64-encoded curve25519 key of the sender * @param {string} sessionId session identifier * - * @returns {{chain_index: number, key: string}} details of the session key. The - * key is a base64-encoded megolm key in export format. + * @returns {{chain_index: number, key: string, + * forwarding_curve25519_key_chain: [string], + * sender_claimed_ed25519_key: string, + * }} + * details of the session key. The key is a base64-encoded megolm key in + * export format. */ OlmDevice.prototype.getInboundGroupSessionKey = function(roomId, senderKey, sessionId) { - function getKey(session, keysClaimed) { + function getKey(session, sessionData) { const messageIndex = session.first_known_index(); + const claimedKeys = sessionData.keysClaimed || {}; + const senderEd25519Key = claimedKeys.ed25519 || null; + return { "chain_index": messageIndex, "key": session.export_session(messageIndex), + "forwarding_curve25519_key_chain": + sessionData.forwardingCurve25519KeyChain || [], + "sender_claimed_ed25519_key": senderEd25519Key, }; } @@ -882,6 +921,8 @@ OlmDevice.prototype.exportInboundGroupSession = function(senderKey, sessionId) { "room_id": r.room_id, "session_id": sessionId, "session_key": session.export_session(messageIndex), + "forwarding_curve25519_key_chain": + session.forwardingCurve25519KeyChain || [], }; } finally { session.free(); diff --git a/src/crypto/algorithms/megolm.js b/src/crypto/algorithms/megolm.js index 697ffded850..61c44e5e08e 100644 --- a/src/crypto/algorithms/megolm.js +++ b/src/crypto/algorithms/megolm.js @@ -246,15 +246,15 @@ MegolmEncryption.prototype._ensureOutboundSession = function(devicesInRoom) { * @return {module:crypto/algorithms/megolm.OutboundSessionInfo} session */ MegolmEncryption.prototype._prepareNewSession = function() { - const session_id = this._olmDevice.createOutboundGroupSession(); - const key = this._olmDevice.getOutboundGroupSessionKey(session_id); + const sessionId = this._olmDevice.createOutboundGroupSession(); + const key = this._olmDevice.getOutboundGroupSessionKey(sessionId); this._olmDevice.addInboundGroupSession( - this._roomId, this._olmDevice.deviceCurve25519Key, session_id, + this._roomId, this._olmDevice.deviceCurve25519Key, [], sessionId, key.key, {ed25519: this._olmDevice.deviceEd25519Key}, ); - return new OutboundSessionInfo(session_id); + return new OutboundSessionInfo(sessionId); }; /** @@ -520,6 +520,9 @@ function MegolmDecryption(params) { // events which we couldn't decrypt due to unknown sessions / indexes: map from // senderKey|sessionId to list of MatrixEvents this._pendingEvents = {}; + + // this gets stubbed out by the unit tests. + this.olmlib = olmlib; } utils.inherits(MegolmDecryption, base.DecryptionAlgorithm); @@ -592,7 +595,8 @@ MegolmDecryption.prototype._decryptEvent = function(event, requestKeysOnFail) { ); } - event.setClearData(payload, res.keysProved, res.keysClaimed); + event.setClearData(payload, res.senderKey, res.keysClaimed.ed25519, + res.forwardingCurve25519KeyChain); }; MegolmDecryption.prototype._requestKeysForEvent = function(event) { @@ -642,8 +646,11 @@ MegolmDecryption.prototype._addEventToPendingList = function(event) { */ MegolmDecryption.prototype.onRoomKeyEvent = function(event) { const content = event.getContent(); - const senderKey = event.getSenderKey(); const sessionId = content.session_id; + let senderKey = event.getSenderKey(); + let forwardingKeyChain = []; + let exportFormat = false; + let keysClaimed; if (!content.room_id || !sessionId || @@ -652,15 +659,49 @@ MegolmDecryption.prototype.onRoomKeyEvent = function(event) { console.error("key event is missing fields"); return; } + if (!senderKey) { console.error("key event has no sender key (not encrypted?)"); return; } + if (event.getType() == "m.forwarded_room_key") { + exportFormat = true; + forwardingKeyChain = content.forwarding_curve25519_key_chain; + if (!utils.isArray(forwardingKeyChain)) { + forwardingKeyChain = []; + } + + // copy content before we modify it + forwardingKeyChain = forwardingKeyChain.slice(); + forwardingKeyChain.push(senderKey); + + senderKey = content.sender_key; + if (!senderKey) { + console.error("forwarded_room_key event is missing sender_key field"); + return; + } + + const ed25519Key = content.sender_claimed_ed25519_key; + if (!ed25519Key) { + console.error( + `forwarded_room_key_event is missing sender_claimed_ed25519_key field`, + ); + return; + } + + keysClaimed = { + ed25519: ed25519Key, + }; + } else { + keysClaimed = event.getKeysClaimed(); + } + console.log(`Adding key for megolm session ${senderKey}|${sessionId}`); this._olmDevice.addInboundGroupSession( - content.room_id, senderKey, sessionId, - content.session_key, event.getKeysClaimed(), + content.room_id, senderKey, forwardingKeyChain, sessionId, + content.session_key, keysClaimed, + exportFormat, ); // cancel any outstanding room key requests for this session @@ -698,7 +739,7 @@ MegolmDecryption.prototype.shareKeysWithDevice = function(keyRequest) { const deviceInfo = this._crypto.getStoredDevice(userId, deviceId); const body = keyRequest.requestBody; - olmlib.ensureOlmSessionsForDevices( + this.olmlib.ensureOlmSessionsForDevices( this._olmDevice, this._baseApis, { [userId]: [deviceInfo], }, @@ -719,29 +760,17 @@ MegolmDecryption.prototype.shareKeysWithDevice = function(keyRequest) { + userId + ":" + deviceId, ); - const key = this._olmDevice.getInboundGroupSessionKey( + const payload = this._buildKeyForwardingMessage( body.room_id, body.sender_key, body.session_id, ); - const payload = { - type: "m.forwarded_room_key", - content: { - algorithm: olmlib.MEGOLM_ALGORITHM, - room_id: body.room_id, - sender_key: body.sender_key, - session_id: body.session_id, - session_key: key.key, - chain_index: key.chain_index, - }, - }; - const encryptedContent = { algorithm: olmlib.OLM_ALGORITHM, sender_key: this._olmDevice.deviceCurve25519Key, ciphertext: {}, }; - olmlib.encryptMessageForDevice( + this.olmlib.encryptMessageForDevice( encryptedContent.ciphertext, this._userId, this._deviceId, @@ -762,6 +791,27 @@ MegolmDecryption.prototype.shareKeysWithDevice = function(keyRequest) { }).done(); }; +MegolmDecryption.prototype._buildKeyForwardingMessage = function( + roomId, senderKey, sessionId, +) { + const key = this._olmDevice.getInboundGroupSessionKey( + roomId, senderKey, sessionId, + ); + + return { + type: "m.forwarded_room_key", + content: { + algorithm: olmlib.MEGOLM_ALGORITHM, + room_id: roomId, + sender_key: senderKey, + sender_claimed_ed25519_key: key.sender_claimed_ed25519_key, + session_id: sessionId, + session_key: key.key, + chain_index: key.chain_index, + forwarding_curve25519_key_chain: key.forwarding_curve25519_key_chain, + }, + }; +}; /** * @inheritdoc diff --git a/src/crypto/algorithms/olm.js b/src/crypto/algorithms/olm.js index f8715b1e2d4..f3c7967ebd1 100644 --- a/src/crypto/algorithms/olm.js +++ b/src/crypto/algorithms/olm.js @@ -222,7 +222,8 @@ OlmDecryption.prototype.decryptEvent = function(event) { ); } - event.setClearData(payload, {curve25519: deviceKey}, payload.keys || {}); + const claimedKeys = payload.keys || {}; + event.setClearData(payload, deviceKey, claimedKeys.ed25519 || null); }; diff --git a/src/crypto/index.js b/src/crypto/index.js index 780bd519810..89d8e56a433 100644 --- a/src/crypto/index.js +++ b/src/crypto/index.js @@ -161,7 +161,8 @@ function _registerEventHandlers(crypto, eventEmitter) { eventEmitter.on("toDeviceEvent", function(event) { try { - if (event.getType() == "m.room_key") { + if (event.getType() == "m.room_key" + || event.getType() == "m.forwarded_room_key") { crypto._onRoomKeyEvent(event); } else if (event.getType() == "m.new_device") { crypto._onNewDeviceEvent(event); @@ -537,6 +538,13 @@ Crypto.prototype.getEventSenderDeviceInfo = function(event) { return null; } + const forwardingChain = event.getForwardingCurve25519KeyChain(); + if (forwardingChain.length > 0) { + // we got this event from somewhere else + // TODO: check if we can trust the forwarders. + return null; + } + // senderKey is the Curve25519 identity key of the device which the event // was sent from. In the case of Megolm, it's actually the Curve25519 // identity key of the device which set up the Megolm session. @@ -558,7 +566,7 @@ Crypto.prototype.getEventSenderDeviceInfo = function(event) { // // (see https://github.com/vector-im/vector-web/issues/2215) - const claimedKey = event.getKeysClaimed().ed25519; + const claimedKey = event.getClaimedEd25519Key(); if (!claimedKey) { console.warn("Event " + event.getId() + " claims no ed25519 key: " + "cannot verify sending device"); @@ -765,18 +773,15 @@ Crypto.prototype.encryptEventIfNeeded = function(event, room) { return null; } - // We can claim and prove ownership of all our device keys in the local - // echo of the event since we know that all the local echos come from - // this device. - const myKeys = { - curve25519: this._olmDevice.deviceCurve25519Key, - ed25519: this._olmDevice.deviceEd25519Key, - }; - return alg.encryptMessage( room, event.getType(), event.getContent(), - ).then(function(encryptedContent) { - event.makeEncrypted("m.room.encrypted", encryptedContent, myKeys); + ).then((encryptedContent) => { + event.makeEncrypted( + "m.room.encrypted", + encryptedContent, + this._olmDevice.deviceCurve25519Key, + this._olmDevice.deviceEd25519Key, + ); }); }; diff --git a/src/models/event.js b/src/models/event.js index a243677cea5..cbe2fd91db4 100644 --- a/src/models/event.js +++ b/src/models/event.js @@ -112,8 +112,22 @@ module.exports.MatrixEvent = function MatrixEvent( new Date(this.event.origin_server_ts) : null; this._clearEvent = {}; - this._keysProved = {}; - this._keysClaimed = {}; + + /* curve25519 key which we believe belongs to the sender of the event. See + * getSenderKey() + */ + this._senderCurve25519Key = null; + + /* ed25519 key which the sender of this event (for olm) or the creator of + * the megolm session (for megolm) claims to own. See getClaimedEd25519Key() + */ + this._claimedEd25519Key = null; + + /* curve25519 keys of devices involved in telling us about the + * _senderCurve25519Key and _claimedEd25519Key. + * See getForwardingCurve25519KeyChain(). + */ + this._forwardingCurve25519KeyChain = []; }; utils.inherits(module.exports.MatrixEvent, EventEmitter); @@ -261,9 +275,18 @@ utils.extend(module.exports.MatrixEvent.prototype, { * "m.room.encrypted" * * @param {object} crypto_content raw 'content' for the encrypted event. - * @param {object} keys The local keys claimed and proved by this event. + * + * @param {string} senderCurve25519Key curve25519 key to record for the + * sender of this event. + * See {@link module:models/event.MatrixEvent#getSenderKey}. + * + * @param {string} claimedEd25519Key claimed ed25519 key to record for the + * sender if this event. + * See {@link module:models/event.MatrixEvent#getClaimedEd25519Key} */ - makeEncrypted: function(crypto_type, crypto_content, keys) { + makeEncrypted: function( + crypto_type, crypto_content, senderCurve25519Key, claimedEd25519Key, + ) { // keep the plain-text data for 'view source' this._clearEvent = { type: this.event.type, @@ -271,8 +294,8 @@ utils.extend(module.exports.MatrixEvent.prototype, { }; this.event.type = crypto_type; this.event.content = crypto_content; - this._keysProved = keys; - this._keysClaimed = keys; + this._senderCurve25519Key = senderCurve25519Key; + this._claimedEd25519Key = claimedEd25519Key; }, /** @@ -287,16 +310,26 @@ utils.extend(module.exports.MatrixEvent.prototype, { * @param {Object} clearEvent The plaintext payload for the event * (typically containing type and content fields). * - * @param {Object=} keysProved Keys owned by the sender of this event. - * See {@link module:models/event.MatrixEvent#getKeysProved}. + * @param {string=} senderCurve25519Key Key owned by the sender of this event. + * See {@link module:models/event.MatrixEvent#getSenderKey}. * - * @param {Object=} keysClaimed Keys the sender of this event claims. - * See {@link module:models/event.MatrixEvent#getKeysClaimed}. + * @param {string=} claimedEd25519Key ed25519 key claimed by the sender of + * this event. See {@link module:models/event.MatrixEvent#getClaimedEd25519Key}. + * + * @param {string[]=} forwardingCurve25519KeyChain list of curve25519 keys + * involved in telling us about the senderCurve25519Key and claimedEd25519Key. + * See {@link module:models/event.MatrixEvent#getForwardingCurve25519KeyChain}. */ - setClearData: function(clearEvent, keysProved, keysClaimed) { + setClearData: function( + clearEvent, + senderCurve25519Key, + claimedEd25519Key, + forwardingCurve25519KeyChain, + ) { this._clearEvent = clearEvent; - this._keysProved = keysProved || {}; - this._keysClaimed = keysClaimed || {}; + this._senderCurve25519Key = senderCurve25519Key || null; + this._claimedEd25519Key = claimedEd25519Key || null; + this._forwardingCurve25519KeyChain = forwardingCurve25519KeyChain || []; this.emit("Event.decrypted", this); }, @@ -309,37 +342,72 @@ utils.extend(module.exports.MatrixEvent.prototype, { }, /** - * The curve25519 key that sent this event + * The curve25519 key for the device that we think sent this event + * + * For an Olm-encrypted event, this is inferred directly from the DH + * exchange at the start of the session: the curve25519 key is involved in + * the DH exchange, so only a device which holds the private part of that + * key can establish such a session. + * + * For a megolm-encrypted event, it is inferred from the Olm message which + * established the megolm session + * * @return {string} */ getSenderKey: function() { - return this.getKeysProved().curve25519 || null; + return this._senderCurve25519Key; }, /** - * The keys that must have been owned by the sender of this encrypted event. - *

- * These don't necessarily have to come from this event itself, but may be - * implied by the cryptographic session. + * The additional keys the sender of this encrypted event claims to possess. + * + * Just a wrapper for #getClaimedEd25519Key (q.v.) * * @return {Object} */ - getKeysProved: function() { - return this._keysProved; + getKeysClaimed: function() { + return { + ed25519: this._claimedEd25519Key, + }; + }, + + /** + * Get the ed25519 the sender of this event claims to own. + * + * For Olm messages, this claim is encoded directly in the plaintext of the + * event itself. For megolm messages, it is implied by the m.room_key event + * which established the megolm session. + * + * Until we download the device list of the sender, it's just a claim: the + * device list gives a proof that the owner of the curve25519 key used for + * this event (and returned by #getSenderKey) also owns the ed25519 key by + * signing the public curve25519 key with the ed25519 key. + * + * In general, applications should not use this method directly, but should + * instead use MatrixClient.getEventSenderDeviceInfo. + * + * @return {string} + */ + getClaimedEd25519Key: function() { + return this._claimedEd25519Key; }, /** - * The additional keys the sender of this encrypted event claims to possess. - *

- * These don't necessarily have to come from this event itself, but may be - * implied by the cryptographic session. - * For example megolm messages don't claim keys directly, but instead - * inherit a claim from the olm message that established the session. + * Get the curve25519 keys of the devices which were involved in telling us + * about the claimedEd25519Key and sender curve25519 key. * - * @return {Object} + * Normally this will be empty, but in the case of a forwarded megolm + * session, the sender keys are sent to us by another device (the forwarding + * device), which we need to trust to do this. In that case, the result will + * be a list consisting of one entry. + * + * If the device that sent us the key (A) got it from another device which + * it wasn't prepared to vouch for (B), the result will be [A, B]. And so on. + * + * @return {string[]} base64-encoded curve25519 keys, from oldest to newest. */ - getKeysClaimed: function() { - return this._keysClaimed; + getForwardingCurve25519KeyChain: function() { + return this._forwardingCurve25519KeyChain; }, getUnsigned: function() {